概述
本篇旨在整体的梳理一下Spring的核心功能,让我们对Spring的整体印象更加具体深刻,为接下来的Spring学习打下基础。
本片主体内容如下:
- Bean的生命周期
- 依赖注入的实现
- Bean初始化原理
- 推断构造方法原理
- AOP的实现
这里要说明一下,我们这里说到的Spring,一般指的是Spring Framework。它和我们常用的Spring MVC和Spring Boot的关系可以这么理解:Spring是一个全栈式的Java开发框架,提供了众多功能;Spring MVC是Spring框架中的一个模块,专门用于开发Web应用程序;而Spring Boot则是基于Spring框架的一个快速开发Web应用程序的工具,它实现了自动配置并提供了一种简单的方法来启动和运行Spring应用程序。三者之间的关系可以概括为:Spring MVC和Spring Boot都是基于Spring框架的衍生产品,它们各自提供了不同的功能以满足不同的开发需求。
当然了,除了Spring MVC 和Spring Boot以外,Spring还包含很多其他的内容,比如Spring Cloud、Spring Data 等等。有兴趣的可以到其官网了解。
Bean的生命周期
在正式开始前,我觉得有必要先说一下什么是Bean。我们平时一提到Spring,不可避免的会提到Bean这个词。那到底什么时Bean?我们应该怎么理解Bean?这里说一下我对Bean的理解:
在Spring框架中,Bean具体指的是由Spring IoC容器实例化、组装和管理的对象。这些对象构成了应用程序的骨干,是Spring框架中核心概念之一。Bean并非由程序员直接创建,而是在运行时,由Spring容器根据配置信息自动创建和管理的。
理解Bean时,可以将其视为Spring IoC容器中的一个组件,该容器负责Bean的创建、初始化、组装以及销毁等生命周期管理。通过Spring的配置文件或注解,可以定义Bean的各种属性,如依赖关系、初始化方法、销毁方法等。Spring容器会根据这些配置信息,自动完成Bean的创建和依赖注入,从而实现应用程序的组件化开发和松耦合。
像上边这样描述下来可能还是很笼统,那先让我们先想一下在用Spring时,我们最常用的一些功能都有哪些?比如说依赖注入、AOP和事务管理等等。其实这些功能中都离不开Bean的使用。它在这几个功能中的大概作用是这样的:
- 依赖注入:Spring容器通过配置文件或注解的方式,将Bean注入到其他Bean中,实现对象之间的解耦。这使得应用程序更加灵活和可维护,因为Bean之间的依赖关系不再硬编码在代码中,而是由Spring容器在运行时动态管理。
- AOP:通过配置Bean,Spring框架可以实现面向切面编程(AOP),将通用的横切关注点(如事务管理、日志记录等)与业务逻辑分离。这有助于减少代码冗余和提高代码的可维护性。
- 事务管理:Spring框架提供了对事务的支持,通过配置Bean可以管理事务的提交、回滚等操作,确保数据的一致性和完整性。
总之,Bean在Spring框架中扮演着核心角色,它使得应用程序的开发更加灵活、可维护和可扩展。通过理解Bean的概念和作用,可以更好地掌握和使用Spring框架进行应用程序的开发。
提出疑问
我始终觉得学习一个知识或者技能,不能盲目的“为了学而学”。我觉得带着问题学习会效果更好一些,所以接下来我会抛出几个简单问题。
首先,我这里为了演示创建了一个很简单的项目,结构如下:
在这个示例demo中,目前只是简单的创建了两个文件一个是TestService,为了作为Bean来展示Spring的一些相关功能,另一个是TestSpringMain文件,其中只有一个main方法,为了运行演示。最下面的依赖包只有如图所示6个Spring的相关依赖,是通过在pom文件中引入spring-context来引入的。pom文件如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring_demo</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Archetype - spring_demo</name>
<url>http://maven.apache.org</url>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.10</version>
</dependency>
</dependencies>
</project>
当前TestService和TestSpringMain分别的内容如下:
TestService:
public class TestService {
public void test(){
System.out.println("test");
}
}
TestSpringMain文件如下:
public class TestSpringMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
TestService testService = (TestService) context.getBean("testService");
testService.test();
}
}
首先让我们看一下在TestSpringMain这个文件中的这三行代码:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
TestService testService = (TestService) context.getBean("testService");
testService.test();
相信学习过Spring的朋友对这几行代码肯定不会陌生,这个相当于Spring学习中的Hello word。那让我们来思考一下这三行代码都做了什么?
- 第一行代码构造了一个ClassPathXmlApplicationContext对象,这个对象是什么?构造时传入的”spring.xml“又是起到什么作用?在构造这个对象的过程中都做了什么?
- 第二行代码通过getBean方法获得了一个TestService对象。那么这个getBean又是如何创建出TestService对象的?如何将传入的参数”testService“和TestService对应上的?返回的TestService对象和我们自己直接new的TestService对象有区别吗?
- 第三行代码很明显就是调用testService的test方法。
Spring中是如何创建一个Bean对象的?
上面我们针对Spring入门的三行代码提出了一些疑问,现在让我们试着猜想一下它的逻辑。
这里我们从第三行开始往回推。首先如果test()方法可以正确执行,那么在第二行调用getBean()时,我们必然是得到了一个TestService类型的实例。那么我们可以知道getBean()方法可以通过入参"testService"得到一个对应的实体类,这里也就是Bean对象。
那是不是可以猜测在第一行创建ClassPathXmlApplicationContext对象时,可能存在一个Map<String,Class>这样的数据结构。然后通过某些操作获得到beanName(比如这里的"testService")作为Key。而当前Bean的类型(这里的TestService)作为值存入这个Map中(事实上Spring内部就是这么做的,这个Map就是BeanDifinitionMap)。
注意,这个Map中存的并不是Bean的实例。而真正的实例时通过调用getBean()时才创建的。想一下为什么不是直接将实例创建好存入Map中?这样不是直接就能获取了么?
这里说一下我的猜想,如果直接创建好所有的Bean并且放入Map中,那么这个时间会变得更长。同时对存储空间消耗的也更大。最主要的是,在这时并不确定这些Bean是否都会被调用。而在getBean()时去创建(其实这里也不一定会创建,后续再详细研究),即需要用了再去创建。明显要更灵活可控。后面要学习的@Scope注解可以指定Bean的作用域,正是于此有关,可以影响Bean实列的创建时间。
好的,我们现在有了一个Map<String,Class>。这样的话,当我调用getBean的时候,就可以通过beanName而获取到对应的Class,然后可以调用这个Class的构造方法,从而创建对应的Bean对象。
这就是Spring创建一个Bean的大概逻辑,当然其中还有很多很多的细节。我们后续会有讲解。
这里要说明一下,其实用ClassPathXmlApplicationContext已经过时了,在新版的Spring MVC和Spring Boot的底层主要用的都是AnnotationConfigApplicationContext,所以呢我们的main方法现在编程了这样:
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
TestService testService = (TestService) context.getBean("testService");
testService.test();
}
可以看到AnnotationConfigApplicationContext的用法和ClassPathXmlApplicationContext是非常类似的,只不过需要传入的是一个class,而不是一个xml文件。
而AppConfig.class和spring.xml一样,表示Spring的配置,比如可以指定扫描路径,可以直接定义Bean,比如我上面的例子中:
spring.xml如下:
<context:component-scan base-package="com.bcf"/>
<bean id="testService" class="com.bcf.service.TestService"></bean>
AppConfig内容如下:
@ComponentScan("com.bcf")
public class AppConfig {
@Bean
public TestService testService(){
return new TestService();
}
}
所以spring.xml和AppConfig.class本质上是一样的。
目前,我们大多数是使用Spring MVC,或者Spring Boot,但它们的底层原理都是上边这种,都需要在内部去创建一个ApplicationContext,其区别在于:
- Spring MVC创建的是XmlWebApplicationContext,和ClassPathXmlApplicationContext类似,是基于XML配置的
- Spring Boot创建的是AnnotationConfigApplicationContext ,基于注解的
就目前而言AnnotationConfigApplicationContext是比重较大的,而且AnnotationConfigApplicationContext和ClassPathXmlApplicationContext大部分底层都一样,所以后续我们就着重探讨AnnotationConfigApplicationContext的底层实现,对于ClassPathXmlApplicationContext,大家可以自行看一下源码。
Bean的创建过程
上面我们基于入门示例的三行代码进行了猜想,同时也基于猜想笼统的简述了一下大概的底层原理。下面我们说明一下Spring中Bean对象的创建过程。
利用该类的构造方法来实例化一个类
我们知道,java中要实例化一个类,必然是通过调用构造方法而得到的。Spring也不例外。现在我们在上面的TestService加入如下代码:
public TestService() {
System.out.println("TestServiced的构造方法");
}
就是这里在构造方法中打印一句话然后再运行一下代码会得到如下结果:
我们知道,每一个java类都有一个默认的无参构造方法,那如果我们自定义一个有参构造方法后,Spring会如何创建呢?看下面的例子:
首先呢,我们为了方便演示,这里引入@Component注解,现在我们的AppConfig如下:
@ComponentScan("com.bcf")
public class AppConfig {
}
就是只定义了一个扫描路径,而类是否需要被创建成一个Bean则用@Component来标记。就是在TestService类上加上@Component注解即可。
这里我们再创建一个UserService类,用来演示后续的功能,代码如下:
@Component
public class UserService {
public void userTest(){
System.out.println("UserServiceTest");
}
}
不难看出,这个UserService类也是一个Bean,因为加了@Componet注解。
好的,准备工作已经就绪,接下来我们改造一下TestService的构造方法,更改后如下:
public TestService(UserService userService) {
userService.userTest();
System.out.println("TestServiced的构造方法" + userService);
}
即当前的构造方法为有参的,参数类型为UserService,并且调用其userTest()方法。目前该类只有这一个构造方法。此时运行代码,我们会得到如下结果:
可以看到,这里定义了构造方法的参数为UserService类型(注意,这个类被声明成了一个Bean)。不难看出在启动过程中,也就是创建Bean的过程中,Spring正确的将UserService参数传给了这个方法,可以正常的调用其userTest()方法。那么其中的底层原理是什么样的呢?
先别急,我们接着看下面的实验:
这里,我们将UserService类的@Component注解去掉,即它现在并不是一个Bean,在Spring创建时并不会将其实例化成Bean。我们再次运行代码,结构如下:
此时会发现其结果是报错,大概就是找不到UserService类型的Bean。即找不到这个参数。
好的,那我们现在就知道了。若要给Bean自定义一个有参数的构造方法,那么这个参数必须是一个Bean。其实很好理解,因为Spring发现当前的类只有一个需要传参的构造方法,那当要实例化这个类的时候,Spring只能是从容器中(Spring容器)查找这个类型的参数,那如果这个参数并不是个Bean,即Spring容器中不存在这个类型的参数,那么Spring就报错了。(这里先这么理解,当这部分的全部实验结束后,我们再一起总结)
经过上面的示例我们知道了,有参构造方法中的参数必须是个Bean对象。那么Spring是如何从容器中找到这个对象的呢?让我们用代码说话,进行如下实验:
先把UserService的@Component注解加回来,将其声明成Bean。此时代码又可以正常运行了,并且我们知道在TestService的构造方法中可以通过userService.userTest()来调用该方法。证明UserService这个参数是有值的。
让我们先来猜想一下,当Spring生成Bean的时候,发现该类构造方法要传一个UserService类型的参数,它内部原理是依据什么查到对应的Bean的呢?这里我们有两个东西可供使用一个是类型,即UserService ,但不要忽略还有一个参数名称,即这里的userService。在最开始的时候,我们说Spring开始会创建一个Map来管理Bean的名称和类型(其实还有其他信息,这个BeanDifinitionMap是个很重要的核心功能,后续会详细讲解)。那这里可以通过这里的beanName(userService)查到对应的类型(UserService)么?答案是不一定,因为可能UserService这个类对应的Bean名称并不叫”userService“,而且当前代码中,我们将参数改成(UserSerVice a)这样,代码依然可以正常运行。那证明我们这个查找逻辑并不是Spring的底层原理。
那我们再换种想法,是不是可以用类型(UserService)去Map中查找到指定对象,然后得到相应类型的Bean对象,作为参数传入这里的构造方法?这样的话,因为我是用类型去查的的,至少类型不会错,那查出来的Bean就肯定能用。这个猜想貌似行得通。
好的,那我们再想一下,有没有这种情况,就是我用类型去查找的时候,发现有多个Bean对象都是这个UserService的类型?那这时候怎么办?让我们再继续实验:
上面的例子结合我们的猜想,我们知道,在我们系统中目前只有一个UserService类型对应的Bean对象,所以上边代码正常执行。接下来再构造几个UserService类型对应的Bean,只要再AppConfig中加入如下代码:
@Bean
public UserService userService1(){
return new UserService();
}
@Bean
public UserService userService2(){
return new UserService();
}
即在这里声明了两个类型为UserService的Bean,同时我们去掉UserService中的@Component注解。则目前系统中应该是有两个UserService类型对应的Bean。为了验证是这样,我们在main方法中试验一下,更改一下方法,如下:
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
// TestService testService = (TestService) context.getBean("testService");
// testService.test();
System.out.println(context.getBean("userService1"));
System.out.println(context.getBean("userService2"));
}
我们先注释掉TestService中的有参构造方法,然后运行代码,然后打印结果如下:
证明这里确实是两个对象。好的,现在再把TestService中的有参构造方法放开,main方法改回原样。现在运行,看一下结果:
好吧,报错了,注意这里:No qualifying bean of type ‘com.bcf.service.UserService’ available: expected single matching bean but found 2: userService1,userService2。** 大概意思是:Spring想找一个类型为com.bcf.service.UserService的bean时遇到了问题。它期望找到一个匹配的bean,但是实际上找到了两个:userService1和userService2。注意,现在构造方法的参数是这样的(UserService userService)。**我们现在将UserService中的@Component注解再加上,即现在Spring容器会有三个UserService类型对应的Bean。分别为:userService、userService1和userService2。再次运行,查看结果:
现在有可以正常运行不报错了。想一下这是为什么?其实这里就是用到了参数的名称(userService)。
现在通过上边的示例,总结一下。我们可以得出结果:
Spring会根据构造方法参数的类型和名字去Spring容器中找Bean对象:
- 先根据入参类型找,如果只找到一个,那就直接用来作为入参,不需要使用参数名称
- 如果根据类型找到多个,则再根据入参名字来确定唯一一个
- 最终如果没有找到或找到多个,则会报错,无法创建当前Bean对象
通过上面我们知道了当调用有参构造方法的时候,Spring的底层原理。那我们再想一下,如果我这个TestService类中存在多个构造方法,会是什么样?Spring将会如何处理?好的我们接着做下面的实验:
为了不影响实验,先把AppConfig中注册的userService1和userService2删除掉。然后我们在TestService中加入如下代码,再声明一个无参的构造方法,即现在存在两个构造方法,如下:
public TestService() {
System.out.println("TestService无参构造方法");
}
public TestService(UserService userService) {
userService.userTest();
System.out.println("TestServiced的构造方法" + userService);
}
想一下,现在执行代码,会打印哪个结果呢?我们一起来看执行结果:
我们看到,这里调用的是无参的构造方法。那我们就可以得出结论:当一个类中有多个构造方法时,其中包含无参的构造方法,那么Spring会在创建Bean的过程中,调用该无参构造方法(可以认为是默认的构造方法)。
好的,现在我们又知道了一个特性,接下来我们接着实验:
想一下,如果一个类有多个有参构造方法,那Spring如何确认调用哪个呢?现在将TestService改成如下:
public TestService(UserService userService) {
// userService.userTest();
// System.out.println("TestServiced的构造方法" + userService);
System.out.println(1);
}
public TestService(UserService userService, UserService userService1) {
System.out.println(2);
}
现在我有两个有参的构造方法,一个打印1,另一个打印2,这里运行的话,打印结果会是什么样的呢?我们一起来看一下:
没错,这里会报错了,大概的意思是,Spring在初始化这个Bean的时候,找不到一个默认的构造方法。就是说这种情况下,Spring不知道要执行哪个构造方法。也就是说,当Spring发现一个类中有多个构造方法并且都是有参的,那么Spring由于不知道需要执行哪个,就会报错。
那有没有办法在这种情况下让Spring知道需要执行哪个构造方法而不报错呢?答案是有的,可以使用**@Autowired**注解,比如这里将代码改成如下:
public TestService(UserService userService) {
// userService.userTest();
// System.out.println("TestServiced的构造方法" + userService);
System.out.println(1);
}
@Autowired
public TestService(UserService userService, UserService userService1) {
System.out.println(2);
}
此时运行,结果如下:
这里就可以正常运行,并打印出2了。
总结一下:
- 如果一个类只有一个构造方法,那么就用这个构造方法
- 如果一个类存在多个构造方法,Spring不知道如何选择,就会看是否有无参的构造方法,有的话就执行,没有即报错。
- 如果某个构造方法上加了@Autowired注解,那就表示程序员告诉Spring就用这个加了注解的方法,那Spring就会用这个加了@Autowired注解构造方法
好了,上边啰嗦了这么多,详细的讲了在Spring创建Bean的过程中,通过调用该类构造方法实例化该类时,是如何确定使用哪个构造方法,以及如何查找构造方法中参数的,这个过程就是推断构造方法。
而至此,也说完了Bean生命周期的第一部分:”利用该类的构造方法实例化一个类“
依赖注入
当Spring通过调用一个类的构造方法得到一个对象后,这个对象其实和我们平时new出来的对象是一样的。目前这个对象当然还不是一个Bean。我们再修改一下TestService类,更改后如下:
@Component
public class TestService {
@Autowired
private UserService userService;
public void test(){
userService.userTest();
System.out.println("test");
}
}
这里我们声明一个UserService 类型的属性,并且使用@Autowired注解修饰,我们知道这里使用的就是依赖注入。我们如果直接new对象的方式,调用的话,肯定是报错的,因为正常的对象(非Bean)不会执行依赖注入,比如将main方法改为如下然后运行:
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
// TestService testService = (TestService) context.getBean("testService");
TestService testService = new TestService();
testService.test();
}
打印的结果是:
因为没有依赖注入,所以testService实例中的userService属性为Null,当使用其调用userTest方法自然也就报错了。我们将main方法恢复成原来的样子如下:
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
TestService testService = (TestService) context.getBean("testService");
testService.test();
}
此时通过Bean来调用,则程序正常运行:
这里我们知道,Spring创建Bean时,会执行依赖注入的逻辑。大概逻辑为:当通过构造方法得到一个类的实例后,会去遍历其属性,若属性被@Autowired修饰,则从容器中查找到对应的Bean进行赋值。查找的过程类似于推断构造方法时的逻辑,即先根据类型查找,然后再用属性名称确认。
Aware回调
当做完依赖注入后,Spring会判断该对象是否实现了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前对象必须实现该接口中所定义的setBeanName()、setBeanClassLoader()、setBeanFactory()方法,那Spring就会调用这些方法并传入相应的参数,这个过程成为”Aware回调“。
我们以BeanNameAware接口为例,进行如下操作:
我们让TestService类实现BeanNameAware接口,并实现setBeanName()方法修改后如下:
public class TestService implements BeanNameAware {
@Autowired
private UserService userService;
private String testBeanName;
public void test(){
// userService.userTest();
// System.out.println("test");
System.out.println("BeanName===>" + this.testBeanName);
}
public void setBeanName(String name) {
this.testBeanName = name;
}
}
此时运行main方法,可以看到:
这里已经获取到了beanName 。
初始化前
在Aware回调后,Spring会判断当前类中是否存在方法使用了@PostConstruct注解。若存在的则执行该方法(当然了Spring真正的初始化前不止判断@PostConstract注解)。让我们来用代码操作一下:
我们再次更改TestService代买如下:
@Component
public class TestService implements BeanNameAware {
@Autowired
private UserService userService;
private String testBeanName;
public void test(){
// userService.userTest();
// System.out.println("test");
System.out.println("BeanName===>" + this.testBeanName);
}
@PostConstruct
public void a(){
System.out.println("初始化前");
}
public void setBeanName(String name) {
this.testBeanName = name;
}
}
就是加了一个a()方法,并用@PostConstract注解。此时运行程序打印如下:
看到a()方法已经执行了。这里就是初始化前这个过程做的。
初始化
初始化前做完之后,Spring会判断该对象是否实现了InitializingBean接口,如果实现了,就表示当前对象必须实现该接口中的afterPropertiesSet()方法,那Spring就会调用当前对象中的afterPropertiesSet()方法,这就是初始化。
其实早期的Spring版本并未划分初始化前,初始化和初始化后,当时只有一个初始化过程,所以这里你会发现初始化过程实现的功能和初始化前可能差不多,我们来看一下代码:
现在,我们让TestService实现InitializingBean接口,并实现其afterPropertiesSet()方法(看这个名字就知道了,翻译过来是在属性赋值之后),代码如下:
@Component
public class TestService implements BeanNameAware, InitializingBean {
@Autowired
private UserService userService;
private String testBeanName;
public void test(){
// userService.userTest();
// System.out.println("test");
System.out.println("BeanName===>" + this.testBeanName);
}
@PostConstruct
public void a(){
System.out.println("初始化前");
}
public void setBeanName(String name) {
this.testBeanName = name;
}
public void afterPropertiesSet() throws Exception {
System.out.println("初始化");
}
}
执行代码,结果如下:
其实这里就可以在初始化前和初始化过程中写一些自定义的逻辑处理。
初始化后(AOP)
在最后,Spring会判断当前对象是否需要AOP,如果不需要那么Bean就创建完了,若需要,则会进行动态代理并生成一个代理对象做为Bean。这个过程就是初始化后。
来看一下代码:
首先在pom文件中加入aop需要的依赖,如下:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
执行maven clean install,确保依赖正确加载。然后我们新增一个文件夹于service同级,在其中添加一个类为TestAspect,代买如下:
@Aspect
@Component
public class TestAspect {
@Before("execution(public void com.bcf.service.TestService.test())")
public void testBefore(JoinPoint joinPoint){
System.out.println("初始化后-->AOP切面");
}
}
即使用**@Aspect定义一个切面类,然后使用@Before定义一个切面方法,其切的是TestService的test()方法。
最后我们要修改一下AppConfig文件,增加@EnableAspectJAutoProxy注解,**使其支持AOP,如下:
@ComponentScan("com.bcf")
@EnableAspectJAutoProxy
public class AppConfig {
}
好的,主备就绪,执行代码,打印如下:
这里可以看到,程序执行了切面逻辑,即初始化后过程正确执行了。
这里要说明一下AOP的大概实现逻辑:
AOP就是进行动态代理,在创建一个Bean的过程中,Spring在最后一步会去判断当前正在创建的这个Bean是不是需要进行AOP,如果需要则会进行动态代理。
如何判断当前Bean对象需不需要进行AOP:
- 找出所有的切面Bean
- 遍历切面中的每个方法,看是否写了@Before、@After等注解
- 如果写了,则判断所对应的Pointcut是否和当前Bean对象的类是否匹配
- 如果匹配则表示当前Bean对象有匹配的的Pointcut,表示需要进行AOP
注意:
通过最后一步,我们可以发现,当Spring根据UserService类来创建一个Bean时:
- 如果不用进行AOP,那么Bean就是UserService类的构造方法所得到的对象。
- 如果需要进行AOP,那么Bean就是UserService的代理类所实例化得到的对象,而不是UserService本身所得到的对象。
AOP这部分逻辑其实还是有些复杂的,这里靠文字很难表达的清楚,看情况后面可能需要搞一下视频演示讲解。
Bean创建后
Bean对象创建出来后:
- 如果当前Bean是单例Bean,那么会把该Bean对象存入一个Map<String, Object>,Map的key为beanName,value为Bean对象。这样下次getBean时就可以直接从Map中拿到对应的Bean对象了。(实际上,在Spring源码中,这个Map就是单例池)
- 如果当前Bean是原型Bean,那么后续没有其他动作,不会存入一个Map,下次getBean时会再次执行上述创建过程,得到一个新的Bean对象。
结束语
至此,本次的分享就结束了。其中很多东西只是笼统的介绍了底层逻辑,而并没有详细展开分析。后续看情况再详细说明。
下一篇有时间的话,会出一期视频,直接手撸一个简易版的Spring出来,敬请期待。。。
原创不易,给点鼓励,下篇见。
相关示例代码:测试代码