spring概念理解
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器
定义了创建、配置和管理 bean 的方式
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模
块的功能如下:
核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory ,它
是工厂模式的实现。 BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范
与实际的应用程序代码分开。
Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文
包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring
框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于
Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以
将声明性事务管理集成到应用程序中。
Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不
同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异
常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次
结构。
Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包
括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结
构。
Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提
供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请
求以及将请求参数绑定到域对象的工作。
Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,
MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText
和 POI。
IOC
IOC优点
轻量级(引入一个jar即可)
非侵入式(不影响项目原有代码)
高低原则:高内聚 低耦合
开闭原则:对扩展开放 对修改关闭
提供了辅助类:JdbcTemplate(是手写JdbcUtil的官方替代者) StringUtils CollectionUtils StreamUtils
提供了事务管理服务 消息服务
提供了单例模式
提供了AOP
可以和其他框架很好的集成
主要解释:降低组件或对象之间的耦合度
不用我们自己手动创建需要的对象,由IOC容器来负责创建bean
伴随着工业级应用的规模越来越庞大,对象之间的依赖关系也越来越复杂,经常会出现对象之间的多重依赖性关系,因此,架构师和设计师对于系统的分析和设计,将面临更大的挑战。对象之间耦合度过高的系统,必然会出现牵一发而动全身的情形。
耦合关系不仅会出现在对象与对象之间,也会出现在软件系统的各模块之间,以及软件系统和硬件系统之间。如何降低系统之间、模块之间和对象之间的耦合度,是软件工程永远追求的目标之一。为了解决对象之间的耦合度过高的问题,软件专家Michael Mattson 1996年提出了IOC理论,用来实现对象之间的“解耦”,
简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。
全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。
由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
通过前后的对比发现:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。
反转了什么呢,“获得依赖对象的过程被反转了”
DI
所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
IOC缺点
第一、软件系统中由于引入了第三方IOC容器,生成对象的步骤变得有些复杂,本来是两者之间的事情,又凭空多出一道手续,所以,我们在刚开始使用IOC框架的时候,会感觉系统变得不太直观。所以,引入了一个全新的框架,就会增加团队成员学习和认识的培训成本,并且在以后的运行维护中,还得让新加入者具备同样的知识体系。
第二、由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运行效率的话,就必须对此进行权衡。
第三、具体到IOC框架产品(比如:Spring)来讲,需要进行大量的配制工作,比较繁琐,对于一些小的项目而言,客观上也可能加大一些工作成本。
第四、IOC框架产品本身的成熟度需要进行评估,如果引入一个不成熟的IOC框架产品,那么会影响到整个项目,所以这也是一个隐性的风险。
我们大体可以得出这样的结论:一些工作量不大的项目或者产品,不太适合使用IOC框架产品。另外,如果团队成员的知识能力欠缺,对于IOC框架产品缺乏深入的理解,也不要贸然引入。最后,特别强调运行效率的项目或者产品,也不太适合引入IOC框架产品,像WEB2.0网站就是这种情况。
HelloWorld
1,pom
<!-- spring-webmvc依赖其他四个,所以引入这一个也可以 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
2,实体类
public class User {
private String name;
public String getName() {
return name; }
public void setName( String name) {
this.name = name; }
public void show(){
System.out.println("Hello"+name); }
}
3, applicationContext.xml(位置不固定)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.baidu.cpt.User">
<property name="name" value="tom"/>
<!-- <property name="name">
<value>alice</value>
</property> 这种写法也可以,但不简洁 -->
</bean>
</beans>
4,Test.java
public class Test {
public static void main(String[] args) {
//HelloSpring helloSpring=new HelloSpring(); 这是原始写法来调show方法
//helloSpring.show();
//获取IoC容器,读取配置文件,初始化Spring上下文
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//根据id获取容器中的bean
User user = (User) ac.getBean("user");
user.show();
//也可以这样获取,此时不需要强转类型但有时候多个bean是同一个类型,这时候就需要bean属于的类型是唯一的,不然最好使用id创建对象
//User p = ac.getBean(User.class);
//p.show();
}
}
IOC获取容器方式
1,FileSystemXmlApplicationContext
这个方法是从系统文件绝对路径加载配置文件,例如:
ApplicationContext ctx = new FileSystemXmlApplicationContext( "G:/Test/applicationcontext.xml");
如果在参数中写的不是绝对路径,那么方法调用的时候也会默认用绝对路径来找,我测试的时候发现默认的绝对路径是eclipse所在的路径。
采用绝对路径的话,程序的灵活性就很差了,所以这个方法一般不推荐。
(如果要使用classpath路径,需要加入前缀classpath: )
2,ClassPathXmlApplicationContext (常用)
这个方法是从classpath下加载配置文件(适合于相对路径方式加载),例如:
ApplicationContext ctx = new ClassPathXmlApplicationContext( "classpath:applicationcontext.xml");
该方法参数中classpath: 前缀是不需要的,默认就是指项目的classpath路径下面;这也就是说用ClassPathXmlApplicationContext时,
默认的根目录是在WEB-INF/classes下面,而不是项目根目录。这个需要注意!
3,BeanFactory(已过时)
Resource resource = new ClassPathResource("ioc03/spring.xml");
BeanFactory bf = new XmlBeanFactory(resource);
User u = (User)bf.getBean("User");
System.out.println(u);
4,XmlWebApplicationContext
专为web工程定制的方法,推荐Web项目中使用。例如:
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
5、ApplicationContext (常用)
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
IOC创建对象方式
(获取容器时,bean对象就被创建了)
无参构造方法来创建
public User() {
System.out.println("user无参构造方法");
}
<bean id="user" class="com.kuang.pojo.User">
<property name="name" value="kuangshen"/>
</bean>
有参构造方法来创建
public UserT(String name) {
this.name = name;
}
<!-- 第一种根据index参数下标设置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
<!-- index指构造方法 , 下标从0开始 -->
<constructor-arg index="0" value="kuangshen2"/>
</bean>
<!-- 第二种根据参数名字设置 (推荐)-->
<bean id="userT" class="com.kuang.pojo.UserT">
<!-- name指参数名 -->
<constructor-arg name="name" value="kuangshen2"/>
</bean>
<!-- 第三种根据参数类型设置(如果多个参数是String会出错,所以不建议) -->
<bean id="userT" class="com.kuang.pojo.UserT">
<constructor-arg type="java.lang.String" value="kuangshen2"/>
</bean>
静态工厂
public class SpringBeanFactory {
// 使用此种方法必须有静态工厂类
public static SpringBean getSpringBean(){
System.out.println("静态工厂 无参 。。。。。。。。。。");
return new SpringBean();
}
public static SpringBean getSpringBean(String name){
System.out.println("静态工厂 有参。。。。。。。。。。");
SpringBean springBean = new SpringBean();
springBean.setName(name);
return springBean;
}
}
<bean id="springBean2" class="ioc10.SpringBeanFactory" factory-method="getSpringBean">
<constructor-arg name="name" value="lucy"/>
</bean>
实例工厂
public class SpringBeanFactory {
public SpringBean getSpringBean(){
System.out.println("实例工厂 无参-------------------------------");
return new SpringBean();
}
public SpringBean getSpringBean(String name){
System.out.println("实例工厂 有参-------------------------------");
SpringBean springBean = new SpringBean();
springBean.setName(name);
return springBean;
}
}
<bean id="springBean2" factory-bean="springBeanFactory" factory-method="getSpringBean">
<constructor-arg name="name" value="alice"/>
</bean>
alias
<!--设置别名:在获取Bean的时候可以使用别名获取,alias标签与bean同级 -->
<alias name="user" alias="user1 "/>
补充一些细节
1、在spring容器管理的Bean中,必须仅且只有一个无重复的ID。
一般情况下,配置一个bean时,需要指定它的ID属性作为bean的名称。
<bean id="helloWorld" class="com.learnSpring.hellWorld"/>
2、如果在spring环境下,如果出现多个配置ID属性值一样的bean
如果spring是默认设定,即可以覆盖bean定义,则根据spring配置文件加载的顺序,
后面同名的bean会覆盖掉前面定义的bean配置,spring不会报错
如果设置不可以覆盖bean定义,则出现多个同ID的bean,则会抛出异常,停止运行
--匿名bean
如果在配置bean的时候并没有声明ID属性,则采用全类限定名作为bean的ID。
<bean class="com.learnSpring.hellWorld"/>
如果存在多个class属性都是一样的匿名的Bean,则生成的ID根据spring读取配置文件的顺序生成ID
"com.learnSpring.hellWorld"
"com.learnSpring.hellWorld#0"
"com.learnSpring.hellWorld#1"
--name属性
如果一个bean只配置了name属性,但是没有配置ID属性,默认会ID属性=name属性
name定义的是bean的alias,可以有多个,并可能与其他的bean重名。
<bean name="hello" class="com.learnSpring.hellWorld"/>
<bean id="hello" class="com.learnSpring.hellWorld"/>
对于上面的两个bean配置,最后会出现两个ID属性都是“hello”的bean,最后的bean会覆盖前面的bean
id name 区别
id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
如果配置id,又配置了name,那么name是别名
name可以设置多个别名,可以用逗号,分号,空格隔开
如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
<bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello">
<property name="name" value="Spring"/>
</bean>
bean继承
在bean标签中设置 parent="user",会继承除了父配置中的属性(除了id,abstract,autowire等),
当然也可以重写属性,进行值再次赋值
如果该bean没有class属性,就必须指定abstrct属性为"true"
如果某个bean是abstrct="true"表示是抽象类,不能被实例化,只能被继承
<!-- 用法1 -->
<bean id="parent" abstract="true">
<property name="password" value="123"/>
</bean>
<bean id="springBean" class="ioc16.SpringBean" parent="parent">
<property name="name" value="admin"/>
<property name="age" value="18"/>
</bean>
<bean id="otherBean" class="ioc16.OtherBean" parent="parent">
<property name="name" value="alice"/>
<property name="sex" value="female"/>
</bean>
<!-- 用法2 -->
<bean id="parent2" class="ioc16.SpringBean" abstract="true">
<property name="password" value="000"/>
</bean>
<bean id="bean1" parent="parent2">
<property name="name" value="zhangsan"/>
<property name="age" value="20"/>
</bean>
<bean id="bean2" parent="parent2">
<property name="name" value="lisi"/>
<property name="age" value="18"/>
</bean>
import
团队多人开发,需要将各自的xml汇总
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="beans.xml"/>
<!-- <import resource="{path}/beans.xml"/> -->
<!--其中bean标签相同也会合并 -->
</beans>
依赖关系depends-on
通过depends-on设置Bean依赖的Bean,依赖的Bean会在本Bean实例化前创建好;多个Bean,通过空格隔开
<bean id="helloSet1" class="hello.Hello" p:name="name" p:numberInt="12" p:numberDouble="3.15">
</bean>
<bean id="helloSet2" class="hello.Hello" p:name="name" p:numberInt="12" p:numberDouble="3.15">
</bean>
<bean id="helloSet3" parent="helloSet1" depends-on="helloSet1 helloSet2">
</bean>
DI注入各种数据类型
public class User {
private Dept dept;
private String name;
private Integer[] arrays;
private List<Dept> lists;
private Set<Dept> sets;
private Map<Dept, Class> map;
private Properties properties;
}
注入方式1:构造器
<bean id="userT" class="com.kuang.pojo.UserT">
<!-- 这里创建对象 和 注入属性值 一次完成 -->
<constructor-arg name="name" value="kuangshen2"/>
</bean>
注入方式2:set注入 (重点)
要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 ,
如果属性是boolean类型, 没有set方法 , 是 is
简单类型 八种基本类型及包装类,String Class Resource
构造方式注入必须保证该类有无参构造,这是一个底层约定。
引用类型使用ref,指向另一个bean标签的id
<bean id="otherBean" class="ioc05.OtherBean">
<property name="otherBean" ref="otherBean"/>
<property name="name" value="jack"/>
</bean>
??<bean id="a1" class="ioc.A">
<constructor-arg value="小明"/>直接赋值,如果有特殊符号需要转义
<constructor-arg index="0" value="小明"/>index属性: 指定参数下标
<constructor-arg type="double" value="小明"/>type属性: 指定参数类型
<constructor-arg type="java.lang.String"> 用value子标签也可以,和value元素有区别
<value><!CDATA[<shanghai>]></value> //含有特殊符号需要转义,或者“<”用<value>子标签<!CDATA[]>来写
</constructor-arg>
<constructor-arg><null/></constructor-arg> 也可赋值为null
<constructor-arg ref="car"/>
</bean>
array
<property name="arrays">
<arrays> //这里也可以写list
<value>1</value>
<value>2</value>
<value>5</value>
</arrays>
</property>
list 元素可重复
<property name="lists">
<list>
<ref bean="otherBean"/>
<bean class="ioc05.OtherBean">
<!-- 不ref其他bean,直接自己定义bean,若是String则使用value -->
<property name="name" value="zhangsan"/>
</bean>
</list>
</property>
**set **会过滤掉重复元素
<property name="sets">
<set>
<ref bean="otherBean"/>
<!-- 不ref其他bean,直接自己定义bean,若是String则使用value -->
<bean class="ioc05.OtherBean">
<property name="name" value="zhangsan"/>
</bean>
</set>
</property>
map
<property name="map">
<map>
<!-- key value-ref value-type -->
<entry key-ref="otherBean" value="java.lang.Integer"/>
</map>
</property>
properties
<property name="properties">
<props>
<prop key="key1">value1</prop>
<prop key="key2">value2</prop>
</props>
</property>
注入Null
<property name="name"><null/></property>
注入方式3: C P 命名空间
P命名空间注入: 需要在头文件中假如约束文件
导入约束 : xmlns:p="http://www.springframework.org/schema/p"
<!--P(属性: property)命名空间 , 属性依然要设置setter方法 -->
<bean id="user" class="com.kuang.pojo.User" p:name="张三" p:age="18" p:user_dept-ref="dept1"/>
c命名空间注入: 需要在头文件中假如约束文件
导入约束 : xmlns:c="http://www.springframework.org/schema/c"
<!--C(构造: Constructor)命名空间 , 实体类必须有构造器 -->
<bean id="user" class="com.kuang.pojo.User" c:name="张三" c:age="18"/>
补充一个特殊写法:内部bean
当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean,为了定义inner bean,在Spring 的 基于XML的 配置元数据中,
可以在 <property/>或 <constructor-arg/> 元素内使用<bean/> 元素,内部bean通常是匿名的,当然外部也无法使用,
它们的Scope一般是prototype。
<bean id="person2" class="com.itdjx.spring.dependency.injection.Person">
<property name="name" value="李玉"/>
<property name="age" value="23"/>
<property name="sex" value="女"/>
<property name="car" >
<bean class="com.itdjx.spring.dependency.injection.Car">
<constructor-arg value="Ferrari" index="0"/>
<constructor-arg value="Italy" index="1"/>
<constructor-arg value="22500000" type="double"/>
</bean>
</property>
</bean>
还有一种util命名空间方式来注入,自行百度。
bean作用域scope
在ioc容器中bean容器默认是单例模式,
假设:线程1将name=zhangsan,线程2赋值为lisi,最终线程1使用时name就变成lisi
这样是不安全的,有其他模式可供选择
Singleton
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对
bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是
在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象
都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成
singleton,可以这样配置:
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
Prototype
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会
导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)
时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是
当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经
验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在
XML中将bean定义成prototype,可以这样配置:
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>
或者
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
Request
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP
请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring
ApplicationContext情形下有效。考虑下面bean定义:
<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实
例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例
的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的
状态变化。当处理请求结束,request作用域的bean实例将被销毁。
Session
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域
仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的
userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作
用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据
userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session
最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。
自动装配bean
xml方式
<!-- autowire,取值:
default,不进行自动装配,等同于no
byName,根据引用属性的名字自动装配,在本xml中查找与setter方法名同名的bean标签,存在的话进行装配
1, 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
2. 去spring容器中寻找是否有此字符串名称id的对象。
3. 如果有,就取出注入;如果没有,就报空指针异常
byType,根据属性类型自动装配,在本xml中查找同类型的bean标签(推荐)
如果刚好找到一个,则注入
如果找到多个,则抛出异常
需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
constructor,根据构造方法形参,在本xml中查找与setter方法名同名的bean标签,存在的话进行装配
实际上是根据byName和byType自动装配,先按byName,再按byType
注:此时不是通过setter方法进行装配的,所以可以不写对应的setter方法
-->
<bean id="springBean" class="ioc17.SpringBean" autowire="constructor">
<!--<property name="otherBean" ref="otherBean"/>-->
</bean>
<bean id="otherBean" class="ioc17.OtherBean">
<property name="name" value="tom"/>
</bean>
<bean id="ob" class="ioc17.OtherBean">
<property name="name" value="alice"/>
</bean>
注解方式
在spring配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
开启属性注解支持!
<context:annotation-config/>
//@Autowired是按类型自动转配的,不支持id匹配,不需要setter
//(required = false)不是必须的,也就是可以为null
public class User {
@Autowired(required = false)
private Cat cat;
}
歧义性 某接口有多个实现类时,利用@Autowired注入不知道选择哪一个实现类
方案1:@Qualifier
@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
@Qualifier不能单独使用
<bean id="dog1" class="com.kuang.pojo.Dog"/>
<bean id="dog2" class="com.kuang.pojo.Dog"/>
@Autowired
@Qualifier(value="cat2")
private Cat cat;
@Component("cat2") //bean类当然要有对应id
方案2:@Primary
@Component
@Primary //设置一个主键注解
public class User {
//...
}
@Autowired 与 @Resource 异同:
1. @Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
2. @Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果
要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我
们想使用名称装配可以结合@Qualifier注解进行使用
3. @Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果
没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在
setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。但是
需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。
注解
在spring4之后,想要使用注解形式,必须得要引入aop的包
<!--指定注解扫描包,可以配置多个-->
<context:component-scan base-package="com.kuang.pojo"/>
@Component("user")//这里自定义id为abc,默认就是类名首字母小写
// 相当于配置文件中 <bean id="user" class="com.kuang.pojo.User"/>
public class User {
@Value("James")
public String name;
}
@Controller:web层
@Service:service层
@Repository:dao层
@Controller("user")
@Scope("prototype")
//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class User {
@Value("张三")
public String name;
}
// 这个也会Spring容器托管,注册到容器中,因为他本来就是一个@Component
// @Configuration代表这是一个配置类,就和我们之前看的beans.xml
@Configuration//相当于一个applicationContext.xml
@ComponentScan("com.kuang.pojo")//如果ComponentScan没有配置value,默认扫描与其配置类相同的包。
//也可以指定多个@ComponentScan(basePackages={"com.kuang.pojo","com.kuang.entity"})
//假如包名修改,这里代码就需要需要,所以不建议这么写死
//还可以直接指定到类:同样不建议这么做
//可以过滤,细节以后研究,过于细节的东西可能用不到,没必要花费时间
@Import(KuangConfig2.class)//将多个配置类汇总为一个
public class KuangConfig {
//注册一个bean , 就相当于我们之前写的一个bean标签
//这个方法的名字,就相当于bean标签中的id属性
//这个方法的返回值,就相当于bean标签中的class属性
@Bean
public User user(){
return new User(); //就是返回要注入到bean的对象!
}
}
//指定多个ComponentScan
1、在jdk8以后可以直接写多个ComponentScan
@ComponentScan(basePackages = "com.yefengyu.annotation1")
@ComponentScan(basePackages = "com.yefengyu.annotation2")
2、也可以使用ComponentScans注解
@ComponentScans(value = {
@ComponentScan(basePackages = "com.yefengyu.annotation1"),
@ComponentScan(basePackages = "com.yefengyu.annotation2")
})
Test.java
获取容器用另一种方式:
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
延迟加载
也叫(按需加载)(懒加载)(实例化动机)
lazy-init 如果为true,表示启动时,不实例化bean,默认false,会一开始就实例化
<bean id="User" class="com.baidu.bean.User" lazy-init="true"/>
@Lazy注解作用于类上时,通常与@Component及其衍生注解配合使用。
@Lazy注解作用于方法上时,通常与@Bean注解配合使用
ps: 使用BeanFactory只能懒实例化,使用时才会实例化
生命周期
代码块 - 实例化 - 数据装配 - init-method - 就绪状态 - 使用 - destroy-method - 销毁状态
<!-- 指定方法,当然这两个方法要存在, chushi() xioahui() -->
<bean id="springBean" class="ioc07.SpringBean" init-method="chushi" destroy-method="xioahui">
<property name="name" value="alice"/>
<property name="sex" value="female"/>
</bean>
context.destory();//强制销毁,暴力方式
context.close();//建议使用
@Bean(initMethod = "init",destroyMethod = "destroy")
或者
@Component
public class Tiger {
public Tiger(){
System.out.println("tiger...constructor....");
}
@PostConstruct
public void init(){
System.out.println("tiger....@postConstruct...");
}
@PreDestroy
public void destroy(){
System.out.println("tiger....@PreDestroy.....");
}
}
FactoryBean
Spring中有两种bean:普通bean、工厂bean(FactoryBean,用来生成指定的bean)
如果普通bean的配置比较麻烦,比如获取一个jdbc连接,就可以使用FactoryBean
准备bean类:实现FactoryBean接口、重写方法
xml中配置bean。返回的是FactoryBean中的getObject返回的对象
public class PreparedStatementFactoryBean implements FactoryBean<PreparedStatement> {
//生成实例的过程
@Override
public PreparedStatement getObject() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/cpt", "root", "123456");
PreparedStatement ps = conn.prepareStatement("select * from t_user");
return ps;
}
//生成实例的类型
@Override
public Class<?> getObjectType() {
return null;
}
//需要生成实例为单例则true 多例则false
@Override
public boolean isSingleton() {
return false;
}
}
AOP
理解
核心思想:动态的添加和删除切面上的逻辑而不影响原来的执行代码
1.x 写法过时(不推荐)。纯xml配置且繁琐,此处总结的是2.x写法。
2.x基于命名空间的配置,原理是使用后处理器,更简单
提供声明式事务;允许用户自定义切面
横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要
关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
● Aspect 切面:横切关注点 被模块化 的特殊对象。即,它是一个类。
切入业务流程的一个独立模块。例如,前面案例的VerifyUser类,一个应用程序可以拥有任意数量的切面。
● Join point 连接点:与切入点匹配的执行点。
表示连接点。也就是业务流程在运行过程中需要插入切面的具体位置。例如,前面案例的AopEmailNotice类的setTeacher方法就是一个连接点。
● Advice 通知:切面必须要完成的工作。即,它是类中的一个方法。
是切面的具体实现方法。可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)和环绕通知(Around)五种。实现方法具体属于哪类通知,是在配置文件和注解中指定的。例如,VerifyUser类的beforeAdvice方法就是前置通知。
● Pointcut 切入点:切面通知 执行的 “地点”的定义。
用于定义通知应该切入到哪些连接点上,不同的通知通常需要切入到不同的连接点上。例如,前面案例配置文件的<aop:pointcut>
标签。
● Target 目标:被通知对象。
被一个或者多个切面所通知的对象。例如,前面案例的AopEmailNotice类。
● Proxy 代理:向目标对象应用通知之后创建的对象。
将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象为目标对象的业务逻辑功能加上被切入的切面所形成的对象。
● Weaving
表示切入,也称为织入。将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期。
Aop在Spring中的作用:
1、日志记录,跟踪,优化和监控
2、事务的处理
3、持久化
4、性能的优化
5、资源池,如数据库连接池的管理
6、系统统一的认证、权限管理等
7、应用系统的异常捕捉及处理
8、针对具体行业应用的横切行为
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
方式1:通过 Spring API 实现
public interface UserService {
public void add();
public void delete();
public void update();
public void search();
}
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加用户");
}
@Override
public void delete() {
System.out.println("删除用户");
}
@Override
public void update() {
System.out.println("更新用户");
}
@Override
public void search() {
System.out.println("查询用户");
}
}
public class Log implements MethodBeforeAdvice {
//method : 要执行的目标对象的方法
//objects : 被调用的方法的参数
//Object : 目标对象
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}
public class AfterLog implements AfterReturningAdvice {
//returnValue 返回值
//method被调用的方法
//args 被调用的方法的对象的参数
//target 被调用的目标对象
@Override
public void afterReturning(Object returnValue, Method method, Object[]
args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName() +"的"+method.getName()+"方法," + "返回值:" + returnValue);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userService" class="com.kuang.service.UserServiceImpl"/>
<bean id="log" class="com.kuang.log.Log"/>
<bean id="afterLog" class="com.kuang.log.AfterLog"/>
<!--aop的配置-->
<aop:config>
<!--切入点 expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
<!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
public class MyTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService)context.getBean("userService");
userService.search();
}
}
方式2: 自定义类来实现Aop
目标业务类不变依旧是userServiceImpl
public class DiyPointcut {
//切入类
public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}
}
<!--注册bean-->
<bean id="diy" class="com.kuang.config.DiyPointcut"/>
<!--aop的配置-->
<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="diy">
<aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
<aop:before pointcut-ref="diyPonitcut" method="before"/>
<aop:after pointcut-ref="diyPonitcut" method="after"/>
</aop:aspect>
</aop:config>
方式3:AOP注解
<aop:aspectj-autoproxy proxy-target-class="true"/>
false使用默认的jdk动态代理
true使用cglib
将切面类增加注解
@Component
@Aspect //表示这是一个切面
public class LogAdvice {
//定义切点表达式(有了表达式@Before等注解)
@Pointcut("execution(* aop.service.impl.*ServiceImpl.*(..))")
public void pc() {
}
@Pointcut("execution(* aop.service.impl.*.*(..))")
public void pc2() {
// 切入点可以有多个
}
@Before("pc()") //引用切点表达式,后面的小括号不能省略,不然程序以为是定义一个切入点
public void before(JoinPoint joinPoint) {
System.out.println("LogAdvice,name:" + joinPoint.getSignature().getName() + ",args:"
+ joinPoint.getArgs() + ",target:" + joinPoint.getThis());
}
@AfterReturning(value = "pc()", returning = "returnValue") //有多个属性就不能简写为"pc()",必须写"value=""
public void afterReturning(JoinPoint joinPoint, Object returnValue) {
System.out.println("LogAdvice.afterReturning,returnValue:" + returnValue);
}
@AfterThrowing(value = "pc2()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
System.out.println("LogAdvice.afterThrowing,e:" + e);
}
@Around("pc()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("开启事务。。。。。");
Object proceed = null;
try {
proceed = joinPoint.proceed();
System.out.println("提交事务。。。。");
} catch (Throwable e) {
System.out.println("回滚事务。。。。");
throw e;
}
return proceed;
}
}
声明式事务
编程式事务管理
将事务管理代码嵌到业务方法中来控制事务的提交和回滚(每个方法手写回滚逻辑)
缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
声明式事务管理
一般情况下比编程式事务好用。
将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理
转账,一方要加法,一方要减法,要么全成功,要么全失败。
使用Spring管理事务,注意头文件的约束导入 : tx
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="get" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置aop织入事务-->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.kuang.dao.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
spring事务传播特性:
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:
- propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这
个事务中,这是最常见的选择。 - propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
- propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与
propagation_required类似的操作