Spring
1. Spring简介
-
spring的模块划分
- Test:spring的单元测试模块
- Core Container:核心容器(IOC)黑色代表这部分的功能由哪些jar包组成,要使用这个部分的完整功能,就不要导入这些jar包
- AOP + Aspects:面向切面编程模块
- Data Access:访问数据库的模块
- Web:web应用模块
-
spring的优势
- 降低了 2EE 的使用难度,并且方便集成各种框架
- 推荐及大量使用面向对象的设计思想,是学习 Java 源码的经典框架
- 面向接口编程,而不是面向类编程,不断地利用 ava 的多态特性及良好的面向对 象设计思想,来降低程序的复杂度及搞合度
- 提供了测试框架,并且支持集成其 测试框架,使测试更容易,对测试程序的编 更简单、高效
-
Spring的优良特性
[1]非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
[2]依赖注入:DI——Dependency Injection,反转控制(IOC)最经典的实现。
[3]面向切面编程:Aspect Oriented Programming——AOP
[4]容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期
[5]组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
[6]一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的Spring JDBC)
2. IOC 控制反转
IOC和bean的配置
/*
1. IOC(Inversion of Control): 反转控制
1. 在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下 开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率
2. 反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资 源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的 降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式
2. DI(Dependency Injection): 依赖注入
IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入,相对 于IOC而言,这种表述更直接
3. IOC容器在Spring中的实现
1. 在通过IOC容器读取Bean的实例之前,需要先将IOC容器本身实例化
2. Spring提供了IOC容器的两种实现方式
1. BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给 开发人员使用的
2. ApplicationContext:BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几 乎所有场合都使用ApplicationContext而不是底层的BeanFactory
4. ApplicationContext的主要实现类
1. ClassPathXmlApplicationContext:对应类路径下的XML格式的配置文件
2. FileSystemXmlApplicationContext:对应文件系统中的XML格式的配置文件
3. 在初始化时就创建单例的bean,也可以通过配置的方式指定创建的Bean是多实例的
5. ConfigurableApplicationContext
1. 是ApplicationContext的子接口,包含一些扩展方法
2. refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力
6. WebApplicationContext
专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作
7. 通过类型获取bean
从IOC容器中获取bean时,除了通过id值获取,还可以通过bean的类型获取。但如果同一个类型的bean在 XML文件中配置了多个,则获取时会抛出异常,所以同一个类型的bean在容器中必须是唯一的
8. 给bean的属性赋值
1. 通过bean的set方法赋值
2. 通过bean的构造器赋值
3. 通过索引值指定参数位置
4. 通过类型不同区分重载的构造器
9. 集合属性: 在Spring中可以通过一组内置的XML标签来配置集合属性,例如:<list>,<set>或<map>
1. 数组和List: 配置java.util.List类型的属性,需要指定<list>标签,在标签里包含一些元素。这些标签 可以通过<value>指定简单的常量值,通过<ref>指定对其他Bean的引用,通过<bean>指定内置bean定义,通 过<null/>指定空元素。甚至可以内嵌其他集合
2. 数组的定义和List一样,都使用<list>元素,配置java.util.Set需要使用<set>标签,定义的方法与 List一样
3. Map: Java.util.Map通过<map>标签定义,<map>标签里可以使用多个<entry>作为子标签。每个条目包 含一个键和一个值,必须在<key>标签里定义键,因为键和值的类型没有限制,所以可以自由地为它们指定 <value>、<ref>、<bean>或<null/>元素,可以将Map的键和值作为<entry>的属性定义:简单常量使用key 和value来定义;bean引用通过key-ref和value-ref属性定义
4. Properties: 使用<props>定义java.util.Properties,该标签使用多个<prop>作为子标签。每个 <prop>标签必须定义key属性
5. 集合类型的bean: 如果只能将集合对象配置在某个bean内部,则这个集合的配置将不能重用。我们需要将集 合bean的配置拿到外面,供其他bean引用。配置集合类型的bean需要引入util名称空间
*/
/*
10. 通过工厂创建bean
1. 静态工厂
调用静态工厂方法创建bean是将对象创建的过程封装到静态方法中。当客户端需要对象时,只需要简单地调 用静态方法,而不用关心创建对象的细节。声明通过静态方法创建的bean需要在bean的class属性里指定静 态工厂类的全类名,同时在factory-method属性里指定工厂方法的名称。最后使用<constrctor-arg> 元素为该方法传递方法参数
2. 实例工厂
实例工厂方法:将对象的创建过程封装到另外一个对象实例的方法里。当客户端需要请求对象时,只需要简单 的调用该实例方法而不需要关心对象的创建细节
3. 实现方式
1. 配置工厂类实例的bean
2. 在factory-method属性里指定该工厂方法的名称
3. 使用 construtor-arg 元素为工厂方法传递方法参数
4. FactoryBean
Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean
工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象
工厂bean必须实现org.springframework.beans.factory.FactoryBean接口
*/
/*
bean的高级配值
1. 配置信息的继承
1. Spring允许继承bean的配置,被继承的bean称为父bean。继承这个父bean的bean称为子bean
2. 子bean从父bean中继承配置,包括bean的属性配置
3. 子bean也可以覆盖从父bean继承过来的配置
4. 补充说明: 父bean可以作为配置模板,也可以作为bean实例。若只想把父bean作为模板,可以设置<bean> 的abstract 属性为true,这样Spring将不会实例化这个bean如果一个bean的class属性没有指定,则必须 是抽象bean并不是<bean>元素里的所有属性都会被继承。比如:autowire,abstract等。也可以忽略父bean 的class属性,让子bean指定自己的类,而共享相同的属性配置。但此时abstract必须设为true
2. bean之间的依赖
有的时候创建一个bean的时候需要保证另外一个bean也被创建,这时我们称前面的bean对后面的bean有依赖。 例如:要求创建Employee对象的时候必须创建Department。这里需要注意的是依赖关系不等于引用关系, Employee即使依赖Department也可以不引用它
3. bean的作用域
1. 在Spring中,可以在<bean>元素的scope属性里设置bean的作用域,以决定这个bean是单实例的还是多实 例的。默认情况下,Spring只为每个在IOC容器里声明的bean创建唯一一个实例,整个IOC容器范围内都能共享 该实例:所有后续的getBean()调用和bean引用都将返回这个唯一的bean实例。该作用域被称为singleton, 它是所有bean的默认作用域
2. 注意: 当bean的作用域为单例时,Spring会在IOC容器对象创建时就创建bean的对象实例。而当bean的作 用域为prototype时,IOC容器在获取bean的实例时创建bean的实例对象
*/
/*
4. bean的生命周期
1. Spring IOC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务
2. Spring IOC容器对bean的生命周期进行管理的过程:
1. 通过构造器或工厂方法创建bean实例
2. 为bean的属性设置值和对其他bean的引用
3. 调用bean的初始化方法
4. bean可以使用了
5. 当容器关闭时,调用bean的销毁方法
3. 在配置bean时,通过init-method和destroy-method 属性为bean指定初始化和销毁方法
5. bean的后置处理器
1. bean的后置处理器允许在调用初始化方法前后对bean进行额外的处理
2. bean后置处理器对IOC容器里的所有bean实例逐一处理,而非单一实例。其典型应用是:检查bean属性的正 确性或根据特定的标准更改bean的属性
3. bean后置处理器时需要实现接口: org.springframework.beans.factory.config.BeanPostProcessor。在初始化方法被调用前 后,Spring将把每个bean实例分别传递给上述接口的以下两个方法:
postProcessBeforeInitialization(Object, String)
postProcessAfterInitialization(Object, String)
4. 添加bean后置处理器后bean的生命周期
1. 通过构造器或工厂方法创建bean实例
2. 为bean的属性设置值和对其他bean的引用
3. 将bean实例传递给bean后置处理器的postProcessBeforeInitialization()方法
4. 调用bean的初始化方法
5. 将bean实例传递给bean后置处理器的postProcessAfterInitialization()方法
6. bean可以使用了
7. 当容器关闭时调用bean的销毁方法
6. 引用外部属性文件
当bean的配置信息逐渐增多时,查找和修改一些bean的配置信息就变得愈加困难。这时可以将一部分信息提取到 bean配置文件的外部,以properties格式的属性文件保存起来,同时在bean的配置文件中引用properties属 性文件中的内容,从而实现一部分属性值在发生变化时仅修改properties属性文件即可。这种技术多用于连接数 据库的基本信息的配置
7. 自动装配
1. 自动装配的概念
1. 手动装配:以value或ref的方式明确指定属性值都是手动装配
2. 自动装配:根据指定的装配规则,不需要明确指定,Spring自动将匹配的属性值注入bean中
2. 装配模式
1. 根据类型自动装配:将类型匹配的bean作为属性注入到另一个bean中。若IOC容器中有多个与目标bean 类型一致的bean,Spring将无法判定哪个bean最合适该属性,所以不能执行自动装配
2. 根据名称自动装配:必须将目标bean的名称和属性名设置的完全相同
3. 通过构造器自动装配:当bean中存在多个构造器时,此种自动装配方式将会很复杂。不推荐使用
3. 选用建议
相对于使用注解的方式实现的自动装配,在XML文档中进行的自动装配略显笨拙,在项目中更多的使用注解的 式实现
*/
实验1:通过IOC容器创建对象,并为属性赋值
-
导入依赖
<dependencies> <!--spring的配置包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.8.RELEASE</version> </dependency> <!--spring的日志依赖包--> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> </dependency> <!--spring的单元测试--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.8.RELEASE</version> </dependency> </dependencies>
-
写配置文件
<?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"> <!--注册一个Person对象,spring就会自动创建这个Person对象--> <!--一个bean标签可以注册一个组件(类),必须写全类名--> <!--id是这个组件的唯一标识--> <bean id="person" class="com.siki.bean.Person"> <!--使用property标签为Person对象的属性赋值--> <property name="name" value="昕昕" /> <property name="password" value="1314520"/> <property name="loving" value="辣条"/> </bean> </beans>
-
测试
//person类 public class Person { private String name; private String password; private String loving; public Person(){ System.out.println("person对象创建了..."); } public Person(String name, String password, String loving) { this.name = name; this.password = password; this.loving = loving; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getLoving() { return loving; } public void setLoving(String loving) { this.loving = loving; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", password='" + password + '\'' + ", loving='" + loving + '\'' + '}'; } } //测试 @Test public void test(){ //ApplicationContext: 代表容器 //ClassPathXmlApplicationContext: 当前spring的应用配置文件在类路径下 //参数: 根据spring的配置文件得到IOC容器 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //取出容器中的组件 Object person1 = context.getBean("person"); Object person2 = context.getBean("person"); System.out.println(person1 == person2); //true System.out.println(context.getBean("person1")); //没有这个组件 } /* 几个细节 1. person对象是在容器创建完成的时候进行初始化的 2. 同一个组件在IOC容器中是单实例的,而且在容器创建完成之前就已经创建好了 3. 容器中如果没有这个组件,获取组件时会报异常 org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'person1' available 4. IOC容器在创建这个组件的时候,property属性会利用set和get方法为person对象的属性进行赋值 5. JavaBean的属性名是由get和set方法决定的,所有的get和set方法千万不要乱改 */
实验2:根据bean的类型从IOC容器中获取bean的实例
-
配置文件
<bean id="person" class="com.siki.bean.Person"> <!--使用property标签为Person对象的属性赋值--> <property name="name" value="昕昕" /> <property name="password" value="1314520"/> <property name="loving" value="辣条"/> </bean> <bean id="person1" class="com.siki.bean.Person"> <property name="name" value="xinxin"/> </bean>
-
测试
@Test public void test02(){ //如果IOC容器中这个类型的bean有多个,查找就会报错 //org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.siki.bean.Person' available: expected single matching bean but found 2: person,person1 Person person = context.getBean(Person.class); System.out.println(person); //解决 Person person1 = context.getBean("person1", Person.class); System.out.println(person1); }
实验3:通过构造器为bean的属性赋值
-
配置文件
<bean id="person2" class="com.siki.bean.Person"> <!--调用有参构造器创建对象并完成赋值--> <constructor-arg name="name" value="李一桐"></constructor-arg> <constructor-arg name="password" value="1234"></constructor-arg> <constructor-arg name="loving" value="演员"></constructor-arg> </bean> <bean id="person3" class="com.siki.bean.Person"> <!--可以省略name属性,但是必须严格按照构造器参数的位置进行赋值--> <constructor-arg value="李一桐"></constructor-arg> <constructor-arg value="1234"></constructor-arg> <!--如果构造器有重载的话,可以通过type指定参数的类型--> <constructor-arg value="演员" index="2" type="java.lang.String"></constructor-arg> </bean> <!-- 通过p名称空间为bean赋值 1. 导入p名称空间 xmlns:p="http://www.springframework.org/schema/p" 2. 给属性赋值时: p:属性名=值 --> <bean id="person4" class="com.siki.bean.Person" p:name="昕昕" p:password="520" p:loving="薯片"></bean>
-
测试
@Test public void test03(){ Object person2 = context.getBean("person2"); System.out.println(person2); Object person3 = context.getBean("person3"); System.out.println(person3); Object person4 = context.getBean("person4"); System.out.println(person4); }
实验4:正确的为各种属性赋值
1. 测试null值
-
配置文件
<!--1. 测试null值--> <bean id="person01" class="com.siki.bean.Person"></bean>
//2. 测试
public class Person {
//基本数据类型直接使用<property>属性
private String name;
private String password;
private String loving;
//引用数据类型
private Car car;
private List<Book> books;
private Map<String,Object> map;
private Properties properties;
public Person(){
}
public Person(String name, String password, String loving) {
this.name = name;
this.password = password;
this.loving = loving;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getLoving() {
return loving;
}
public void setLoving(String loving) {
this.loving = loving;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
public List<Book> getBooks() {
return books;
}
public void setBooks(List<Book> books) {
this.books = books;
}
public Map<String, Object> getMap() {
return map;
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
", loving='" + loving + '\'' +
", car=" + car +
", books=" + books +
", map=" + map +
", properties=" + properties +
'}';
}
}
//1. 测试null值,引用类型默认为null
@Test
public void test01(){
Person person01 = (Person) context.getBean("person01");
System.out.println(person01);
}
2. ref引用外部的值
-
配置文件
<!--2.1 引用类型赋值(引用其他bean)--> <bean id="car01" class="com.siki.bean.Car"> <property name="name" value="宝马"></property> <property name="price" value="10000"></property> <property name="color" value="红色"></property> </bean> <bean id="person02" class="com.siki.bean.Person"> <!--ref: 代表引用外面的值--> <property name="car" ref="car01"></property> </bean> <!--2.2 引用内部bean(注意: 内部bean的id是不能被获取到的,只能在内部使用)--> <bean id="person03" class="com.siki.bean.Person"> <property name="car"> <!--我们可以使用bean标签创建--> <bean id="car02" class="com.siki.bean.Car"> <property name="name" value="宝马"></property> <property name="price" value="10000"></property> <property name="color" value="红色"></property> </bean> </property> </bean>
-
测试
@Test public void test02(){ //引用外部bean Person person02 = (Person) context.getBean("person02"); System.out.println(person02.getCar()); //引用内部bean Person person03 = (Person) context.getBean("person03"); System.out.println(person03.getCar()); }
3. 集合类型赋值
<!--3. 集合属性赋值-->
<bean id="book01" class="com.siki.bean.Book">
<property name="name" value="三国演义"/>
<property name="price" value="20"/>
</bean>
<bean id="person04" class="com.siki.bean.Person">
<property name="books">
<!--为list属性赋值 books = new ArrayList<>()-->
<list>
<bean id="book02" class="com.siki.bean.Book">
<property name="name" value="西游记" />
<property name="price" value="10"/>
</bean>
<ref bean="book01"/>
</list>
</property>
<property name="map">
<!--为mao属性赋值 map = new HashMap<>()-->
<map>
<!--一个entry代表一个键值对-->
<entry key="key01" value="xinxin"></entry>
<entry key="key02" value="520"></entry>
<entry key="key03" value-ref="book01"></entry> <!--外部引用-->
<entry key="key04">
<bean class="com.siki.bean.Car">
<property name="name" value="奥迪"/>
<property name="price" value="100000"/>
</bean>
</entry>
</map>
</property>
<property name="properties">
<!--为properties属性赋值 properties = new Properties() 所有的key-value都是String类型-->
<props>
<!--key和value都是直接写在标签体中-->
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
@Test
public void test03(){
//list属性赋值
Person person04 = (Person) context.getBean("person04");
System.out.println(person04.getBooks());
//map属性赋值
Map<String, Object> map = person04.getMap();
System.out.println(map);
//properties属性赋值
Properties properties = person04.getProperties();
System.out.println(properties);
}
4.util名称空间创建集合类型的bean
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<!--导入util名称空间-->
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd">
<!--4. util名称空间创建集合类型的bean,方便别人引用-->
<!--相当于new LinkedHashMap<>()-->
<util:map id="map">
<entry key="key01" value="xinxin"></entry>
<entry key="key02" value="520"></entry>
<entry key="key03" value-ref="book01"></entry>
<entry key="key04">
<bean class="com.siki.bean.Car">
<property name="name" value="奥迪"/>
<property name="price" value="100000"/>
</bean>
</entry>
</util:map>
<bean id="person05" class="com.siki.bean.Person">
<property name="map" ref="map"></property>
</bean>
@Test
public void test04(){
Person person05 = (Person) context.getBean("person05");
System.out.println(person05.getMap());
}
5. 级联属性赋值
<!--5. 级联属性(属性的属性)赋值 级联属性可以修改属性的属性,但是原来的bean中的值可能也会被修改-->
<bean id="person06" class="com.siki.bean.Person">
<!--为car赋值的时候,修改car的price-->
<property name="car" ref="car01"/>
<property name="car.price" value="20000"/>
</bean>
public void test05(){
Person person06 = (Person) context.getBean("person06");
Car car = (Car) context.getBean("car01");
System.out.println(car);
System.out.println(person06.getCar());
}
实验5:配值通过静态工厂方法创建的bean、实例工厂方法创建的bean、FactoryBean
<!--5. 配值通过静态工厂方法创建的bean、实例工厂方法创建的bean、FactoryBean
1. bean的创建默认就是框架利用反射new出来的bean实例
2. 工厂模式: 工厂帮我们创建对象,一个专门帮我们创建对象的类
1. 静态工厂: 工厂本身不需要创建对象,通过调用静态方法创建bean实例
2. 实例工厂: 工厂本身需要创建对象,先创建工厂对象,再创建bean实例
-->
<!--1. 静态工厂 factory-method: 指定哪个方法是工厂方法-->
<bean id="book01" class="com.siki.factory.StaticFactory" factory-method="getBook">
<!--为方法指定参数-->
<constructor-arg name="name" value="岛上书店"></constructor-arg>
</bean>
<!--2. 实例工厂 -->
<bean id="instanceFactory" class="com.siki.factory.InstanceFactory"></bean>
<!--factory-bean: 指定当前对象创建使用哪个工厂-->
<!--factory-method: 指定工厂方法-->
<bean id="book02" class="com.siki.bean.Book" factory-bean="instanceFactory" factory-method="getBook">
<constructor-arg name="name" value="追风筝的人"></constructor-arg>
</bean>
<!--3. FactoryBean: 是spring规定的一个接口,只要是这个接口的实现类,spring都认为是一个工厂
1. 先编写一个FactoryBean的实现类
2. 在spring配置文件中注册
3. IOC容器启动的时候不会创建实例,获取的时候才会创建
-->
<bean id="myFactoryBean" class="com.siki.factory.MyFactoryBean"></bean>
//实例工厂
public class InstanceFactory {
public Book getBook(String name){
System.out.println("InstanceFactory...");
Book book = new Book();
book.setName(name);
book.setPrice(30);
return book;
}
}
//静态工厂
public class StaticFactory {
public static Book getBook(String name){
System.out.println("StaticFactory...");
Book book = new Book();
book.setName(name);
book.setPrice(10);
return book;
}
}
//实现了FactoryBean接口的类都是spring认识的工厂类,spring会自动调用工厂方法创建实例
public class MyFactoryBean implements FactoryBean<Book> {
//工厂方法
@Override
public Book getObject() throws Exception {
Book book = new Book();
book.setName("墨菲定律");
return book;
}
//返回创建对象的类型,spring会自动创建这个方法来确定对象是什么类型的
@Override
public Class<?> getObjectType() {
return Book.class;
}
//是否是单例模式
@Override
public boolean isSingleton() {
return false;
}
}
@Test
public void test02(){
System.out.println(context.getBean("book01"));
System.out.println(context.getBean("book02"));
System.out.println(context.getBean("myFactoryBean"));
}
实验6:通过继承实现bean配值信息的重用
<!--6. 通过继承实现bean配置信息的重用-->
<bean id="person07" class="com.siki.bean.Person">
<property name="name" value="昕昕"/>
<property name="password" value="1314520"/>
<property name="loving" value="xinxin"/>
</bean>
<!--通过parent属性指定当前bean的配值继承哪个bean-->
<bean id="person08" class="com.siki.bean.Person" parent="person07">
<property name="name" value="宝贝"/>
</bean>
@Test
public void test06(){
Person person08 = (Person) context.getBean("person08");
System.out.println(person08);
}
实验7:通过abstract属性创建一个模板bean
<!--abstract="true" 表示这个bean的配值是抽象的,不能获取它的实例,只能被别人用来继承-->
<bean id="person07" class="com.siki.bean.Person" abstract="true">
<property name="name" value="昕昕"/>
<property name="password" value="1314520"/>
<property name="loving" value="xinxin"/>
</bean>
实验8:bean之间的依赖
<!--9. bean之间的依赖(只是改变bean的创建顺序)-->
<!--一般情况下,按照bean的配值顺序创建bean-->
<!--但是我们可以改变bean的创建顺序,depends-on: 表示先创建book,再创建person,最后创建car-->
<bean id="car" class="com.siki.bean.Car" depends-on="book,person"></bean>
<bean id="person" class="com.siki.bean.Person"></bean>
<bean id="book" class="com.siki.bean.Book"></bean>
实验9:测试bean的作用域,分别创建单实例、多实例的bean
<!--10. 测试bean的作用域,分别创建单实例和多实例的bean
默认情况下是单实例
1. prototype: 多实例
1. 容器启动默认不会创建多实例bean
2. 获取的时候才会创建bean实例
3. 每次获取都会创建一个新的实例
2. singleton: 单实例
1. 在容器启动完成之前就已经创建好对象了,保存在容器中
2. 任何时候获取都是获取到的之前创建好的那个对象
3. request: web环境下使用,同一次请求创建一个bean实例(基本上没用)
4. session: web环境下使用,同一次会话创建一个bean实例(基本上没用)
-->
<bean id="book" class="com.siki.bean.Book" scope="prototype"></bean>
实验10:创建带有生命周期方法的bean
<!--10. 创建带有生命周期的bean
1. IOC容器中创建的bean:
1. 单实例bean: 容器启动的时候就会创建,容器关闭的时候也会销毁bean
2. 多实例bean: 获取的时候创建,也会销毁
2. 我们可以为bean自定义一些生命周期的方法,spring在创建或者销毁bean的时候就会调用这些指定的方法,完成指定的操作
3. 自定义初始化和销毁方法: 可以抛异常,但是不能有参数
-->
<bean id="book01" class="com.siki.bean.Book" init-method="init" destroy-method="destory"></bean>
//初始化方法
public void init(){
System.out.println("init...");
}
//销毁方法
public void destory(){
System.out.println("destory...");
}
/*
单实例bean的生命周期
(容器启动)构造器--->初始化init()--->销毁destory()
多实例bean的生命周期
获取bean(构造器--->初始化方法)--->容器关闭不会bean的销毁方法
*/
@Test
public void test01(){
//关闭容器 注意: 只有ApplicationContext的子类才有close()方法
context.close(); //容器关闭的时候会调用destory方法
}
实验11:测试bean的后置处理器
<!--11. 测试bean的后置处理器: spring的一个接口,可以在bean的初始化前后调用指定方法
1. 编写bean的后置处理器的实现类
2. 在配置文件中注册
3. 注意: 无论bean是否有初始化方法,后置处理器都会工作
-->
<bean id="myBeanPostProcessor" class="com.siki.processor.MyBeanPostProcessor"></bean>
//bean的后置处理器
public class MyBeanPostProcessor implements BeanPostProcessor {
//初始化之前调用
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println(bean + "-->" + beanName + " will postProcessBeforeInitialization...");
//返回传入的bean,这是初始化之后返回的bean,返回的是什么,容器中保存的就是什么
return bean;
}
//初始化之后调用
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println(bean + "-->" + beanName + " will postProcessAfterInitialization...");
return bean;
}
}
实验12:引用外部属性文件
<!--导入数据库连接池-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!--db.properties-->
jdbc.username=root
jdbc.password=123456
jdbc.jdbcUrl=jdbc:mysql://localhost:3306
jdbc.driverClass=com.mysql.jdbc.Driver
<!--2. 通过外部属性文件db.properties进行赋值-->
<!--加载外部属性文件 classpath: 表示引用类路径下的资源-->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--因为username是spring中key的一个关键字,为了防止配置文件中的key和spring中的key命名一样,所以改为jdbc.username-->
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
<property name="driverClass" value="${jdbc.driverClass}"/>
</bean>
<!--我们可以看看spring中的关键字username是什么-->
<bean id="book01" class="com.siki.bean.Book">
<property name="name" value="${username}"/>
</bean>
@Test
public void test01() throws SQLException {
//从容器中拿到连接池
DataSource dataSource = (DataSource) context.getBean("dataSource");
System.out.println(dataSource.getConnection());
//查看spring中的username关键字
System.out.println(context.getBean("book01"));
}
实验13:基于xml的自动装配
<!--13. 基于xml的自动装配(自动赋值) 注意: 只有自定义类型才可以自动赋值,基本数据类型没有-->
<bean id="car" class="com.siki.bean.Car">
<property name="name" value="宝马"/>
<property name="price" value="120000"/>
<property name="color" value="蓝色"/>
</bean>
<!--1. 没有赋值-->
<bean id="person01" class="com.siki.bean.Person"></bean>
<!--2. 为person里面的自定义属性手动赋值-->
<bean id="person02" class="com.siki.bean.Person">
<!--property: 手动赋值-->
<property name="car" ref="car"/>
</bean>
<!--3. 自动装配
1. default/no: 不自动装配
2. byName: 按照名字自动装配,以属性名(car)作为id去容器中找这个组件,进行赋值,如果找不到就装 配null
car = context.getBean("car");
3. byType: 按照类型自动装配,以属性的类型(Car)作为id去容器中找这个组件,如果容器中有多个这种 类型的组件,就会报错,如果没找到,也会装配null
car = context.getBean(Car.Class);
4. constructor: 按照构造器进行赋值
1. 先按照有参构造器的参数的类型进行装配,如果找到了就赋值,如果没有找到就直接赋值null
2. 如果按照类型找到了多个,就会将参数名作为id继续进行装配,找到了就装配,找不到就赋值null
3. 按照构造器赋值,要么装配成功,要么赋值null,不会报错
-->
<bean id="person03" class="com.siki.bean.Person" autowire="byName"></bean>
//可以为car赋值的有参构造函数
public Person(Car car){
this.car = car;
}
@Test
public void test02() {
//没有赋值
System.out.println(context.getBean("person01"));
//手动赋值
System.out.println(context.getBean("person02"));
//自动装配
System.out.println(context.getBean("person03"));
}
实验14:SpEL测试
<!--14. SpEL测试(spring表达式语言 Spring Expression Language)
1. 在SpEL中使用字面量
2. 引用其他bean
3. 引用其他bean的某个属性值
4. 调用非静态方法
5. 调用静态方法
6. 使用运算符
-->
<bean id="person04" class="com.siki.bean.Person">
<!--字面量-->
<!-- <property name="password" value="#{13*14}"/>-->
<!--引用其他bean的某个属性值-->
<property name="name" value="#{car.name}"/>
<property name="car" value="#{car}"/>
<!--调用静态方法 #{T(全类名).静态方法名(参数列表)}-->
<property name="loving" value="#{T(java.util.UUID).randomUUID().toString()}"/>
<!--调用非静态方法 对象.方法名-->
<property name="password" value="#{car.getPrice()}"/>
</bean>
实验15:通过注解分别创建Dao、Service、Controller
<!--15. 通过注解分别创建Dao、Service、Controller
1. 通过给bean添加某些注解,可以快速的将bean加入到容器中
1. @Controller: 控制器
2. @Service: 业务逻辑
3. @Repository: dao层
4. @Component: 给不属于以上几层的组件添加这个注解
2. 注解可以随便加,spring底层不会去验证这个注解,推荐各自层添加各自注解,注解是给程序员看的
3. 组件的id就是默认类名首字母小写
4. 一定要导入aop包,支持注解模式
5. 使用注解添加到容器中的组件,和使用配置文件加入到容器中的组件行为都是默认一样的
1. 组件的id,默认都是类名首字母小写
2. 组件的作用域,默认都是单实例
-->
<!--告诉spring,自动扫描添加了注解的组件
1. context:component-scan: 自动逐渐扫描
2. base-package: 指定扫描的基础包,把基础包及下面所有的子包中所有添加注解了的组件,全部扫描进IOC容器中
-->
<context:component-scan base-package="com.siki"/>
@Controller
public class BookServlet {
}
@Service
public class BookService {
}
@Repository("book")
@Scope(value = "prototype") //指定为多实例
public class BookDao {
}
@Test
public void test01(){
Object bookDao1 = context.getBean("book");
Object bookDao2 = context.getBean("book");
System.out.println(bookDao1 == bookDao2);
}
实验16:使用context:include-filter指定扫描包时要包含的类
<!--16. 使用context:include-filter指定扫描包时要包含的类,只扫描哪些组件-->
<!--use-default-filters: 一定要禁用掉默认的过滤规则,默认全部扫描-->
<context:component-scan base-package="com.siki" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
实验17:使用context:exclude-filter指定扫描包时不包含的类
<!--17. 使用context:exclude-filter指定扫描包时不包含的类,扫描的时候可以排除一些不要的组件-->
<context:component-scan base-package="com.siki">
<!--type="annotation": 指定排除规则,按照注解进行排除-->
<!--type="assignable": 指定排除某个具体的类,按照类进行排除-->
<!--type="custom": 自定义一个TypeFilter,自己写代码决定是否排除(了解即可,用不到)-->
<!--type="regex": 正则表达式(了解即可,用不到)-->
<!--expression: 注解的全类名-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:exclude-filter type="assignable" expression="com.siki.service.BookService"/>
</context:component-scan>
实验18:使用@Autowired注解实现根据类型实现自动装配
<!--18. 使用@Autowired注解实现根据类型实现自动装配-->
<context:component-scan base-package="com.siki"/>
@Controller
public class BookServlet {
//spring会自动的为这个属性赋值,去容器中找到这个属性对应的组件
@Autowired
private BookService service;
public void sava(){
service.sava();
}
}
@Service
public class BookService {
@Autowired
private BookDao dao;
public void sava(){
dao.saveBook();
}
}
@Repository
public class BookDao {
public void saveBook(){
System.out.println("saveBook...");
}
}
@Test
public void test02(){
BookServlet servlet = (BookServlet) context.getBean("bookServlet");
servlet.sava();
}
实验19:默认根据@Autowired注解标记的成员变量名作为id查找bean
<!--18. 默认根据@Autowired注解标记的成员变量名作为id查找bean
@Autowired原理:
1. 先按照类型去容器中找对应的组件
1. 如果找到一个,直接进行装配
2. 如果没有找到,就会报错
3. 如果找到多个,按照变量名作为默认id继续进行查找,找到就装配
-->
实验20:@Qualifier注解
<!--20. @Qualifier注解
根据@Autowired进行查找,如果找到多个,有两种可能
1. 匹配上
进行装配
2. 没有匹配上
报错
解决办法:
@Qualifier: 指定一个名称作为id,让spring别使用变量名作为id
注意: @Autowired标注的自动装配的属性默认一定是会装配上的,如果找不到立马报错
-->
@Service
public class BookService {
@Qualifier("dao")
@Autowired
private BookDao daoExt;
public void sava(){
daoExt.saveBook();
}
}
实验21:在方法上位置使用@Qualifier注解
/*
方法上写@Autowired
1. 这个方法也会在bean创建的时候自动运行
2. 这个方法的每一个参数都会自动装配
*/
@Autowired
public void hello(BookDao bookDao){
System.out.println("---->" + bookDao);
}
实验22:@Autowired注解的required属性指定某个属性允许不被赋值
@Service
public class BookService {
@Qualifier("dao")
@Autowired(required = false) //表示找不到赋值为null,不是一定要装配成功
private BookDao daoExt;
public void sava(){
//daoExt.saveBook();
}
}
@Resource注解和@Autowired注解的区别
@Controller
public class BookServlet {
/*
区别:
1. @Autowired: 最强大、spring自己的注解
2. @Resource: Java自己的注解,JDK的标准
3. @Resource扩展性很强,如果切换成另外一个框架,还是可以使用的,但是@Autowired就不行了
*/
//@Autowired
@Resource
private BookService service;
public void sava(){
service.sava();
}
}
Spring的单元测试
/*
1. 导包
2. @ContextConfiguration: 指定配置文件的路径
3. @RunWith: 指定使用哪种驱动进行单元测试,默认是junit
4. 好处: 我们不需要在使用context.getBean()获取组件了,直接使用@Autowired进行自动装配即可
*/
@ContextConfiguration(locations = "classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringTest {
@Autowired
private BookDao dao;
@Test
public void test(){
System.out.println(dao);
}
}
实验23:测试泛型依赖注入
<!--23. 测试泛型依赖注入-->
<context:component-scan base-package="com.siki"/>
//原始方式
public class Book {
}
public class User {
}
public abstract class BaseDao<T> {
public abstract void save();
}
@Repository
public class BookDao extends BaseDao<Book>{
@Override
public void save() {
System.out.println("bookDao...");
}
}
@Repository
public class UserDao extends BaseDao<User>{
@Override
public void save() {
System.out.println("userDao...");
}
}
@Service
public class BookService {
@Autowired
private BookDao dao;
public void save(){
dao.save();
}
}
@Service
public class UserService {
@Autowired
private UserDao dao;
public void save(){
dao.save();
}
}
@ContextConfiguration(locations = "classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringTest {
@Autowired
private BookService bookService;
@Autowired
private UserService userService;
@Test
public void test(){
bookService.save();
userService.save();
}
}
//泛型依赖注入
public abstract class BaseDao<T> {
public abstract void save();
}
@Repository
public class BookDao extends BaseDao<Book>{
@Override
public void save() {
System.out.println("bookDao...");
}
}
@Repository
public class UserDao extends BaseDao<User>{
@Override
public void save() {
System.out.println("userDao...");
}
}
//这里不需要添加@Service注解,因为只要子类继承该类,就可以实现dao的自动装配
public class BaseService<T> {
@Autowired
private BaseDao<T> dao;
public void save(){
System.out.println(dao);
dao.save();
}
}
//子类只需要继承父类即可
@Service
public class BookService extends BaseService<Book>{
}
@Service
public class UserService extends BaseService<User> {
}
@ContextConfiguration(locations = "classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringTest {
@Autowired
private BookService bookService;
@Autowired
private UserService userService;
@Test
public void test(){
bookService.save();
userService.save();
//带泛型的父类类型: com.siki.service.BaseService<com.siki.bean.Book>
//spring中可以使用带泛型的父类类型来确定子类的类型
System.out.println(bookService.getClass().getGenericSuperclass());
}
}
3. AOP 面向切面编程
1. AOP概述
-
AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充
-
AOP编程操作的主要对象是切面(aspect),而切面模块化横切关注点
-
在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的类里——这样的类我们通常称之为“切面”
-
AOP的好处:
- 每个事物逻辑位于一个位置,代码不分散,便于维护和升级
- 业务模块更简洁,只包含核心业务代码
-
AOP术语
- 横切关注点:从每个方法中抽取出来的同一类非核心业务
- 切面(Aspect):封装横切关注点信息的类,每个关注点体现为一个通知方法
- 通知(Advice):切面必须要完成的各个具体工作
- 目标(Target):被通知的对象
- 代理(Proxy):向目标对象应用通知之后创建的代理对象
- 连接点(Joinpoint):横切关注点在程序代码中的具体体现, 对应程序执行的某个特定位置, 例如:类某个方法调用前、调用后、方法捕获到异常后等, 在应用程序中可以使用横纵两个坐标来定位一个具体的连接点
- 切入点(pointcut):定位连接点的方式。每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物。如果把连接点看作数据库中的记录,那么切入点就是查询条件——AOP可以通过切入点定位到特定的连接点。切点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件
-
面向切面编程:指在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行的编程方式
2. 计算机执行加减乘除计算
-
动态代理
使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上
/*
要求: 核心功能(+、-、*、/) + 日志模块,在核心功能运行期间,日志可以自己动态的加上
1. 动态代理很强大,日志记录可以做的很好,而且与业务逻辑解耦
2. 缺点
1. 代码写起来太难
2. JDK默认的动态代理,如果目标对象没有实现任何接口,是无法为它创建代理对象的
3. 代理对象和被代理对象唯一能产生的关联就是实现了同一个接口
4. spring实现了AOP功能,底层就是动态代理,可以利用spring一句代码都不写就创建动态代理,实现简单,而且,没有强制要求目标对象必须实现接口
*/
public interface Calculator {
public int add(int i,int j);
public int sub(int i,int j);
public int mul(int i,int j);
public int div(int i,int j);
}
public class MyCalculator implements Calculator {
@Override
public int add(int i, int j) {
return i + j;
}
@Override
public int sub(int i, int j) {
return i - j;
}
@Override
public int mul(int i, int j) {
return i * j;
}
@Override
public int div(int i, int j) {
return i / j;
}
}
//Calculator代理对象
public class CalculatorProxy {
//为传入的参数对象创建一个代理对象
public static Calculator getProxy(Calculator calculator){
ClassLoader loader = calculator.getClass().getClassLoader(); //类加载器
Class<?>[] interfaces = calculator.getClass().getInterfaces(); //实现的接口
Object proxy = Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
//proxy: 代理对象,给JDK使用的,任何时候都不要动这个对象
//method: 当前将要执行的目标对象的方法
//args:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//利用反射执行目标方法
System.out.println(method.getName() + " --> " + Arrays.asList(args));
Object invoke = method.invoke(calculator, args);
//返回值必须返回出去,外界才能拿到正在执行后的返回值
return invoke;
}
});
return (Calculator) proxy;
}
}
@Test
public void test(){
Calculator calculator = new MyCalculator();
Calculator proxy = CalculatorProxy.getProxy(calculator);
System.out.println(proxy.add(1, 2));
}
3. AOP测试
-
AOP简单配置
-
1 AspectJ:Java社区里最完整最流行的AOP框架
在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP
-
在Spring中启用AspectJ注解支持
-
导入jar包
-
引入aop名称空间
-
配置aop:aspectj-autoproxy
当Spring IOC容器侦测到bean配置文件中的aop:aspectj-autoproxy元素时,会自动为与AspectJ切面匹配的bean创建代理
-
-
用AspectJ注解声明切面
- 要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为bean实例
- 当在Spring IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那些与 AspectJ切面相匹配的bean创建代理
- 在AspectJ注解中,切面只是一个带有@Aspect注解的Java类,它往往要包含很多通知
- 通知是标注有某种注解的简单的Java方法
- AspectJ支持5种类型的通知注解:
- @Before:前置通知,在方法执行之前执行
- @After:后置通知,在方法执行之后执行
- @AfterRunning:返回通知,在方法返回结果之后执行
- @AfterThrowing:异常通知,在方法抛出异常之后执行
- @Around:环绕通知,围绕着方法执行
-
导包
<!--AOP加强版jar包--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.1</version> </dependency>
-
写配置文件
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" <!--导入aop名称空间--> 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/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--1. 将目标类和切面类(封装了通知方法的类)加入到容器中--> <!--2. 还应该告诉spring到底哪个类是切面类(@Aspect注解标注的类)--> <!--3. 告诉spring,切面类里面的方法,什么时候执行--> <context:component-scan base-package="com.siki"/> <!--4. 开启基于注解的aop模式--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
-
方式一:采用接口
public interface Calculator { public int add(int i,int j); public int sub(int i,int j); public int mul(int i,int j); public int div(int i,int j); } @Service public class MyCalculator implements Calculator { @Override public int add(int i, int j) { LogUtils.logStart(); return i + j; } @Override public int sub(int i, int j) { LogUtils.logStart(); return i - j; } @Override public int mul(int i, int j) { LogUtils.logStart(); return i * j; } @Override public int div(int i, int j) { LogUtils.logStart(); return i / j; } } @Aspect //表示这是一个切面类 @Component public class LogUtils { //在目标方法执行之前运行 @Before("execution(public int com.siki.impl.MyCalculator.*(int,int))") //切入点表达式 public static void logStart(){ System.out.println("logAdd --> xxx"); } //在目标方法正常执行完成之后运行 @AfterReturning("execution(public int com.siki.impl.MyCalculator.*(int,int))") public static void logReturn(){ System.out.println("logSub --> xxx"); } //在目标方法出现异常的时候运行 @AfterThrowing("execution(public int com.siki.impl.MyCalculator.*(int,int))") public static void logException(){ System.out.println("logMul --> xxx"); } //在目标方法结束的时候运行 @After("execution(public int com.siki.impl.MyCalculator.*(int,int))") public static void logEnd(){ System.out.println("logDiv --> xxx"); } } @Test public void test02(){ //从容器中拿到目标对象 //注意: 如果想要用类型获取,一定要用接口类型,不要用本类 Calculator bean = context.getBean(Calculator.class); System.out.println(bean.add(1, 2)); //细节1: AOP底层用的就是动态代理,所以容器中保存的组件是它的代理对象,也就是接口类型 System.out.println(bean); //com.siki.impl.MyCalculator@399c4be1 System.out.println(bean.getClass()); //class com.sun.proxy.$Proxy17 }
-
方式二:不要接口
@Service public class MyCalculator { public int add(int i, int j) { LogUtils.logStart(); return i + j; } public int sub(int i, int j) { LogUtils.logStart(); return i - j; } public int mul(int i, int j) { LogUtils.logStart(); return i * j; } public int div(int i, int j) { LogUtils.logStart(); return i / j; } } //去掉接口 @Test public void test03(){ //没有接口,就是本类型 //细节2: cglib可以为没有接口的组件创建代理对象 MyCalculator bean = context.getBean(MyCalculator.class); System.out.println(bean.add(1, 2)); //cglib帮我们创建好的代理对象 System.out.println(bean.getClass());//class com.siki.impl.MyCalculator$$EnhancerBySpringCGLIB$$2763047e }
-
切入点表达式的作用:通过表达式的方式定位一个或多个具体的连接点
-
切入点表达式的语法格式:execution([权限修饰符] [返回值类型] [简单类名/全类名]方法名(参数列表)
- 例如:execution(public int com.siki.impl.MyCalculator.*(…))
- *:通配符,可以匹配一个或多个字符(权限位置不能写,可以不写,表示任意权限)
- … :匹配任意多个参数,任意类型参数(还可以表示匹配任意多层路径)
- 在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来
- 例如:execution (* .add(int,…)) || execution( *.sub(int,…))
@Aspect //表示这是一个切面类 @Component public class LogUtils { /* 细节4: 我们可以在通知方法运行的时候,拿到目标方法的详细信息 JoinPoint point 封装了当前目标方法的详细信息 切入点表达式通常都会是从宏观上定位一组方法,和具体某个通知的注解结合起来就能够确定对应的连接点 那么就一个具体的连接点而言,我们可能会关心这个连接点的一些具体信息 例如:当前连接点所在方法的方法名、当前传入的参数值等等。这些信息都封装在JoinPoint接口的实例对象中 1. 只需要为通知方法的参数列表上写一个参数 2. 获取方法返回值时,告诉spring,使用哪个变量获取返回值 3. 获取异常信息时,告诉spring,使用哪个变量获取异常信息 细节5: spring对通知方法的要求一点都不严格,唯一的要求就是参数列表一定要正确,不能乱写 因为通知方法是spring利用反射调用的,每次方法调用都得确定这个方法的参数列表的值 spring必须明确知道每一个参数 细节6: 抽取可重用的切入点表达式 1. 随便声明一个没有实现的返回void的空方法 2. 给方法上标注@Pointcut注解 */ @Pointcut("execution(public int com.siki.impl.MyCalculator.*(int,int))") public void helloPointcut(){ } //在目标方法执行之前运行 @Before("helloPointcut()") //切入点表达式 public static void logStart(JoinPoint point){ System.out.println(point.getSignature().getName() + " --> " + Arrays.asList(point.getArgs())); } //在目标方法正常执行完成之后运行(returning: 用来接收返回值) @AfterReturning(value = "helloPointcut()",returning = "result") public static void logReturn(JoinPoint point,Object result){ System.out.println(point.getSignature().getName() + " --> xxx"); } //在目标方法出现异常的时候运行(throwing: 用来接收异常信息) @AfterThrowing(value = "helloPointcut()",throwing = "e") public static void logException(JoinPoint point,Exception e){ System.out.println(point.getSignature().getName() + " --> " + e.getMessage()); } //在目标方法结束的时候运行 @After("helloPointcut()") public static void logEnd(JoinPoint point){ System.out.println(point.getSignature().getName() + " --> xxx"); } /* 细节8: 环绕通知: 最强大的通知 1. 环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。 2. 对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint 它是JoinPoint的子接口,允许控制何时执行,是否执行连接点 3. 在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。 如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。 注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用joinPoint.proceed()的返回值,否则会出现空指针异常 执行顺序: 环绕通知优先执行 环绕前置-->普通前置-->目标方法执行-->环绕正常返回/出现异常-->环绕后置-->普通后置-->普通返回/出现异常 */ @Around("helloPointcut()") public Object myAround(ProceedingJoinPoint point){ Object[] args = point.getArgs(); Object proceed = null; try { //其实就是利用反射调用目标方法,相当于动态代理的method.invoke(obj,args) System.out.println("before"); //前置通知 proceed = point.proceed(args); System.out.println("afterReturning"); } catch (Throwable throwable) { //注意: 如果使用try..catch捕捉异常,一旦环绕通知捕捉到了,那么异常通知将捕捉不到该异常 //解决办法: 可以将异常抛出 System.out.println("afterThrowing " + point.getSignature().getName()); }finally { System.out.println("after"); } //反射调用后的值也要返回出去 return proceed; } } @Test public void test04(){ /* 细节3: 通知方法的执行顺序 1. 正常执行 前置通知-->后置通知-->返回通知(正常返回才会有) 2. 异常执行 前置通知-->后置通知-->异常通知(返回时出现异常才会有) */ MyCalculator bean = context.getBean(MyCalculator.class); bean.mul(1,2); System.out.println("--------------------"); //bean.div(1,0); }
//多个切面执行顺序 @Aspect @Component @Order(1) //@Order可以改变切面的执行顺序,数值越小,优先级越高 public class ValidateUtils { @Before("com.siki.utils.LogUtils.helloPointcut()") public static void logStart(JoinPoint point){ System.out.println("Validate" + point.getSignature().getName() + " --> " + Arrays.asList(point.getArgs())); } @AfterReturning(value = "com.siki.utils.LogUtils.helloPointcut()",returning = "result") public static void logReturn(JoinPoint point,Object result){ System.out.println("Validate " + point.getSignature().getName() + " --> yyy"); } @AfterThrowing(value = "com.siki.utils.LogUtils.helloPointcut()",throwing = "e") public static void logException(JoinPoint point,Exception e){ System.out.println("Validate" + point.getSignature().getName() + " --> " + e.getMessage()); } @After("com.siki.utils.LogUtils.helloPointcut()") public static void logEnd(JoinPoint point){ System.out.println("Validate " + point.getSignature().getName() + " --> yyy"); } } @Test public void test05(){ MyCalculator bean = context.getBean(MyCalculator.class); bean.add(1,1); } /* 运行结果: Logadd --> [1, 1] Validateadd --> [1, 1] Validateadd --> yyy Validateadd --> yyy Logadd --> xxx Logadd --> xxx */
-
4. AOP应用
-
AOP使用场景
- 日志,保存到数据库中
- 权限验证
- 安全检查
- 事务控制
-
基于配置的AOP
<!-- 基于注解的aop的步骤 1. 将目标类和切面类都加入到IOC容器中 @Component 2. 告诉spring哪个是切面类 @Aspect 3. 在切面类中使用五个通知注解来配置切面中的这些通知方法什么时候执行 4. 开启基于注解的AOP功能 注意: 注解: 快速方便 配置: 功能完善,重要的用配置,不重要的用注解 --> <!--基于配置的AOP--> <!--1. 将目标类和切面类都加入到IOC容器中--> <bean id="myCalculator" class="com.siki.impl.MyCalculator"></bean> <bean id="validateUtils" class="com.siki.utils.ValidateUtils"></bean> <bean id="logUtils" class="com.siki.utils.LogUtils"></bean> <!--2. 告诉spring哪个是切面类--> <aop:config> <!--pointcut: 指定切入点表达式--> <aop:pointcut id="myPoint" expression="execution(* com.siki.impl.*.*(..))"/> <!--指定切面--> <aop:aspect ref="logUtils"> <!--3. 在切面类中使用五个通知注解来配置切面中的这些通知方法什么时候执行--> <!--method: 指定目标方法--> <aop:before method="logStart" pointcut="execution(* com.siki.impl.*.*(..))"/> <aop:after-returning method="logReturn" pointcut-ref="myPoint" returning="result"/> <aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="e"/> <aop:after method="logEnd" pointcut-ref="myPoint"/> <aop:around method="myAround" pointcut-ref="myPoint"/> </aop:aspect> <aop:aspect ref="validateUtils" order="1"> <aop:before method="logStart" pointcut-ref="myPoint"/> <aop:after-returning method="logReturn" pointcut-ref="myPoint" returning="result"/> <aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="e"/> <aop:after method="logEnd" pointcut-ref="myPoint"/> </aop:aspect> </aop:config>
4. 声明式事务
1. 事务
- 在JavaEE企业级开发的应用领域,为了保证数据的完整性和一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术
- 事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要么都不执行
- 事务的四个关键属性(ACID)
- 原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行
- 一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚
- 隔离性((isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰
- 持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中
2. Spring事务管理
-
编程式事务管理
-
使用原生的JDBC API进行事务管理
- 获取数据库连接Connection对象
- 取消事务的自动提交
- 执行操作
- 正常完成操作时手动提交事务
- 执行失败时回滚事务
- 关闭相关资源
-
评价
使用原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余
-
-
声明式事务管理
- 大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理
- 事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理
- Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制
- Spring既支持编程式事务管理,也支持声明式的事务管理
-
Spring提供的事务管理器
- Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的
- Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的
- 事务管理器可以以普通的bean的形式声明在Spring IOC容器中
-
事务管理器的主要实现
- DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取
- JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理
- HibernateTransactionManager:用Hibernate框架存取数据库
-
事务的传播行为
-
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行
-
事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为
-
-
事务的隔离级别
-
数据库事务并发问题:假设现在有两个事务Transaction01和Transaction02并发执行
- 脏读
- Transaction01将某条记录的AGE值从20修改为30
- Transaction02读取了Transaction01更新后的值:30
- Transaction01回滚,AGE值恢复到了20
- Transaction02读取到的30就是一个无效的值
- 不可重复读
- Transaction01读取了AGE值为20
- Transaction02将AGE值修改为30
- Transaction01再次读取AGE值为30,和第一次读取不一致
- 幻读
- Transaction01读取了STUDENT表中的一部分数据
- Transaction02向STUDENT表中插入了新的行
- Transaction01读取了STUDENT表时,多出了一些行
- 脏读
-
隔离级别:数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱
- 读未提交(READ UNCOMMITTED):允许Transaction01读取Transaction02未提交的修改
- 读已提交(READ COMMITTED):要求Transaction01只能读取Transaction02已提交的修改
- 可重复读(REPEATABLE READ):确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新
- 串行化(SERIALIZABLE):确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下
-
各个隔离级别解决并发问题的能力
-
在Spring中指定事务隔离级别
- 用@Transactional注解声明式地管理事务时可以在@Transactional的isolation属性中设置隔离级别
- 在Spring 2.x事务通知中,可以在tx:method元素中指定隔离级别
-
3. 事务测试
- 未使用事务
<!--事务(注意: 使用事务时,aop的所有jar包也要导入)-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!--db.properties-->
jdbc.username=root
jdbc.password=123456
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/tx
jdbc.driverClass=com.mysql.jdbc.Driver
<!--1. 包扫描-->
<context:component-scan base-package="com.siki"/>
<!--2. 配置数据源-->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
<property name="driverClass" value="${jdbc.driverClass}"/>
</bean>
<!--3. 配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
@Repository
public class BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//修改余额
public void updateBalance(String username,int price){
String sql = "update account set balance = balance - ? where username = ?";
jdbcTemplate.update(sql,price,username);
}
//获取某本图书的价格
public int getPrice(String isbn){
String sql = "select price from book where isbn = ?";
return jdbcTemplate.queryForObject(sql,Integer.class,isbn);
}
//修改库存(为了简单起见,每次减1)
public void updateStock(String isbn){
String sql = "update book_stock set stock = stock - 1 where isbn = ?";
jdbcTemplate.update(sql,isbn);
}
}
@Service
public class BookService {
@Autowired
private BookDao dao;
//结账
public void checkout(String username,String isbn){
dao.updateStock(isbn); //减库存
int price = dao.getPrice(isbn);
dao.updateBalance(username,price); //减余额
}
}
@Test
public void test01(){
BookService bean = context.getBean(BookService.class);
bean.checkout("Tom","ISBN-001");
System.out.println("sucess...");
}
- 使用事务
<!--1. 包扫描-->
<context:component-scan base-package="com.siki"/>
<!--2. 配置数据源-->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
<property name="driverClass" value="${jdbc.driverClass}"/>
</bean>
<!--3. 配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--4. 事务控制-->
<!--4.1 配置事务管理器,让其进行事务控制-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--控制数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--4.2 开启基于注解的事务控制模式(导入tx名称空间)-->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!--4.3 给事务方法加注解@Transactional-->
@Repository
public class BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//修改余额
public void updateBalance(String username,int price){
String sql = "update account set balance = balance - ? where username = ?";
jdbcTemplate.update(sql,price,username);
}
//获取某本图书的价格
public int getPrice(String isbn){
String sql = "select price from book where isbn = ?";
return jdbcTemplate.queryForObject(sql,Integer.class,isbn);
}
//修改库存(为了简单起见,每次减1)
public void updateStock(String isbn){
String sql = "update book_stock set stock = stock - 1 where isbn = ?";
jdbcTemplate.update(sql,isbn);
}
//修改图书价格
public void updatePrice(String isbn,int price){
String sql = "update book set price = ? where isbn = ?";
jdbcTemplate.update(sql,isbn,price);
}
}
@Service
public class BookService {
@Autowired
private BookDao dao;
//表示这是一个事务方法
@Transactional
public void checkout(String username,String isbn){
dao.updateStock(isbn); //减库存
int price = dao.getPrice(isbn);
dao.updateBalance(username,price); //减余额
}
}
4. 事务的属性
/*
事务属性: 注意: 有事务逻辑,容器中保存的是这个事务的代理对象
1. isolation-Isolation: 事务的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
2. propagation-Propagation: 事务的传播行为
当前事务是否需要和大事务使用同一事务
3. noRollbackFor-Class[]: 哪些异常,事务可以不回滚
1. 可以让原来默认回滚的事务,让其不回滚
2. @Transactional(noRollbackFor = {ArithmeticException.class})
注意: 运行时异常可以不用处理,默认回滚,编译时异常默认不回滚
4. rollbackFor-Class[]: 哪些异常,事务需要回滚
1. 可以让原来默认不回滚的事务,让其回滚
2. @Transactional(rollbackFor = {FileNotFoundException.class})
5. readOnly-boolean: 设置事务为只读事务 @Transactional(readOnly = true)
readOnly = true 可以加快查询速度
6. timeout-int: 超时,超出指定执行时长后自动终止回滚 @Transactional(timeout = 2)
*/
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void checkout(String username,String isbn){
// try {
// Thread.sleep(3000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
dao.updateStock(isbn); //减库存
int price = dao.getPrice(isbn);
dao.updateBalance(username,price); //减余额
int i = 1 / 0;
}
5. 事务传播
@Service
public class BookService {
@Autowired
private BookDao dao;
@Transactional(propagation = Propagation.REQUIRED)
public void checkout(String username,String isbn){
dao.updateStock(isbn);
int price = dao.getPrice(isbn);
dao.updateBalance(username,price);
int i = 1 / 0;
}
@Transactional(propagation = Propagation.REQUIRED)
public void updatePrice(String isbn,int price){
dao.updatePrice(isbn,price);
}
}
@Service
public class MyService {
@Autowired
private BookService service;
//事务传播(多事务并发运行)
@Transactional
public void update(){
//传播行为就是用来设置这个事务方法需不需要和之前的大事务共享一个事务(即使用同一连接)
service.checkout("Tom","ISBN-001");
service.updatePrice("ISBN-002",900);
//int i = 1 / 0;
}
}
@Test
public void test02(){
MyService bean = context.getBean(MyService.class);
bean.update();
}
6. 事务控制
<context:component-scan base-package="com.siki"/>
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
<property name="driverClass" value="${jdbc.driverClass}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--
基于xml配置的事务
1. 配置事务管理器
2. 配置事务方法: 事务切面按照我们的切入点表达式去切入事务方法
-->
<!--配置事务方法-->
<aop:config>
<aop:pointcut id="point" expression="execution(* com.siki.service.*.*(..))"/>
<!--配置事务增强-->
<!--advice-ref: 指向事务管理器的配置-->
<aop:advisor advice-ref="advice" pointcut-ref="point"/>
</aop:config>
<!--指定配置哪个事务管理器-->
<tx:advice transaction-manager="transactionManager" id="advice">
<tx:attributes>
<!--指明哪些方法是事务方法,切入点表达式只是说,事务管理器要切入这些方法,但是哪些方法需要事务,由tx:method控制-->
<tx:method name="*"/>
<tx:method name="checkout" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
@Repository
public class BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void updateBalance(String username,int price){
String sql = "update account set balance = balance - ? where username = ?";
jdbcTemplate.update(sql,price,username);
}
public int getPrice(String isbn){
String sql = "select price from book where isbn = ?";
return jdbcTemplate.queryForObject(sql,Integer.class,isbn);
}
public void updateStock(String isbn){
String sql = "update book_stock set stock = stock - 1 where isbn = ?";
jdbcTemplate.update(sql,isbn);
}
public void updatePrice(String isbn,int price){
String sql = "update book set price = ? where isbn = ?";
jdbcTemplate.update(sql,isbn,price);
}
}
@Service
public class BookService {
@Autowired
private BookDao dao;
public void checkout(String username,String isbn){
dao.updateStock(isbn);
int price = dao.getPrice(isbn);
dao.updateBalance(username,price);
}
public void updatePrice(String isbn,int price){
dao.updatePrice(isbn,price);
}
}
@Test
public void test(){
BookService bean = context.getBean(BookService.class);
bean.checkout("Tom","ISBN-001");
}
5. Spring源码
1. IOC容器启动创建bean
/*
我们需要清楚:
1. IOC是一个容器
2. 容器启动的时候会创建所有的单实例bean
3. 我们可以直接从容器中获取到某个bean
我们需要了解:
1. IOC容器的创建过程
2. 容器启动期间做了些什么、什么时候创建的所有单实例bean
3. IOC是如何创建这些单实例bean,并且进行管理的,最后将这些bean保存在什么地方
源码: 从HelloWorld开始,进行debug调试,边调试边查看控制台的打印
*/
//先进入ClassPathXmlApplicationContext的构造器
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh(); //所有单实例bean创建完成
}
}
@Override
public void refresh() throws BeansException, IllegalStateException {
//保证多线程情况下,IOC容器只会被创建一次
synchronized (this.startupShutdownMonitor) {
//准备刷新容器
prepareRefresh();
//ConfigurableListableBeanFactory是BeanFactory的子类
//这里准备了一个创建所有bean的工厂
//这一步,spring会解析xml配置文件将要创建的所有bean,并加入到beanDefinitionMap中
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//此时所有的bean已经加入到beanFactory中,准备beanFactory
prepareBeanFactory(beanFactory);
try {
postProcessBeanFactory(beanFactory);
invokeBeanFactoryPostProcessors(beanFactory);
//对spring中的一些组件注册后置处理器
registerBeanPostProcessors(beanFactory);
//初始化信息源,用来支持国际化功能的
initMessageSource();
//初始化一些事件转化器(spring创建和销毁bean的时候会产生一些其他的时间,需要进行这些事件 转化器处理)
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//初始化一些其他的IOC容器,这是个空方法,留给子类实现的
onRefresh();
// Check for listener beans and register them.
//注册监听器
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
//完成BeanFactory的初始化
//这个方法里面才是对bean的初始化,重点研究
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
//此时已经完成了容器的刷新
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
//bean的初始化
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
//这里主要是spring帮我们做一些自定义类型的类型转换
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
// Register a default embedded value resolver if no bean post-processor
// (such as a PropertyPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
// Stop using the temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(null);
// Allow for caching all bean definition metadata, not expecting further changes.
beanFactory.freezeConfiguration();
// Instantiate all remaining (non-lazy-init) singletons.
//这是初始化所有的单实例bean
beanFactory.preInstantiateSingletons();
}
public void preInstantiateSingletons() throws BeansException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Pre-instantiating singletons in " + this);
}
//拿到所有要创建的bean的名字
List<String> beanNames = new ArrayList(this.beanDefinitionNames);
//获取迭代器,遍历每一个bean的名字,创建单实例bean
Iterator var2 = beanNames.iterator();
while(true) {
String beanName;
Object bean;
do {
while(true) {
//获取bean的一些定义信息
RootBeanDefinition bd;
do {
do {
do {
if (!var2.hasNext()) {
var2 = beanNames.iterator();
while(var2.hasNext()) {
beanName = (String)var2.next();
Object singletonInstance = this.getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton)singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(() -> {
smartSingleton.afterSingletonsInstantiated();
return null;
}, this.getAccessControlContext());
} else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
return;
}
beanName = (String)var2.next();
//根据bean的id获取bean的定义信息
bd = this.getMergedLocalBeanDefinition(beanName);
} while(bd.isAbstract()); //是抽象的
} while(!bd.isSingleton()); //不是单例的
} while(bd.isLazyInit()); //是懒加载的(用的时候再创建)
//是否是一个实现了FactoryBean接口的工厂bean
if (this.isFactoryBean(beanName)) {
bean = this.getBean("&" + beanName);
break;
}
this.getBean(beanName); //创建bean的细节
}
} while(!(bean instanceof FactoryBean)); //不是工厂bean
FactoryBean<?> factory = (FactoryBean)bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
SmartFactoryBean var10000 = (SmartFactoryBean)factory;
((SmartFactoryBean)factory).getClass();
isEagerInit = (Boolean)AccessController.doPrivileged(var10000::isEagerInit, this.getAccessControlContext());
} else {
isEagerInit = factory instanceof SmartFactoryBean && ((SmartFactoryBean)factory).isEagerInit();
}
if (isEagerInit) {
this.getBean(beanName);
}
}
}
public Object getBean(String name) throws BeansException {
return this.doGetBean(name, (Class)null, (Object[])null, false);
}
//重点研究
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
//先拿到bean的名字
String beanName = this.transformedBeanName(name);
//先从已经注册的所有单实例bean中查看有没有这个bean
Object sharedInstance = this.getSingleton(beanName);
Object bean;
if (sharedInstance != null && args == null) {
if (this.logger.isDebugEnabled()) {
if (this.isSingletonCurrentlyInCreation(beanName)) {
this.logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
} else {
this.logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null);
} else {
if (this.isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
BeanFactory parentBeanFactory = this.getParentBeanFactory();
if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) {
String nameToLookup = this.originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory)parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
}
if (args != null) {
return parentBeanFactory.getBean(nameToLookup, args);
}
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
if (!typeCheckOnly) {
//标志这个bean已经被创建,防止多线程时多次创建
this.markBeanAsCreated(beanName);
}
try {
RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
this.checkMergedBeanDefinition(mbd, beanName, args);
//拿到创建当前bean之前需要提前创建的bean,也就是xml中的depends-on属性
//如果有,就循环创建
String[] dependsOn = mbd.getDependsOn();
String[] var11;
if (dependsOn != null) {
var11 = dependsOn;
int var12 = dependsOn.length;
for(int var13 = 0; var13 < var12; ++var13) {
String dep = var11[var13];
if (this.isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
this.registerDependentBean(dep, beanName);
try {
this.getBean(dep);
} catch (NoSuchBeanDefinitionException var24) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", var24);
}
}
}
//创建bean实例
if (mbd.isSingleton()) {
//重写createBean()方法创建bean
sharedInstance = this.getSingleton(beanName, () -> {
try {
return this.createBean(beanName, mbd, args);
} catch (BeansException var5) {
this.destroySingleton(beanName);
throw var5;
}
});
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
var11 = null;
Object prototypeInstance;
try {
this.beforePrototypeCreation(beanName);
prototypeInstance = this.createBean(beanName, mbd, args);
} finally {
this.afterPrototypeCreation(beanName);
}
bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
String scopeName = mbd.getScope();
Scope scope = (Scope)this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, () -> {
this.beforePrototypeCreation(beanName);
Object var4;
try {
var4 = this.createBean(beanName, mbd, args);
} finally {
this.afterPrototypeCreation(beanName);
}
return var4;
});
bean = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
} catch (IllegalStateException var23) {
throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", var23);
}
}
} catch (BeansException var26) {
this.cleanupAfterBeanCreationFailure(beanName);
throw var26;
}
}
if (requiredType != null && !requiredType.isInstance(bean)) {
try {
T convertedBean = this.getTypeConverter().convertIfNecessary(bean, requiredType);
if (convertedBean == null) {
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
} else {
return convertedBean;
}
} catch (TypeMismatchException var25) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", var25);
}
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
} else {
return bean;
}
}
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized(this.singletonObjects) {
//先从singletonObjects(ConcurrentHashMap)中拿出这个bean
Object singletonObject = this.singletonObjects.get(beanName);
//如果没有,就创建
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
this.beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = this.suppressedExceptions == null;
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet();
}
try {
//这里利用反射就创建了一个单实例bean
singletonObject = singletonFactory.getObject();
newSingleton = true;
} catch (IllegalStateException var16) {
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw var16;
}
} catch (BeanCreationException var17) {
BeanCreationException ex = var17;
if (recordSuppressedExceptions) {
Iterator var8 = this.suppressedExceptions.iterator();
while(var8.hasNext()) {
Exception suppressedException = (Exception)var8.next();
ex.addRelatedCause(suppressedException);
}
}
throw ex;
} finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
this.afterSingletonCreation(beanName);
}
if (newSingleton) {
//此时bean已经创建完成,就把这个bean加入到map中
this.addSingleton(beanName, singletonObject);
}
}
//如果有,就直接返回
return singletonObject;
}
}
//从这里取出singletonObjects(ConcurrentHashMap)
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/** Cache of singleton objects: bean name --> bean instance */
//map里面存放的就是bean的名字和实例
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
}
public interface ObjectFactory<T> {
/**
* Return an instance (possibly shared or independent)
* of the object managed by this factory.
* @return the resulting instance
* @throws BeansException in case of creation errors
*/
//返回一个bean实例
T getObject() throws BeansException;
}
//将新创建的bean加入到map中
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
/*
面试题: BeanFactory和ApplicationContext的区别
1. BeanFactory: bean工厂接口,负责创建bean实例,是Spring最底层的接口
2. ApplicationContext: 是BeanFactory的子接口,更多的是负责容器功能的实现,可以基于BeanFactory创建好的对象之上完成强大的容器功能,容器从map中获取bean,并且实现AOP功能,都依赖于ApplicationContext
3. 综上所述: BeanFactory是最底层的接口,ApplicationContext是BeanFactory的子接口,是留给程序员使用的IOC接口
*/