Spring 学习笔记
Spring
资料: https://pan.baidu.com/s/1aS4B69iA8-AtXqT7D9obXA 提取码: rczx
-
Spring 是一个开源框架
-
Spring为简化企业级开发而生,使用Spring,javaBean就可以实现很多以前要靠EJB才能实现的功能。同样的功能,在EJB中要通过繁琐的配置和复杂的代码才能实现,而在Spring中却非常的优雅和简洁。
-
Spring是一个IOC(DI)和AOP容器框架
-
Spring的优良特性
- 非侵入式
基于Spring开发的应用中的对象可以不依赖于Spring的API - 依赖注入
DI——Dependency Injection,反转控制(IOC)最经典的实现 - 面向切面编程
Aspect Oriented Programming——AOP - 容器
Spring是一个容器,因为它包含并且管理应用对象的生命周期 - 组件化
Spring实现了使用简单的组件配置组合成一个复杂的应用。在Spring中可以使用XML和Java注解组合这些对象 - 一站式
在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring自身也提供了表述层的SpringMVC和持久层的SpringJDBC)
- 非侵入式
Spring 模块化分
-
Test
Spring单元测试模块 -
Core Container
核心容器(IOC),黑色代表这部分的功能由哪些jar包组成。要使用这部分的完整功能,这些jar包都要导入。
-
AOP、Aspects
面向切面编程模块 -
Data Access/Integration
数据访问 -
Web
Spring开发web应用模块
IOC和DI
- IOC 容器
可以用来整合大多数第三方框架 - AOP 面向切面编程
通常用来完成一些,声明式事务。例如操作数据库等…
IOC
IOC全称(Inversion of control或控制反转)。
-
控制
指资源的获取方式,获取方式又分为一下两点-
主动式:
要什么资源都需要自己创建。这种方式在创建简单对象时非常方便, 但在创建复杂对象时,是非常麻烦的。class TestClass { Object obj = new Object(); }
-
被动式
资源的获取不是我们自己创建,而是交给容器创建和设置。class TestClass { Object obj; public void test() { obj.xxx(); } }
容器管理所有的组件。假设
TestClass
受容器管理,Object
也受容器管理,容器可以自动的检查出哪些组件需要用到另一些组件。容器会帮我们创建出TestClass
对象并把Object对象赋值过去。
-
-
反转
主动获取资源变为被动的接受资源
DI
DI(Dependency Injection或依赖注入)。容器能知道哪些组件运行的时候需要使用到另外一个组件。容器会通过反射的方式将容器中准备好的Object
注入到TestClass
。
以前是自己new对象,现在所有的对象交给容器创建。「给容器中注册组件」
只要是容器管理的组件,都能使用容器提供的强大功能。
HelloWorld
导包
需要导入所有核心jar包
commons-logging-1.1.3.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
编写配置文件
spring的配置文件中,集合了spring的ioc容器管理的所有组件。以下为spring 配置文件基础格式。
<?xml version="1.0" encoding="UTF-8"?>
注册一个对象,Spring会自动创建这个对象。一个Bean标签可以注册一个组件
package top.clover.spring.bean;
public class Person {
private String name;
private Integer age;
private String gender;
private String email;
}
在Bean标签中,有一个class,它表示你要注册哪一个组件。这里需要的是一个全类名。
在这个bean标签中,还有一个id,这是这个组件的唯一标识。
<?xml version="1.0" encoding="UTF-8"?>
如果你还想为组件赋值,可以使用property
标签。name
表示属性名,value
表示属性值。
除了调用set
进行赋值外,还可以使用constructor-arg
标签调用有参构造器进行赋值。
public Person(String name, Integer age, String gender, String email) {
this.name = name;
this.age = age;
this.gender = gender;
this.email = email;
}
constructor-arg
也可以使用索引进行赋值
编写测试类
在配置文件写好后,可以使用JUnit
来进行测试
@Test
public void getPersonBean() {
ApplicationContext app = new ClassPathXmlApplicationContext("ioc.xml");
Person bean = app.getBean(Person.class);
System.out.println(bean.getAge());
}
ApplicationContext
代表ioc容器。在spring中,有很多种容器,其中有两个最为常用org.springframework.context.support.ClassPathXmlApplicationContext
和org.springframework.context.support.FileSystemXmlApplicationContext
。
ClassPathXmlApplicationContext
表示在xml配置文件在当前项目类路径下,FileSystemXmlApplicationContext
获取其它位置的xml位置文件。
app.getBean(Person.class);
可以通过类的方式获取到对应的bean,也可以通过刚刚定义的id去获取
app.getBean("person");
总结
- 容器中对象的创建都是在容器创建完成的时候就已经创建好了。
- 同一个组件在ioc容器中是单实例的
- 如果组件并不在ioc中,则会出现
NoSuchBeanDefinitionException
异常,无论是以id的形式还是class的形式获取。 - 在
getBean(Class<?>)
的时候,如果ioc中存在多个同类型组件时,则会抛出NoUniqueBeanDefinitionException
异常 - 如果使用
property
对bean赋值,那么该属性必须存在set
方法。
P名称空间
ioc可以通过p名称空间进行赋值,在xml中名称空间是用来防止标签重复的。在beans
标签中添加xmlns:p="http://www.springframework.org/schema/p"
。添加后就可以使用p
名称空间进行赋值。
<?xml version="1.0" encoding="UTF-8"?>
复杂属性赋值
null值
在java中,除了基本类型,其它类型在不赋值的情况下默认值都是null。如果需要给一个属性null值,不能通过property
标签的value
属性来赋值,可以通过null
标签。
自定义复杂类型
public class Person {
private String name;
private Integer age;
private String gender;
private String email;
private Car car;
getter/setter....
}
在Person
中,存在一个Car
类型,这个类型属于我们自定义的复杂类型。可以使用两种方式进行赋值
使用其它Bean
在propertu
中有一个ref
属性,这个属性值必须是某一个bean
的id,spring可以通过这个属性将指定的bean
对指定的属性进行赋值。
@Test
public void testBeanPropertyRef() {
ApplicationContext app = new ClassPathXmlApplicationContext("ioc.xml");
Person bean = app.getBean(Person.class);
Car car = bean.getCar();
System.out.println("汽车:" + car.getCardName());
System.out.println("颜色:" + car.getColor());
}
汽车:小轿车
颜色:五彩斑斓的黑
引用内部bean
这种方式相当于car = new Car();
。与外部无关,所以getBean
的时候会找不到这个car
List类型
在ioc中对List
类型进行赋值,可以使用list
标签。
除了内部Bean的方式,还可以使用ref
标签引用外部Bean。
Map类型
同样的,使用List类型有对应的list标签,那么Map类型也有一个map标签。spring底层使用的是LinkedHashMap
。
一个entry
对于一个KV,map也提供了一个value-ref
属性用于引用外部Bean。用法与其它ref
属性一致。
Util名称空间
使用这个名称空间,需要在beans
中添加xmlns:util="http://www.springframework.org/schema/util"
,还需要在xsi中添加两个与util名称相关的链接http://www.springframework.org/schema/util, http://www.springframework.org/schema/util/spring-util-4.0.xsd
。可以为Map
、List
、set
、properties
等类型创建对应的Bean。
以map为例,util:map
相当于new 一个LinkedHashMap
。不需要再使用map标签
ApplicationContext app = new ClassPathXmlApplicationContext("ioc.xml");
Map diyMap =(Map) app.getBean("diyMap");
System.out.println(diyMap);
{name=clover, age=19}
Bean之间的依赖
Bean
的创建默认是按照配置文件中配置的先后顺序。
person创建了...
book被创建...
如果你注册的bean需要依赖到某个bean时,若是你的bean先被创建,可能会发生不可预料的错误。那么这时候就需要用到spring提供的depends-on
属性。它需要一个id,也可以通过,
隔开传多个id。表示我当前这个bean依赖到了xxx。
book被创建...
person创建了...
Bean的多实例和单实例
在spring中,bean默认是单实例,若想讲bean组册为多实例,需要使用bean
标签提供的scope
属性。该属性有两个属性值
-
prototype
表示当前bean是多实例的
- 容器启动默认不会去创建bean
- 多实例bean只有在获取的时候被创建
- 每次获取都会创建一个新的对象
-
singleton
表示当前bean是单实例的- 在容器启动完成之前就已经创建好对象保存在容器中了。
- 在任何时候获取都是获取之前创建好的那个对象。
静态工厂和实例工厂
public class AirPlane {
/**
* 机长名称
*/
private String captainName;
/**
* 副驾驶
*/
private String coPilot;
/**
* 发动机
*/
private String engine;
/**
* 载客量
*/
private Integer total;
}
工厂模式
工厂是一个专门帮我们创建对象的类
静态工厂
工厂本身不需要创建对象。都是通过静态方法调用:obj = xxxFactory.getxxx(String className)
public class AirPlaneStaticFactory {
/**
* 飞机工厂
* @param captainName 机长名称
* @return 飞机实例
*/
public static AirPlane getAirPlane(String captainName) {
AirPlane airPlane = new AirPlane();
airPlane.setCaptainName(captainName);
airPlane.setCoPilot("Clover");
airPlane.setEngine("太行");
airPlane.setTotal(300);
return airPlane;
}
}
通过IOC调用静态工厂注册Bean
创建好工厂后,需要通过bean去调用这个工厂,最后将工厂的返回值注册为一个Bean对象。
bean
标签class
属性指向工厂全类名,需要配合factory-method
属性让ioc调用工厂方法。如果工厂方法需要传参,可以使用constructor-arg
赋值。
@Test
public void testAirPlaneStaticFactory() {
AirPlane airPlaneBean = (AirPlane)app.getBean("AirPlaneByAirPlaneStaticFactory");
System.out.println("通过静态工厂创建的bean对象:" + airPlaneBean);
}
通过静态工厂创建的bean对象:AirPlane{captainName='张三', coPilot='Clover', engine='太行', total=300}
实例工厂
工厂本身需要创建对象:obj = new xxxFactory();
public class AirPlaneInstanceFactory {
/**
* 飞机工厂
*
* @param captainName 机长名称
* @return 飞机实例
*/
public AirPlane getAirPlane(String captainName) {
AirPlane airPlane = new AirPlane();
airPlane.setCaptainName(captainName);
airPlane.setCoPilot("Clover");
airPlane.setEngine("太行");
airPlane.setTotal(300);
return airPlane;
}
}
通过IOC调用实例工厂注册Bean
- 先配置出实例工厂对象
- 配置要创建的AirPlane使用哪个工厂创建
通过factory-bean
属性来指定使用哪个工厂实例创建bean
@Test
public void testAirPlaneInstanceFactory() {
AirPlane airPlaneBean = (AirPlane)app.getBean("airPlaneByAirPlaneInstanceFactory");
System.out.println("通过实例工厂创建的bean对象:" + airPlaneBean);
}
通过实例工厂创建的bean对象:AirPlane{captainName='李四', coPilot='Clover', engine='太行', total=300}
FactoryBean
FactoryBean是Spring规定的一个接口,只要是这个接口接口的实现类,Spring都认为是一个工厂。
getObject
获取工厂创建的对象getObjectType
获取对象类型isSingleton
当前工厂注册的Bean是否为单例
FactoryBean
创建的对象不管是单实例还是多实例,都是在获取的时候才创建对象。
public class MyFactoryBean implements FactoryBean {
@Override
public AirPlane getObject() throws Exception {
AirPlane airPlane = new AirPlane();
airPlane.setCaptainName("王五");
airPlane.setCoPilot("Clover");
airPlane.setEngine("太行");
airPlane.setTotal(300);
return airPlane;
}
@Override
public Class<?> getObjectType() {
return AirPlane.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
实现了FactoryBean
接口后还需要在配置文件中进行注册
@Test
public void testMyFactoryBean() {
AirPlane airPlaneBean = (AirPlane)app.getBean("airPlaneBySpringFactoryBean");
System.out.println(
"通过FactoryBean创建的bean对象:" + airPlaneBean);
}
通过FactoryBean创建的bean对象:AirPlane{captainName='王五', coPilot='Clover', engine='太行', total=300}
Bean的后置处理器
- 编写后置处理器实现类
BeanPostProcessor
- 将后置处理器注册在配置文件中
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* bean在初始化的时候会调用这个方法
* @param bean bean对象
* @param beanName bean注册的名称
* @return 要注册的bean对象
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("「"+ bean +"」bean准备开始初始化了 --->> " + beanName);
return bean;
}
/**
* 初始化方法之后被调用
* @param bean bean对象
* @param beanName bean名称
* @return 要注册的bean对象
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("「"+ bean +"」bean初始化方法调用完了 --->> " + beanName);
return bean;
}
}
在配置文件注册
「AirPlane{captainName='张三', coPilot='Clover', engine='太行', total=300}」bean准备开始初始化了 --->> airPlaneByAirPlaneStaticFactory
「AirPlane{captainName='张三', coPilot='Clover', engine='太行', total=300}」bean初始化方法调用完了 --->> airPlaneByAirPlaneStaticFactory
Dao、Controller、Service
通过给bean上添加某些注解,可以快速的将bean加入到ioc容器中。
分别使用Spring提供的这四个注解
-
@Controller
用来给控制层加上 -
@Service
业务逻辑层的组件添加这个注解 -
@Repository
给数据库层(持久化层/Dao层)添加这个注解 -
@Component
可以给有功能的类添加上这个注解,表示这个类是一个组件
注解可以随便加,Spring底层不会去验证你的这个组件是否如你注解所说就是一个dao层或者就是一个servlet层的组件。
使用注解将组件快速的加入到容器需要这几步:
- 给组件上标注所需注解
- 告诉Spring自动扫描加了注解的组件。这个功能依赖了context名称空间
- 导入aop包,这个包支持注解模式
在配置文件中加入context
名称空间:xmlns:context="http://www.springframework.org/schema/context"
加入之后这个名称空间提供了一个context:compnent-scan
标签,用来自动扫描组件。这个标签中有一个base-package
属性,用来指定扫描的基础包,把基础包及子包下的所有加了注解的类自动的扫描进ioc容器中。
默认注册为单例模式。若想注册为多例,需要使用
@Scope
注解
@Autowired自动装配
被@Autowired
标注的属性,spring会根据类型到ioc容器中查找并赋值给被标注的属性。前提是你这个属性所在的类和目标类型已经被注册进容器中。
@Controller
public class BookController {
@Autowired
private BookService bookService;
public void doGet() {
bookService.saveBook();
}
}
@Service
public class BookService {
@Autowired
private BookDao bookDao;
public void saveBook() {
System.out.println("调用bookDao中的saveBook保存图书....");
bookDao.saveBook();
}
}
@Repository
public class BookDao {
public void saveBook() {
System.out.println("保存书籍成功....");
}
}
测试这个controller
/**
* 测试自动注入
*/
@Test
public void testAutowired() {
BookController controller = app.getBean(BookController.class);
controller.doGet();
}
调用bookDao中的saveBook保存图书....
保存书籍成功....
在方法上使用
@Autowired
可以标注在很多地方
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
如果作用在方法上
- 这个方法会在bean创建的时候自动运行
- 这个方法上的每一个参数都会自动注入
@Controller
public class BookController {
@Autowired
public void testAutowired(BookService bookService) {
System.out.println("容器创建,autowired运行:---》"+bookService);
}
}
容器创建,autowired运行:---》top.ctong.service.BookService@5e21e98f
大致原理
@Autowired
默认按照类型去容器中找到对应的组件- 找到一个就直接赋值
- 没有找到就抛异常
BeanCreationException
- 如果找到多个
- 会根据变量名最为id再找一次
app.getBean(String id)
- 如果这时候还是匹配不上,那么还是抛出错误。
- 会根据变量名最为id再找一次
@Qualifier
可以修改@Autowired
的哦人行为,@Autowired
会根据@Qualifier
的值进行查找。app.getBean(String id);
自动装配有三种
@Autowired
这是spring自己的注解,相比其它两个,它更强大@Resource
这是javax提供的,是一个标准。相比其它,他的扩展性更强。如果换成其它框架,@Resource
还可以使用,@Autowired
可能就不行了。@Inject
泛型注入
在同类型组件中,@Autowired
可以根据泛型去匹配
例如有一个BaseDao
public abstract class BaseDao {
abstract void save();
}
有两个类实现BaseDao
@Repository
public class BookDao extends BaseDao {
@Override
public void save() {
System.out.println("保存图书");
}
}
@Repository
public class UserDao extends BaseDao {
@Override
public void save() {
System.out.println("用户保存");
}
}
有一个BaseService
去使用泛型注入对应Dao
public class BaseService {
@Autowired
private BaseDao baseDao;
public void save() {
System.out.println("baseDao类型是:" + baseDao);
}
}
分别有UserService
和BookService
继承BaseService
@Service
public class BookService extends BaseService{
public void saveBook() {
System.out.println("调用bookDao中的saveBook保存图书....");
}
}
@Service
public class UserService extends BaseService{
public UserService() {
// 防止存在一个或多个有参构造器时反射通过无参构造起实例化发生异常
}
}
最后测试结果
@Test
public void testBaseService() {
UserService userBean = app.getBean(UserService.class);
BookService BookBean = app.getBean(BookService.class);
userBean.save();
BookBean.save();
}
baseDao类型是:top.ctong.dao.UserDao@6692b6c6
baseDao类型是:top.ctong.dao.BookDao@1cd629b3
AOP
AOP(Aspect Oriented Programming或面向切面编程) 。这是一种新的编程思想。他不是用来替代OOP的,他是一种基于OOP基础之上衍生出的一种新的思想。
面向切面编程是指在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行。
AOP与动态代理
- 动态代理写起来代码复杂
- jdk默认的动态代理,如果目标对象没有实现任何接口,是无法为它创建代理对象的。
- spring实现的AOP功能,底层就是动态代理
- spring可以一句代码都不写的去创建动态代理
- spring实现简单,而且没有强制要求目标对象必须实现接口
- 原生动态代理就是切面编程,而Spring简化了面向切面编程
AOP的几个专业术语
-
横切关注点
每一个方法的开始 -
通知方法
每一个横切关注点触发的方法 -
切面类
通知方法所在的类 -
连接点
每一个方法的每一个位置都是一个连接点 -
切入点
在众多连接点中选出我们感兴趣的地方。例如:方法结束需要记录日志,方法异常需要记录日志,方法返回需要记录日志的地方。
-
切入点表达式
导包
若想spring支持面向切面编程,需要导入spring-aspects-4.0.0.RELEASE.jar
,这是spring提供的基础版jar包。
由于spring提供的包比较基础,可以使用第三方加强包「即使目标对象没有实现任何接口也能创建动态代理」。
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
配置
-
将目标类和切面类(装了通知方法的类)加入到ioc容器
@Service public class MyMathCalculator 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; } }
-
使用
@Aspect
注解告诉spring哪个是切面类@Aspect @Component public class LogUtils { /** * 方法执行之前的日志记录器 */ @Before("execution(public int top.ctong.aop.impl.MyMathCalculator.*(int, int))") public static void methodStartBefore() { System.out.println("方法执行前...."); } @After("execution(public int top.ctong.aop.impl.MyMathCalculator.*(int, int)))") public static void methodAfter() { System.out.println("方法执行完成...."); } @AfterReturning("execution(public int top.ctong.aop.impl.MyMathCalculator.*(int, int)))") public static void methodReturning() { System.out.println("方法执行返回值...."); } @AfterThrowing("execution(public int top.ctong.aop.impl.MyMathCalculator.*(int, int)))") public static void methodException() { System.out.println("方法执行完成...."); } }
-
告诉spring切面类里面的每一个方法都什么时候运行。
@Before
在目标方法执行之前运行「前置通知」@After
在目标方法结束之后运行「后置通知」@AfterReturning
在目标方法正常返回之后运行「返回通知」@AfterThrowing
在目标方法抛出异常之后运行「异常通知」@Around
环绕「环绕通知」
-
标注好所需注解后,还需要告诉spring,你这个切面在哪里运行,需要写切入点表达式
excution(访问权限符 返回值类型 方法签名)
-
在配置文件中 开启基于注解的AOP功能
<?xml version="1.0" encoding="UTF-8"?>
测试
@Test
public void testLogUtilsAspect() {
Calculator bean = this.app.getBean(Calculator.class);
bean.add(1,1);
}
方法执行前....
方法执行完成....
方法执行返回值....
注意:当前bean
还是一个代理对象,因为AOP底层就是原生Proxy
,所以不能通过MyMathCalculator.class
来获取。但当被代理类没有任何父类的时候,又可以通过MyMathCalculator.class
进行获取。
在没有任何父类的时候,是cglib帮我们创建的代理对象。cglib为我们创建的一个内部类,内部类中实现了所有MyMathCalculator
的方法
class top.ctong.aop.impl.MyMathCalculator$$EnhancerByCGLIB$$1a9d9ab2
切入点表达式
切入点表达式的固定写法
excution(访问权限符 返回值类型 方法签名(参数列表))
通配符
*
- 匹配一个或多个字符
- 匹配任意一个参数
- 如果放在路径上,那么只能匹配一层路径
- 权限不能表示
..
- 匹配任意多个参数,任意类型参数
- 匹配任意多层路径
&&
切入的位置要同时满足两种表达式||
切入的位置最少要满足一种表达式!
不满足指定表达式触发
抽取表达式
抽取可重用的切入点表达式
- 随便声明一个没有实现的返回
void
的空方法。 - 给方法上标注
@Pointcut
注解
@Aspect
@Component
public class AspectLogUtils {
@Pointcut("execution(* top.ctong..add(..))")
public static void aspectAddMethod() {
// 抽取切入点表达式
}
@After("aspectAddMethod()")
public static void aspectAllAddMethod() {
System.out.println("方法执行了...");
}
}
目标方法信息
只需要为通知方法的参数列表上写一个JoinPoint
类型的参数。它封装了目标方法的详细信息
@After("execution(* top.ctong.aop.impl.MyMathCalculator.*(..))")
public static void methodAfter(JoinPoint joinPoint) {
// 获取方法签名
Signature signature = joinPoint.getSignature();
System.out.println("「" + signature.getName() + "」方法执行完成....使用的参数:「" + Arrays.asList(joinPoint.getArgs()) + "」");
}
「add方法执行完成....使用的参数:「[1, 1]」
获取方法返回值,需要在通知注解中指定,在@AfterReturning
注解中,有一个returning
属性,用来指定参数名
@AfterReturning(value = "execution(public int top.ctong.aop.impl.MyMathCalculator.*(int, int)))", returning = "result")
public static void methodReturning(JoinPoint joinPoint, Object result) {
// 获取方法签名
Signature signature = joinPoint.getSignature();
System.out.println("「" + signature.getName() + "」方法执行返回值===>>" + result);
}
获取异常和获取返回值操作一样
@AfterThrowing(value = "execution(* top.ctong.aop.impl.MyMathCalculator.*(..))", throwing = "exception")
public static void methodException(Exception exception) {
exception.getCause();
}
Around环绕通知
@Around
是spring最强大的通知。它相当于我们手写动态代理。
@Around("aspectAddMethod()")
public Object aspectAround(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
// 类似method.invoke()
return pjp.proceed(args);
}
基于配置文件的AOP
<?xml version="1.0" encoding="UTF-8"?>
若想使用基于配置文件的aop,需要引入aop
空间。
aop:aspect
指定切面类aop:pointcut
抽取切入点表达式,这个标签如果放在aop:aspect
中,那么只能在当前切面可使用,若想全局使用,可以将其放置在切面外,也就是aop:config
直接子级。aop:before
指定前置切入aop:after
指定后置切入aop:after-throwing
指定异常切入aop:after-returning
指定返回时切入
SpringIOC源码
- IOC是一个容器
- 容器启动的时候创建所有单实例对象
- 我们可以直接从容器中获取到这个对象
SpringIOC启动过程
-
ClassPathXmlApplicationContext
所有构造器调用的都是这个构造器public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
-
在构造器中,最重要的代码是
refresh();
。它负责创建所有单实例bean和启动容器-
在第一行代码中,
synchronized (this.startupShutdownMonitor)
有个同步锁,是为了保证在多线程环境中IOC容器只被创建一次。生产环境默认是多线程的。
-
prepareRefresh();
解析配置文件,以准备刷新上下文 -
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
准备一个Bean工厂,在此工厂中准备好了待初始化的Bean、系统环境信息等基础信息用于后来创建Bean。也就是说在这一步已经解析好了xml配置文件。
-
prepareBeanFactory(beanFactory);
设置工厂的所有可能用到的外置工厂,例如注解解析器,类加载器等其它工厂 -
postProcessBeanFactory(beanFactory)
工厂的后置处理设置 -
invokeBeanFactoryPostProcessors(beanFactory);
调用bean工厂的后置处理器 -
registerBeanPostProcessors(beanFactory);
注册所有BeanPostProcessor.class
类型的Bean。也就是说在这一步把所有BeanPostProcessor.class
实例化。 -
initMessageSource();
支持国际化语言服务 -
initApplicationEventMulticaster();
初始化事件传唤器,spring有创建、销毁bean时会产生非常多的事件,转换器是为了能让其它组件感知到spring触发了哪个事件。 -
onRefresh();
这是一个空的方法,留给子类的一个初始化方法。例如你重写IOC容器,可以重写这个方法进行初始化某些必要的Bean。 -
registerListeners();
注册监听器,注册的是ApplicationListener.class
类型的监听器,这是一个spring事件监听器。 -
finishBeanFactoryInitialization(beanFactory);
完成所有剩余的bean实例的初始化-
beanFactory.preInstantiateSingletons();
初始化所有剩余的beanorg.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
-
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
根据id拿到Bean的定义信息。里面包含了bean所有的详细信息。 -
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { ... }
如果当前bean不是一个抽象类,并且是单实例和不是懒加载。那么就创建它。懒加载就是在使用的时候才创建。
-
if (isFactoryBean(beanName)) {...} else {getBean(beanName);}
如果是工厂bean那么就通过工厂去创建这个bean。否则直接创建。 -
所有的
getBean
调用的都是org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
,直接看它就好-
final String beanName = transformedBeanName(name);
获取Bean名称 -
Object sharedInstance = getSingleton(beanName);
从单例缓存池中检查是否存在这个bean。如果存在就直接拿这个bean。 -
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
获取当前bean的定义信息。 -
String[] dependsOn = mbd.getDependsOn();
获取当前bean是有依赖了谁。也就是在xml文件中bean标签的depends-on
属性。如果有那么循环它,再调用getBean
方法。也就是说,先创建好所依赖的对象再创建自己。String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dependsOnBean : dependsOn) { if (isDependent(beanName, dependsOnBean)) { throw new BeanCreationException("Circular depends-on relationship between '" + beanName + "' and '" + dependsOnBean + "'"); } registerDependentBean(dependsOnBean, beanName); getBean(dependsOnBean); } }
-
sharedInstance = getSingleton(...)
创建bean。-
singletonObject = singletonFactory.getObject();
一系列验证当前bean不存在之后开始创建。org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
通过反射创建:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance
-
addSingleton(beanName, singletonObject);
将创建好的bean对象放到单实例缓存池中在项目中使用
getBean
其实也是在这个缓存池中获取对应的bean
-
-
-
-
-
BeanFactory和ApplicationContext的区别
ApplicationContext是BeanFactory的子接口
- BeanFactory是一个工厂接口,也是Spring最底层的接口。负责创建Bean实例,容器里面保存的所有单例bean其实都是在一个map中。
- ApplicationContext是容器接口,更多的是负责容器功能的实现。(可以基于BeanFactory创建好的对象之上完成强大的容器)容器可以从map中获取这个bean,并且aop、di在ApplicationContext接口的下面的这些类里面。
BeanFactory是最底层的接口,而ApplicationContext更多是留给我们使用的ioc容器接口。