Spring进阶学习 01、Spring中各个类介绍
@[toc]
前言
本篇博客是在学习 图灵学院-SPRING源码教程 笔记,若文章中出现相关问题,请指出!
所有博客文件目录索引:博客目录索引(持续更新)
一、初识BeanDefinition与BeanFactory
1.1、认识BeanDefinition
BeanDefinition
:BeanDefinition
表示Bean
定义,Spring
根据BeanDefinition
来创建Bean
对象,BeanDefinition
有很多的属性用来描述Bean
,BeanDefinition
是Spring
中非常核心的概念。
重要属性:beanClass、scope、isLazy、dependsOn、primary、initMethodName
beanClass
:表示一个bean的类型,比如:UserService.class、orderService.class,Spring在创建Bean的过程中会根据此属性来实例化得到的对象。scope
:表示一个bean的作用域,比如:①scope等于singleton,该bean就是一个单例Bean。②scope等于prototype,该bean就是一个原型bean。isLazy
:表示一个bean是不是需要懒加载,原型的isLazy属性不起作用,懒加载的单例bean,会在第一个getBean的时候生成该bean,非懒加载的单例bean,则会在Spring启动过程中直接生成好。
哪些会解析成BeanDefinition
呢?
- 如:
@Component
、@Bean
、<bean/>
。
1.2、认识BeanFactory
1.2.1、介绍BeanFactory
BeanFactory
:是一种"spring容器",其翻译就是Bean工厂,其可以用来创建Bean、获取Bean,BeanFactory是Spring中非常核心的组件。
BeanFactory
的核心子接口和实现类:
- ConfigurationBeanFactory:接口
- AbstractBeanFactory:抽象类
- AutowireCapableBeanFactory:接口
DefaultListableBeanFactory
:抽象类
- ListableBeanFactory:接口
1.2.2、认识实现类DefaultListableBeanFactory
介绍一个比较重要的实现类DefaultListableBeanFactory
- 功能:支持单例Bean、支持Bean别名、支持父子BeanFactory、支持Bean类型转换、支持Bean后置处理、支持FactoryBean、支持自动装配等等。
为啥特别介绍这个类呢?因为这个实现类实现了上面提及的所有接口,并且其还有子类
可以从继承图上明显看到该类的继承关系!
BeanDefinition、BeanFactory与Bean对象的关系
简明扼要:BeanFactory将利用BeanDefinition来生成Bean对象。
BeanDefinition相当于是BeanFactory的原材料,Bean对象就相当于是BeanFactory所生产出来的产品。
二、Bean的生命周期
Bean的生命周期
:其描述的是Spring中一个Bean创建过程和销毁过程中所经历的步骤,其中Bean创建过程是重点。我们可以利用Bean生命周期机制来对Bean进行自定义加工!
包含下面六个步骤:
A过程:BeanDefinition表示Bean定义,其定义了某个Bean的类型,Spring就是利用BeanDefinition来创建Bean的,比如需要利用BeanDefinition中的beanClass属性确定Bean的类型,从而实例化出来对象。
B过程:一个Bean中可以有多个构造方法,此时就需要Spring来判断使用哪个构造方法,这个过程很复杂。通过构造方法推断之后确定一个构造方法后,就可以利用构造方法实例化得到一个对象。
C过程:通过构造方法反射得到一个实例化对象。
- 额外说明:在
Spring
中,可以通过BeanPostProcessor
机制来对实例化进行干预。
D过程:实例化得到的对象此时可以说是不完整的,其不完整意思指的是对对象中的某些属性还没有进行属性填充,也就是Spring还没有自动给某些属性赋值,属性填充就是指的自动注入、依赖注入。
E过程:在一个对象属性填充之后,Spring提供了初始化机制,程序员可以利用初始化机制来对Bean进行自定义加工:①可利用InitializingBean
接口来对其他属性进行赋值。(init-method方法注册)。②对Bean中的某些属性进行校验。
F过程:初始化后是Bean创建生命周期后最后一个步骤。AOP机制就是在这个步骤中通过BeanPostProcessor
机制实现的,初始化后得到的对象才是真正的Bean对象。
依赖注入注解
@Autowired
介绍@Autowired注解类
//构造器、方法、参数、属性、注解类
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
@Autowired
:表示某个属性是否需要进行依赖注入,可以写在属性和方法上。注解中的required属性默认为true,表示没有对象可以注入给属性时抛异常。
使用于作用域不同效果说明:
- 某个属性:Spring在进行Bean的生命周期过程中,在属性填充这一步会基于实例化出来的对象,对该对象中加了
@Autowired
的属性自动赋值。- 过程:Spring会先根据属性的类型(可以说是全限定类名)去Spring容器中找出该类型所有的Bean对象;若是找到多个,则再根据属性的名字从多个中再确定一个。如果required属性为true,并且根据属性信息找不到对象则直接抛异常。
- 某个方法:当
@Autowired
注解写在某个方法上时,Spring在Bean生命周期的属性填充阶段,会根据方法的参数类型、参数名称从Spring容器找到对象当做方法入参,自动反射调用该方法。(简而言之与注入属性过程一致,多了一个反射方法过程,方法名可随意) - 某个构造器:Spring会在推断构造方法阶段,选择该构造方法来进行实例化,在反射调用构造方法之前,会先根据构造方法参数类型、参数名从Spring容器中找到Bean对象,当做构造方法入参。
- 值的一提:若是有参构造进行属性依赖注入时并没有根据bean类型、name找到bean实例时,设置了
@Autowired(required = false)
,就会取消异常转而去调用无参构造器。
- 值的一提:若是有参构造进行属性依赖注入时并没有根据bean类型、name找到bean实例时,设置了
实操1:作用于属性
本次实验目录如下:
bean类:
@Service
public class LoginService {
}
@Controller
public class LoginController {
@Autowired(required = false)
private LoginService loginService;
@Override
public String toString() {
return "LoginController{" +
"loginService=" + loginService +
'}';
}
}
注解配置类:
@Configuration //表示其是配置类
@ComponentScan("xyz.changlu") //自动扫描xyz.changlu包下的类注解
public class SpringConfig {
}
测试类:
public class Main{
public static void main(String[] args) throws IOException {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
LoginController bean = applicationContext.getBean(LoginController.class);
System.out.println(bean);
}
}
①成功注入情况
按照上面执行,能够正常注入
②注入失败抛出异常
额外操作:将LoginService类上面的@Service
移除。
之后运行测试类:由于我们压根没有创建Bean实例,所以IOC容器根据type、name去查找bean来进行依赖注入属性时由于都没有找到就会抛出异常!
③注入失败不抛出异常
额外操作:①将LoginService类上面的@Service
移除。②给LoginController类的属性loginService上注解加上参数值false
@Autowired(required = false)
private LoginService loginService;
此时运行测试类,依赖注入失败时并不会抛出异常!
实操2:作用于方法
程序部分可以复用实操1中的代码,只需要删除掉LoginController类中属性上注解和添加一个依赖注入方法即可:
@Controller
public class LoginController {
private LoginService loginService;
//作用于方法上,根据方法的参数类型、名称来去找到bean作为实际参数,最终进行注入操作
@Autowired //若是找不到对应参数的bean实例,不抛出异常可以为:@Autowired(required = false)
public void setLoginService(LoginService loginService){ //方法名可以随意
this.loginService = loginService;
}
}
一般也是三种情况:同作用于属性一致!
总结:本质就是在进行属性填充阶段,若是在方法上写了@Autowired,那么就会根据其参数的类型、name值来作为查找bean的条件,若是查找到就会当做实例传入到方法参数中,并通过反射调用执行该方法。
实操3:作用于构造器
程序部分可以复用实操1中的代码,只需要修改LoginController类中的属性和添加一个有参构造器进行依赖注入即可:
@Controller
public class LoginController {
private LoginService loginService;
//可以写无参构造器,若是有参构造器注入失败(又设置了参数required = false),就会调用无参构造器
public LoginController() {
}
@Autowired //同样根据参数类型、名称去找到bean实例进行依赖注入,并执行构造器进行实例化
public LoginController(LoginService loginService) {
this.loginService = loginService;
}
}
一般也是三种情况:同作用于属性一致!
额外说明:之前无论是作用于属性、方法,设置了@Autowired(required = false)
没有找到对应的bean实例时不会抛出异常,而在构造器中设置了的话同样也是不抛出异常,并且其会自动去调用无参构造器,若此时没有无参构造器那么就会报无参构造异常!
小贴士:尽量都要创建无参构造器噢,防止构造器实例化注入失败调用无参构造器来进行实例!
@Resource(javax包下)
介绍@Resource注解
@Target({TYPE, FIELD, METHOD}) //类、字段、方法
@Retention(RUNTIME)
public @interface Resource {
String name() default "";
...
}
@Resource
:该注解与@Autowired类似,也是用来进行依赖注入的。其是Java层面(即java默认自带的注解)所提供的注解。
- 相较于
@Autowired
是Spring所提供的注解,他们依赖注入的底层实现逻辑也不同。
属性介绍:重点看其中的name属性,针对于name属性是否有值,@Resource的依赖注入底层流程是不同的。该name值就是要查找的bean对象的name。
name
有值情况:Spring会直接根据所指定的name值去Spring容器中找Bean对象,如果找到了则成功;如果没有找到,则报错。name
没有值时(单单写个注解):- 先判断属性名字在Spring容器中是否存在Bean对象。如果存在,则成功找到Bean对象进行注入。
- 如果不存在,则根据属性类型去Spring容器找Bean对象,找到一个则进行注入。
小总结:与@Autowired不同的是,当name没有值时@Resource
是先根据属性名称去找bean对象,找不到再去根据属性类型去找bean对象。并且@Resource
还能够根据指定的name
名称去找容器中找bean实例(@Autowired需要配合@Qualifier配合才能根据指定name去找)。
实操
实际上与@Autowired的测试类几乎相同,仅仅只是将@Autowired
换为@Resource
@Controller
public class LoginController {
@Resource
private LoginService loginService;
@Override
public String toString() {
return "LoginController{" +
"loginService=" + loginService +
'}';
}
}
上面理论说当name值没有时,会先根据属性名去找bean
实例,若找不到才根据类型去找的,可是我实际测试时报出的异常去@Autowired
抛出的异常原因都是一致的!这让我对于name、类型查找的顺序不经有点怀疑?
@Value
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
String value();
}
@Value
:该注解与@Resource、@Autowired类似,也是用来对属性进行依赖注入的。其能够从properties文件中获取值(能够解析SPEL,即spring表达式),注入属性以及对象依赖注入。
介绍三种用途:
@Value("changlu")
(直接注入字符串):直接将字符串"changlu"赋值给String属性,如果属性类型不是String或无法进行类型转换就会报错。@Value("${name}")
(读取properties文件进行注入):会把${}
中的字符串当做key,从Properties文件中找出对应的value赋值给属性;若是没有找到,则会把"${name}"
当成普通字符串注入给属性。@Value("#{bean1}")
(根据name找到bean实例注入):会将#{}
中的字符串当做Spring表达式进行解析,Spring会将其中的bean1作为name,根据该name从spring容器中找对应bean,若是找到就进行属性注入,没找到则报错。
实操1:字符串注入
目录如下:
这里仅需一个LoginController
类、测试类Main
、自动注解配置类SpringConfig
即可:
@Controller
public class LoginController {
@Value("changlu") //将changlu字符串注入到name中
private String name;
@Override
public String toString() {
return "LoginController{" +
"name='" + name + '\'' +
'}';
}
}
配置类:
@Configuration
@ComponentScan("xyz.changlu")
public class SpringConfig {
}
测试类:
public class Main{
public static void main(String[] args) throws IOException {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
LoginController bean = applicationContext.getBean(LoginController.class);
System.out.println(bean);
}
}
情况一:注入成功
运行效果:
情况二:非String类型注入,抛出异常
若是@Value("changlu")
这种形式放置在一个其他类型(非String
类型)上,就会出现类型转换异常,如下:
实操2:读取properties配置文件
准备操作
①在resource目录下添加cl.properties
配置文件,并添加键值对name=changlu
②在LoginController类中的name属性上添加@Value("${name}")
读取配置文件
@PropertySource({"classpath:cl.properties"}) //配置文件源,spring就会读取该properties配置文件
@Controller
public class LoginController {
@Value("${name}") //使用${}表达式就会默认去properties中去找到name对应的value
private String name;
@Override
public String toString() {
return "LoginController{" +
"name='" + name + '\'' +
'}';
}
}
测试效果
情况一:读取成功
其他与实操1的大致相同进行测试:
情况二:读取配置文件失败
上面是成功在配置文件中找到的情况,若是我们将@Value("${name}")
改为 @Value("${age}")
,由于age在配置文件中没有对应键值对,就会将字符串"${age}"
注入到name属性值上:
实操3:依赖注入bean实例
准备过程
测试环境与实操1中大致相同,需要更改的如下:
①添加一个LoginService类,并设置@Service("loginser")
@Service("loginser") //为了体现出测试的效果,我们为对应的bean实例设置自定义名称:
public class LoginService {
}
②接着修改LoginController
中的属性为LoginService
实例
@Controller
public class LoginController {
@Value("#{loginser}") //使用#{}表达式,将其中loginser作为name去spring容器中查找bean实例
private LoginService loginService;
@Override
public String toString() {
return "LoginController{" +
"loginService=" + loginService +
'}';
}
}
测试效果
情况一:根据name正确找到bean实例注入成功
直接根据上面修改,运行测试类即可
情况二:没有找到bean实例报异常
修改:将其中的@Value("#{loginser}")
修改为@Value("#{login}")
,此时保证login作为name的bean实例没有被进行注册,运行测试类就会出现异常如下:
依赖注入注解小总结
共同点:都可以用来在spring框架中进行依赖注入的。
@Autowired
:
- 属于spring(spring-beans)中的注解。
- 只有一个属性就是required,默认为true,若是设置为false,依赖注入失败时不会抛出异常!
- 默认查找bean实例的顺序为类型、name值。若是想要根据自己指定的name去找bean实例,就需要配合@Qualifer(设置name名称)注解!
@Resource
:属于jdk自带的注解,但能够被spring识别进行依赖注入。
- 属于jdk自带的注解,在javax包目录下。
- 有多个属性,比较重要的是name值,使用该注解时带上name属性,就会根据该name从spring容器中去找bean实例。相较于@Autowired注解需要配合@Qualifer才可以。
- 默认查找bean实例顺序为name、类型。(暂时不太确定自己测试与@Autowired一致,仅仅从异常抛出情况来看)
@Value
:
- 属于spring(spring-beans)的注解。
- 只有一个value属性,其有三个功能①字符串注入(直接传入,#{}、
{}
表达式,需要配合一个
@PropertySource来加载配置文件使用(没有则将
"${xxx}"作为字符串传入)。③依赖注入bean实例使用
#{}表
达式中的值作为bean的name去查找。 - bean依赖注入功能若是找不到bean实例就会抛出异常!
三、认识FactoryBean
3.1、介绍FactoryBean与BeanFactory的区别
Spring中有两种Bean
在Spring中有两种类型的Bean,一种是普通Bean
,另一种是工厂Bean
,也就是FactoryBean
。这两种Bean
都被容器管理,但是工程Bean跟普通Bean不同,其返回的对象不是特定类的一个实例,其返回的是该FactoryBean的getObject()方法所返回的对象。
在Spring
框架内部有很多地方有FactoryBean
的实现类,在很多应用如:Spring
的AOP
、ORM
、事务管理以及其他第三方框架(hibernate、mybatis、jpa集成都有体现)
分辨BeanFactory与FactoryBean区别
BeanFactory
:以Factory结尾,表示它是一个工厂类,是用来管理Bean的一个工厂。
- 是一个Spring容器,是一个大型工厂,可以生产出各种各样的Bean。
FactoryBean
:以Bean结尾,表示它是一个Bean,不同于普通Bean的是,其实现了FactoryBean<T>接口的Bean,根据该Bean的id从BeanFactory
中获取的实际上是FactoryBean
的getObject()返回的对象,而不是FactoryBean本身,若是要获取FactoryBean的对象可以根据&id
名来获取。
- 本身是一个Bean,同时它相当于一个小型工厂,可以生产出另外的Bean(
getObject()
中获取)。
3.2、认识FactoryBean
getObject()
:返回的是Bean对象。getObjectType()
:返回的是Bean对象类型。boolean isSingleton()
:返回是否为单例,默认是true,表示单例。
FactoryBean
:其是Spring
所提供的一种较灵活的创建Bean
的方式,可以通过实现FactoryBean
接口中的getObject()
方法来返回一个对象,这个对象就是最终的Bean
对象。
3.3、自定义一个FactoryBean
自动配置类
@Configuration
@ComponentScan("xyz.changlu") //自动扫描xyz.changlu包目录下
public class SpringConfig {
}
User
类:下面自定义FactoryBean实际创建得到的一个bean
public class User {
}
自定义FactoryBean类:
@Component("clfb") //这里设置name为clfb。(若是没有自定义参数,那么就会name为changluFactoryBean)
public class ChangluFactoryBean implements FactoryBean { //实现FactoryBean接口,并重写两个方法
//返回的实际bean对象,其name为clfb
@Override
public Object getObject() throws Exception {
return new User();
}
//返回bean对象的类型
@Override
public Class<?> getObjectType() {
return User.class;
}
}
测试类:
public class Main{
public static void main(String[] args) throws IOException {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
//FactoryBean实现类会创建两个Bean对象
//获取到ChangluFactoryBean创建的bean对象
Object bean = applicationContext.getBean("clfb");
System.out.println(bean);
//获取到ChangluFactoryBean这个bean对象
Object clfb = applicationContext.getBean("&clfb");
System.out.println(clfb);
}
}
运行测试类得到以下结果:
总结:对于上面的FactoryBean
实际上对应了两个Bean
对象:
beanName
为"clfb",bean
对象为getObject
方法所返回的User
对象。beanName
为"&clfb",bean
对象为ChangluFactoryBean
类的实例对象。
四、认识ApplicationContext
ApplicationContext
:该接口是比BeanFactory
更加强大的Spring
容器,它既可以创建bean
、获取bean
,还支持国际化、事件广播、获取资源等BeanFactory
不具备的功能。(该接口多继承了多个接口其中就包含了BeanFactory
)
介绍ApplicationContext继承的接口
EnvironmentCapable
(环境能力):表示拥有获取环境变量的功能,可以通过ApplicationContext获取操作系统环境变量和JVM环境变量。
HierarchicalBeanFactory
(分层):拥有获取父BeanFactory、判断某个name是否存在bean对象的功能。
ListableBeanFactory
(清单):拥有获取所有beanNames
、判断某个beanName
是否存在beanDefinition
对象、统计BeanDefinition
个数、获取某个类型对应的所有beanNames等功能。(获取bean名称、是否存在bean、个数、类型)
ApplicationEventPublisher
:拥有事件发布功能,可以发布事件,这是ApplicationContext
相对于BeanFactory
比较突出、常用的功能。
ResourcePatternResolver
:拥有加载并获取资源的功能,这里的资源可以是文件,图片等某个URL资源。
MessageSource
:拥有国际化功能,比如可以直接利用MessageSource对象获取某个国际化资源(比如不同国家语言所对应的字符)
五、认识BeanPostProcessor
BeanPostProcessor
是Spring
所提供的一种扩展机制,可以利用该机制对Bean
进行定制化加工,在Spring
底层源码实现中,也广泛使用了该机制,BeanPostProcessor
通常也叫做Bean
后置处理器。
BeanPostProcessor
:在Spring中是一个接口,我们定义一个后置处理器(实现该接口即可),在Spring中还存在一些接口继承了BeanPostProcessor,这些子接口是在BeanPostProcessor的基础上增加了一些其他功能。
postProcessBeforeInitialization()
:初始化前方法,表示可以利用这个方法来对Bean在初始化前进行自定义加工。postProcessAfterInitialization()
:初始化后方法,利用这个方法来对Bean在初始化后进行自定义加工。
介绍子接口(扩展功能):
InstantiationAwareBeanPostProcessor
首先看一些Bean后置处理器接口有哪些子类接口与实现类:
我们来其中子接口InstantiationAwareBeanPostProcessor
中的方法:主要看其中三个方法,有一个已经过时
postProcessBeforeInstantiation()
:实例化前。postProcessAfterInstantiation()
:实例化后。postProcessProperties()
:属性注入后。
这个接口相较于BeanPostProcessor
更强大具有实例化前后、属性注入后的方法功能。
AOP是如何工作的?
介绍AOP与六个核心概念
AOP
:就是面向切面编程,是一种非常适合在无需修改业务代码的前提下,对某个或某些业务增加统一的功能,比如日志记录、权限控制、事务管理等,能很好的使代码解耦,提高开发效率。
其包含六个核心概念:
Advice
:通知、建议,在Spring中通过定义Advice来定义代理逻辑。Pointcut
:切点,表示Advice对应的代理逻辑应用到哪个类、哪个方法上。Advisor
:Advice+Pointcut,表示代理逻辑和切入点的一个整体,程序员可通过定义或封装一个Advisor,来定义切点和代理逻辑。Weaving
织入,将Advice代理逻辑在源代码级别嵌入到切点的过程。Target
:表示目标对象,也就是被代理对象,在AOP生成的代理对象中会持有目标对象。Join Point
:表示连接点,在Spring AOP中,就是方法的执行点。
AOP的工作原理
AOP是发生在Bean的生命周期过程中的:
- Spring生成bean对象时,先实例化出来一个对象,也就是target对象。
- 接着对target对象进行属性填充。
- 在初始化后步骤中,会判断target对象有没有对应的切面。
- 如果有切面,就表示当前target对象需要进行AOP。
- 通过
Cglib
或JDK
动态代理机制生成一个代理对象,作为最终的bean对象。 - 在代理对象中有一个target属性指向target对象。
- 点赞
- 收藏
- 关注作者
评论(0)