Spring 基础回顾
入门使用
java
// 创建一个Spring容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
// 从容器中获取Bean
UserService userService = (UserService) context.getBean("userService");
// 调用userService的test方法
userService.test();
以上三行代码为 Spring 学习过程中的入门代码。大体来说,上面代码的意思是:
- 构造一个 Spring 容器,用于管理各个 bean,一旦容器被创建,就可以从容器中获取这 bean,并且利用它们来构建应用程序。
- 从构建的 Spring 容器中,获取名称为 userService 的 bean。
- 通过从 Spring 中获取的对象,调用对象的方法。
下面介绍另一种初始化 Spring 容器的方式,代码如下:
java
// 构造Spring容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
该方法与上一种方法初始化 Spring 容器的使用方式一样,只不过创建时,传入的参数不同,一个是传入 xml 文件,另一个是传入class文件。
问题思考
通过以上三行代码,可以总结出三个问题
- Spring 容器是怎么初始化的?
- 通过 Spring 容器获取的对象,和直接 new 出来的对象有什么区别呢?
- 使用 Spring 中获取的对象调用该方法和通过 new 出来的对象调用该方法,有什么区别吗?
后续通过对 Spring 中 bean 的初始化流程,对以上问题进行解答。
Spring 中对象的创建
在 Spring 中,对象的创建大部分都是在创建 Spring 容器的时候进行创建的,也就是该行代码所运行的时机。
java
// 构造Spring容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
- 首先看下
AppConfig.class
文件中的内容:
java
@ComponentScan("com.lingxi")
public class AppConfig {
/**
往Spring中注册对象,在此处Spring中有两个OrderService对象,
一个名为orderService,一个名为orderService1
第一个对象是Spring进行包扫描时,自动注册的对象,第二个是该方法注册的对象。
*/
@Bean
public OrderService orderService1() {
return new OrderService();
}
}
在该文件配置中,主要代码就是@ComponentScan("com.lingxi")
,它告诉 Spring 要去扫描哪个包下的文件,去获取扫描路径。该注解的 ==value== 参数是个数组类型,可以指定多个包。
- 根据从注解中获取的扫描路径,进行包扫码 Spring 根据从第一步获取到的扫描路径,对该路径下的所有类进行扫描,若某个类中含有
@Component
、@Service
、@mapper
等注解,Spring 会将它们记录下来,放到一个Map中,该Map的结构为 Map<String, BeanDefinition>,key 为对象名称,val 可以理解为该类的信息。 - 当调用
getBean()
方法时,会从该 Map 中获取对象,并返回。
Bean 的创建过程
上图为 Spring 创建 bean 的大体流程。
对象创建:利用该类的构造方法,去实例化对象。
依赖注入:创建完对象之后,会判断该对象是否有需要注入的属性值,若存在需要注入的属性值,则对这些属性进行注入。
Aware回调:判断该对象是否实现了Aware回调接口,若实现了相应接口,那Spring就会调用这些方法并传入相应的参数。
初始化前 :判断该对象是否有被@PostConstruct
注解修饰的方法,若存在,则去执行该方法。
初始化 :判断该对象是否实现了InitializingBean接口,若实现了该接口,则去执行afterPropertiesSet
方法。
初始化后 :判断该对象是否需要进行 AOP,若需要进行 AOP,则会生成该对象的代理对象,放进beanDefinitionMap
中;若不需要进行AOP,则生成原始对象,放进该beanDefinitionMap
中。
构造方法的推断
在 Spring 创建对象的过程中,具体选择哪一个构造方法去创建对象,也是有规律的。
- 当构造方法只有一个时,不论该构造方法是有参构造还是无参构造,都会选择该构造方法
- 当构造方法有多个时
- 若存在无参构造,则使用无参构造去创建对象。
- 若不存在无参构造,则会报错 ,若要指定默认构造方法,则需要在构造方法上添加
@Autowired
注解,指定默认构造方法。
验证如下: 验证1: 首先验证只有一个无参构造方法的情况:
java
@Component
public class OrderService {
// 无参构造
public OrderService() {
System.out.println("无参构造 ====》orderService init ...");
}
public void test() {
System.out.println("orderService test ...");
}
}
输出结果:
然后验证只有一个有参构造的情况
java
@Component
public class OrderService {
// 有参构造
public OrderService(UserService userService) {
System.out.println("有参构造 ====》orderService init ...");
}
public void test() {
System.out.println("orderService test ...");
}
}
输出结果如下:
即当对象只有一个构造方法时,不论该方法为有参构造还是无参构造,Spring 都会使用这个构造方法去构造对象。
验证2:存在多个构造函数,且存在一个无参构造
java
@Component
public class OrderService {
public OrderService() {
System.out.println("无参构造 ====》orderService init ...");
}
public OrderService(UserService userService) {
System.out.println("有参构造 ====》orderService init ...");
}
public void test() {
System.out.println("orderService test ...");
}
}
当存在多个构造函数,且存在一个无参构造,会使用无参构造去创建对象。
验证3:存在多个构造函数,且没有无参构造。
java
@Component
public class OrderService {
public OrderService(UserService userService) {
System.out.println("有参构造1 ====》orderService init ...");
}
public OrderService(UserService userService, UserService userService1) {
System.out.println("有参构造2 ====》orderService init ...");
}
public void test() {
System.out.println("orderService test ...");
}
}
这里报错 No default constructor found; nested exception is java.lang.NoSuchMethodException
说明在创建对象时,未找到默认构造方法,此时需要告诉 Spring 使用哪一个构造方法进行对象的创建。 指定默认构造方法:
java
@Component
public class OrderService {
// 指定该构造方法为默认构造方法,也可指定另一个
@Autowired
public OrderService(UserService userService) {
System.out.println("有参构造1 ====》orderService init ...");
}
public OrderService(UserService userService, UserService userService1) {
System.out.println("有参构造2 ====》orderService init ...");
}
public void test() {
System.out.println("orderService test ...");
}
}
当存在多个构造方法时,且不存在无参构造,Spring 会抛出异常,找不到默认构造方法,需在想要的构造方法上,添加 @Autowired 注解,指定构造方法。
注意: Spring会根据入参的类型和入参的名字去Spring中找Bean对象: 1.先根据入参类型找,如果只找到一个,那就直接用来作为入参 2.如果根据类型找到多个,则再根据入参名字来确定唯一一个 3.最终如果没有找到,则会报错,无法创建当前Bean对象
AOP 的大体流程
AOP 是 Spring 的一个重要模块,其核心就是动态代理。 在 Spring 创建 Bean 的过程中,会判断该对象是否需要进行 AOP,若需要进行 AOP,则会进行动态代理,产生代理对象。
如何判断对象是否需要进行动态代理
- 找出所有的切面 Bean, 即被
@Aspect
注解标记的类。 - 遍历切面中的每个方法,看是否写了
@Before
、@After
等注解。 - 如果写了,则判断所对应的Pointcut(切点表达式)是否和当前 Bean 对象的类是否匹配。
- 如果匹配则表示当前Bean对象有匹配的的Pointcut,表示需要进行 AOP,则需要产生代理对象。
AOP 的大体流程
- 通过 cglib 动态代理,由被代理类 UserService, 生成代理类 UserServiceProxy
- 代理类中重写了父类的方法,比如 UserService 中的
test()
方法 - 代理类中有一个 target 属性,该属性的值为被代理对象(也就是由 Spring 创建的普通对象)
- 代理类中的
test()
方法被执行时的逻辑如下: a. 执行切面逻辑(@Before) b. 调用target.test()
即通过原始对象调用test()
方法时,不会获得 AOP 增强的功能,只有原始功能;而通过代理对象去调用test()
方法时,能够根据在 Spring 中配置的相应方法,对原始功能进行增强。