spring 5.3 源码学习(一)
spring核心原理学习
注:该文章均不涉及spring的源码,从spring原理方面进行理解,在后续文章会展示spring的源码
spring解析
在以前学习spring的会经常使用到
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
通过ClassPathXmlApplicationContext加载spring的配置文件,现在这个类已经过时,在新版的springmvc和springboot中都采用AnnotationConfigApplicationContext
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class)
其作用也是一样加载AppConfig配置类的信息
@Component
public class UserService {
public void test() {
System.out.println("test");
}
}
@ComponentScan("com.mydemo")
public class AppConfig {
}
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
}
}
通过以上代码getBean(“userService”)方法创建一个叫做userService的bean并调用该bean的方法。但是创建出来的userService怎么知道是UserService 类型的呢?
我们可以想到spring不会把所有的类都创建,那么必须要知道哪些类需要创建哪些类不需要创建。所以spring第一步需要进行包扫描,通过扫描路径下那些类有@Component、 @Service等注解。那么扫描路径哪里来,可以想到是通过某个配置把路径传进去。然后userService如何对应UserService 这个类呢。应该是一个Map类把创建bean的name作为key,对应的类型作为value保存起来,这样通过getBean(“userService”)就能找到对应的类了。
总结
spring会通过解析AppConfig这个类获取扫描路径
通过扫描路径扫描哪些类上有@Component、 @Service这些注解。
把有这些注解的类创建出来放入到一个Map<String, Class>当中保存起来
然后通过getBean(“userService”);bean的名称获取到bean对应的类型。
那么问题来了bean是如何创建的呢
bean的创建流程
创建一个对象往往通过new xxx();其实就是调用了类的构造方法,那么spring也应当是调用了类的构造方法(默认为无参构造)来创建对象,那么创建了对象之后,对象的属性是怎么赋值的呢,我们在使用spring的时候经常使用的@Autowired,然后这个属性就有值了,为什么呢?
同样spring不会把bean中的每个属性都进行赋值,而是通过扫描属性上是否有@Autowired注解,有的话在进行属性注入。那么这是怎么做到的呢,下面通过代码来大致展示该流程。
//假设下面这行代码是扫描之后,通过全限定名加载得到的类对象
Class<?> clazz = Class.forName("com.mydemo.service.UserService");
//实例化对象
UserService bean = (UserService)clazz.newInstance();
//对象属性赋值
for (Field field : clazz.getFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.set(bean,??);
}
}
那么在属性注入之后就成为一个bean了吗,还不是,还需要进行初始化前,初始化,初始化后。
初始化前
为什么要有初始化前这个步骤,举个例子,如果在属性注入的时候,假设我们需要注入一个User类到UserService中,User类包含了用户名和密码,需要从数据库中获取,那么单纯的通过上面的代码就不能完成。这时候我们需要在依赖注入之后,初始化之前把数据库中的用户信息查出来放到User类中,这就是初始化前(当然这只是举个例子而已)。我们把查询用户数据封装成一个方法,只需要在初始化前调用即可。那么spring怎么知道那些方法是在初始化前执行的呢?这就需要@PostConstruct
注解。
@Component
public class User {
private String username;
private String password;
}
@Component
public class UserService {
@Autowired
private User user;
@PostConstruct
private void a(){
//从数据库中查询信息 赋值给User
}
public void test() {
System.out.println("test");
}
}
//判断哪些方法需要初始化前之情
for (Method method : clazz.getMethods()) {
if (method.isAnnotationPresent(PostConstruct.class)) {
method.invoke(bean,null);
}
}
可以理解为初始化前是来处理@PostConstruct
注解
初始化
和初始化前类似,只不过初始化用来处理实现了InitializingBean接口的类。实现了该接口要重写afterPropertiesSet()这个方法。上面的例子也可用在此处。代码大致如下:
@Component
public class UserService implements InitializingBean {
@Autowired
private User user;
public void test() {
System.out.println("test");
}
@Override
public void afterPropertiesSet() throws Exception {
//从数据库中查询信息 赋值给User
}
}
//判断是否实现InitializingBean
if (bean instanceof InitializingBean){
((InitializingBean)bean).afterPropertiesSet();
}
初始化后
初始化后用来处理一个非常重要的概念就是AOP。
总结
bean的创建流程就是bean的生命周期。spring的生命周期大致如下:
- 利用类的构造方法实例化得到一个对象(一个类中有多个构造方法,是如何选择呢,这个叫构造方法推断)
- 得到一个对象之后spring会扫描这个对象属性中是否含有
@Autowired
,有的话依赖注入 - 依赖注入后,Spring会判断该对象是否实现了BeanNameAware接口、 BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前 对象必须实现该接口中所定义的setBeanName()、setBeanClassLoader()、 setBeanFactory()方法,那Spring就会调用这些方法并传入相应的参数(Aware回调,以后再说)
- Aware回调后,Spring会判断该对象中是否存在某个方法被@PostConstruct注解 了,如果存在,Spring会调用当前对象的此方法(初始化前)
- 然后Spring会判断该对象是否实现了InitializingBean接口,如果实现了,那Spring就会调用当前对象中的afterPropertiesSet()方法(初始化)
- 最后,Spring会判断当前对象需不需要进行AOP,如果不需要那么Bean就创建完 了,如果需要进行AOP,则会进行动态代理并生成一个代理对象做为Bean(初始化后)
推断构造方法
spring在生成bean的过程中,需要利用该类的构造方法实例化得到一个对象,但是如果一个类有多个构造方法spring会用哪个呢?
public class Test {
public static void main(String[] args) {
ZhouyuApplicationContext applicationContext = new ZhouyuApplicationContext(AppConfig.class);
OrderService orderService = (OrderService) applicationContext.getBean("orderService");
orderService.test();
}
}
@Component
public class UserService {
//定义两个普通类OrderService 、GoodsService 分别加上@Component
public UserService(OrderService orderService,GoodsService goodsService) {
System.out.println(orderService);
System.out.println(goodsService);
}
public UserService(OrderService orderService) {
System.out.println(orderService);
}
public void test() {
System.out.println("test");
}
}
当存在两个构造参数时执行Test的main方法会报错:Failed to instantiate [com.mydemo.service.UserService]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.zhouyu.service.UserService.<init>()
;说找不到默认的构造方法。我们知道当构建了有参构造而不显示申明无参构造的话,那么这个类就没有无参构造方法。
如果显示申明了无参构造,再来执行上述代码就不会报错,如果只有一个有参构造,再来执行上述代码也不会报错。但是如果有多个构造方法怎么办呢?只需要在构造方法上添加@Autowired,spring就会知道调用这个构造方法,如果不添加@Autowired,那么就会默认调用无参构造,如果没有无参构造就会报错。
另一个问题,spring怎么知道有参构造的参数对应哪些类型呢?之前有提过spring会把创建出来的类放到一个Map中,然后根据入参的类型去找,如果只找到一个,就作为入参,如果找到多个,再根据参数名来找,如果还找不到就会报错。
总结
spring在生成bean时会调用bean的构造方法,具体判断如下:
- 如果只有一个构造方法,不管是有参还是无参都会调用
- 如果有多个,默认调用无参构造,但是如果有某个构造方法上添加了@Autowired注解,就会调用有@Autowired注解的构造方法。如果多个构造方法中没有@Autowired也没有无参构造spring就会报错
No default constructor found;
- 在调用有参构造时,会根据入参的类型和名称在spring生成的bean中寻找。spring在生成一个bean后会把bean的名称作为key,bean的类型作为value保存到Map中(以单例为例子)。因此spring会先根据入参类型查找,找到唯一就作为入参,如果有多个再根据bean的名称确定唯一,如果找不到bean作为入参参数,spring就会报错
No qualifying bean of type 'com.mydemo.service.OrderService' available: expected at least 1 bean which qualifies as autowire candidate
这个过程就是推断构造方法
AOP大致流程
之前有说到在初始化后的这个阶段用来处理AOP。它是怎么处理呢?把上述代码案例改造一下
//修改配置类开启动态代理
@ComponentScan("com.mydemo")
@EnableAspectJAutoProxy
public class AppConfig {
}
//给UserService的test方法添加切面
@Aspect
@Component
public class MyAspect {
@Before("execution(public void com.mydemo.service.UserService.test())")
public void demoBefore(JoinPoint joinPoint) {
System.out.println("AOPbefore");
}
}
//其他代码不变
spring会判断这个类有没有相关切面(怎么判断后面再说),没有就直接生成bean。有的话就生成代理对象。
先说说怎么生成这个代理对象。spring的代理采用cglib动态代理。代码类似于
class UserServiceProxy extends UserService{
public void test(){
//执行切面的方法
TODO
//执行自己的方法
super.test();
}
}
大致创建过程:
- 生成代理类UserServiceProxy,代理类继承UserService
- 代理类中重写了父类的方法,比如UserService中的test()方法
- 代理类中还会有一个target属性,该属性的值为被代理对象(也就是通过 UserService类推断构造方法实例化出来的对象,进行了依赖注入、初始化等步骤的对象)
- 代理类中的test()方法被执行时的逻辑如下: a. 执行切面逻辑(@Before) b. 调用target.test()
测试代码
@Component
public class UserService {
@Autowired
private OrderService orderService;
public void test() {
System.out.println(orderService);
}
}
public class Test {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
}
}
在userService.test();处打上断点
可以看到得到的userService 是一个代理对象,同时orderService没有值,也就是说虽然在UserService类中的OrderService属性添加了@Autowired 但是并没有赋值。但是最后public void test() {System.out.println(orderService);}
输出结果是有值的
为什么呢?再看下面这个图
也就是是说userService的代理对象中的OrderService是没有值的,但是一开始生成的userService对象(初始化后之前)的OrderService是有值的。也就是说初始化后生成代理类没有进行OrderService的依赖注入。
因此上面代理的代码应该要改成
class UserServiceProxy extends UserService{
UserService target
public void test(){
//执行切面的方法
TODO
//执行自己的方法
target.test();
}
}
spring在生成代理对象UserServiceProxy的时候会把之前普通的UserService传给target,让后target调用test(),实际上调用test方法的是UserService 而不是 UserServiceProxy,执行切面方法的则是UserServiceProxy。
然后再回到前面提到的spring是如何判断这个类是不是需要生成代理类?
- 找出所有的切面Bean(也就是标注有@Aspect的类)
- 遍历切面中的每个方法,看是否写了@Before、@After等注解
- 如果写了,则判断所对应的Pointcut是否和当前Bean对象的类是否匹配
- 如果匹配则表示当前Bean对象有匹配的的Pointcut,表示需要进行AOP
总结
- 在初始化后spring先判断那些bean需要生成代理对象
通过扫描所有类找出有@Aspect的类作为切面bean
遍历每个切面bean中每个方法,把标注有@Before、@After等注解等注解的方法,和对应的切点封装的一个map中
每次创建在初始化后通过这个map看能否找到对应代理信息,如果有则这个类需要生成代理类 - 通过cglib给需要代理的类创建代理对象
通过创建一个proxy继承于该类,重写该类被代理的方法。
把被代理的类注入到proxy的target中。这个target就是初始化前创建的类
通过proxy调用代理方法,在代理方法中执行切面方法,然后通过target调用具体的方法 - 最后得到的最终bean就是代理对象
因此我们在使用AOP的时候,为什么失效,就要考虑是不是代理对象在调用AOP,如果不是自然不会生效
spring事务大致流程
事务流程和aop相似。当我们在某个方法上加了@Transactional注解后,就表示该方法在调用时会开启Spring事 务,而这个方法所在的类所对应的Bean对象会是该类的代理对象。
Spring事务的代理对象执行某个方法时的步骤:
- 判断当前执行的方法是否存在@Transactional注解
- 如果存在,则利用事务管理器(TransactionMananger)新建一个数据库连接
- 修改数据库连接的autocommit为false
- 执行target.test(),执行程序员所写的业务逻辑代码,也就是执行sql
- 执行完了之后如果没有出现异常,则提交,否则回滚 Spring事务是否会失效的判断标准:某个加了@Transactional注解的方法
被调用时,要判断到底是不是直接被代理对象调用的,如果是则事务会生效,如果不是则失效。