入门使用的Spring代码:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();
思考问题:
1、第一行代码都做了哪些事情?
2、第二行代码getBean()
如何实现的?返回的UserService
对象和new创建的对象有什么区别?
在新版的Spring MVC
和Spring Boot
的底层主要用的都是AnnotationConfigApplicationContext
:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
注意:AppConfig.class
和spring.xml
的本质是一样的,都表示Spring的配置,类比两种写法:
spring.xml中的内容为:
AppConfig中的内容为:
@ComponentScan("com.gax")
public class AppConfig {
@Bean
public UserService userService(){
return new UserService();
}
}
目前spring.xml
这种方式已经很少见了;更多的是使用Spring MVC
或者Spring Boot
,这两种也是基于spring.xml
这种方式,在内部创建一个ApplicationContext
,但是也有区别:
1、Spring MVC
创建的是XmlWebApplicationContext
,和ClassPathXmlApplicationContext
类似,都是基于XML配置的
2、Spring Boot
创建的是AnnotationConfigApplicationContext
后续重点学习研究的是AnnotationConfigApplicationContext
这一种
AnnotationConfigApplicationContext
可以简单理解为创建Java对象,当调用context.getBean("userService")
方法时创建一个对象。那么问题来了,方法内部如何知道"userService"
对应的是UserService
类呢?
可以分析出来AnnotationConfigApplicationContext
的构造方法做的一些事情:
1、解析AppConfig.class
,得到扫描路径
2、遍历扫描路径下的所有Java类,如果类上面有@Component、@Service等注解,那么Spring就会把这个类记录下来,存放在一个Map里,比如像Map
,Spring源码中叫BeanDefinitionMap
3、Spring根据规则生成当前类的beanName
,作为存入Map时的key,当前类作为存入Map时的value(Spring中的beanName
默认是类名首字母小写)
Spring到底是如何来创建一个Bean的呢,也就是Bean创建的生命周期。大致流程如下:
1、利用该类的构造方法来实例化得到一个对象(有多个构造方法时,Spring会做出选择,推断构造方法)
2、得到一个对象后,Spring会判断该对象中是否存在被@Autowired注解了的属性,把这些属性找出来并由Spring进行赋值,依赖注入
3、依赖注入后,Spring会判断该对象是否实现了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前对象必须实现该接口中所定义的setBeanName()、setBeanClassLoader()、 setBeanFactory()方法,那Spring就会调用这些方法并传入相应的参数,Aware回调
4、Aware回调后,Spring会判断该对象中是否存在某个方法被@PostConstruct注解了,如果存在,Spring会调用当前对象的此方法,初始化前
5、Spring会判断该对象是否实现了InitializingBean接口,如果实现了,就表示当前对象必须实现该接口中的afterPropertiesSet()方法,那Spring就会调用当前对象中的afterPropertiesSet()方法,初始化
6、Spring会判断当前对象需不需要进行AOP,如果不需要那么Bean就创建完了,如果需要进行AOP,则会进行动态代理并生成一个代理对象做为Bean,初始化后
注意:不用AOP时,Bean就是构造方法创建的普通对象;需要AOP时,Bean是代理类实例化的对象
Bean对象创建完成后,如果是单例Bean就放在Map里,下次可以直接从Map中直接获取(Spring源码中Map就是单例池);如果是原型Bean就不用放在Map里,下次重新创建
选择构造方法的判断逻辑:
1、只有一个构造方法,无论是否有参,Spring都会选择这个
2、有多个构造方法时,Spring会选择无参的构造方法(无参本身就是一种默认)
没有无参构造方法,且构造方法上面都没有@Autowired注解时报错,Spring不知道选择哪一个
没有无参构造方法,但是某个构造方法上面添加了@Autowired注解,Spring选择这一个构造方法
注意:Spring选择有参构造方法时,Spring会先根据参数类型,类型相同再根据参数名字去找Bean对象,类型和名字都找不到就报错
推断构造方法:确定用哪个构造方法,确定入参的Bean对象
判断当前Bean对象需不需要进行AOP:
1、找出所有的切面Bean
2、遍历切面中的每个方法,看是否写了@Before、@After等注解
3、如果写了,则判断所对应的Pointcut是否和当前Bean对象的类匹配
4、如果匹配则表示当前Bean对象有匹配的的Pointcut,需要进行AOP
利用cglib进行AOP的大致流程:(cglib说白了就是父子类)
1、生成代理类XXServiceProxy,代理类继承XXService
2、代理类中重写了父类的方法
3、代理类中有一个target属性,该属性的值为被代理对象
(这里的被代理对象:是推断构造方法实例化的对象,完成了依赖注入、初始化等)
4、代理类中的方法被执行时,先执行before的切面逻辑,再执行target.目标方法
注意:XXServiceProxy是代理类,它实例化出来的是代理对象;target是被代理对象
思考:代理对象的属性是null,没有进行依赖注入,为什么?
没有必要,大部分情况用不到,需要用的话也可以通过target获取
在某个方法上加了@Transactional注解后,就表示该方法在调用时会开启Spring事务,而这个方法所在的类所对应的Bean对象会是该类的代理对象。
1、判断当前执行的方法是否存在@Transactional注解
2、如果存在,则利用事务管理器(TransactionMananger)新建一个数据库连接
3、修改数据库连接的autocommit为false
4、执行target.目标方法,业务逻辑的sql
5、执行完之后没有异常则提交,否则回滚
事务失效的场景分析:
1、调用目标方法的是否为代理类,使用代理类调用时才有切面的逻辑
2、是否使用的同一个事务管理器(或者说是否同一个数据库连接)