Spring笔记
:o:导学
传统Java开发的代码耦合性过强,接口与实现紧密耦合。此外,Log等日志功能也都写在业务代码中,这使得代码非常的繁琐和重复,同时也不便于维护
为了解决这个问题,基于工厂设计模式的Ioc思想应运而生。
:rainbow:IoC、 DI和AOP思想
- IOC 控制反转思想的提出
IOC思想: Inversion of Control,翻译为“控制反转” 或“反转控制” ,强调的是原来在程序中创建Bean的权利反
转给第三方。
例如:原来在程序中手动的去 new UserServiceImpl(),手动的去new UserDaoImpl(),而根据IoC思想的指导,
寻求一个第三方去创建UserServiceImpl对象和UserDaoImpl对象。这样程序与具体对象就失去的直接联系。
工厂设计模式, BeanFactory来充当第三方的角色,来产生Bean实例
BeanFactory怎么知道产生哪些Bean实例呢?
可以使用配置文件配置Bean的基本信息, BeanFactory根据配置文件来生产Bean实例
将实例类的创建交给工厂,通过BeanFactory创建对象,具体的,我们通过代码创建一个BeanFactiry工厂对象,让他来读取配置文件并创建Bean实例
例如:
在你的业务代码中
1 | BeanFactory factory = new BeanFactory("Beans.xml"); |
在resources包下创建一个Bean.xml配置文件
1 | <bean id="userService" class="com.example.service.impl.userServiceImpl"> |
将User在BeanFactory内部设置给userService的过程叫做注入,而UserService需要User的注入后进行工作,因此这个过程称为“依赖注入”也就是DI思想
AOP思想:
AOP(Aspect Oriented Programming),即面向切面编程,它也是基于代理模式,可以在代理对象中对目标对象的方法进行增强,它采用横向切面抽取方法,可以理解为在方法的前后进行一些操作,比如可以在业务代码之前生成日志,这样,你就不需要在每一段业务代码前都加入关于输出日志的代码了。
:rainbow:Spring容器
:o:基于xml的Spring应用
Xml配置方式 | 功能描述 |
---|---|
Bean的id和全限定名配置 | |
通过name设置Bean的别名,通过别名也能直接获取到Bean实例 | |
Bean的作用范围, BeanFactory作为容器时取值singleton和prototype | |
Bean的实例化时机,是否延迟加载。 BeanFactory作为容器时无效 | |
Bean实例化后自动执行的初始化方法, method指定方法名 | |
Bean实例销毁前的方法, method指定方法名 | |
设置自动注入模式,常用的有按照类型byType,按照名字byName | |
指定哪个工厂Bean的哪个方法完成Bean的创建 |
:rainbow:基本使用
:one:Bean的实例化配置
Spring的实例化方式主要如下两种:
构造方式实例化:
底层通过构造方法对Bean进行实例化
- 有参:在xml中加一个constructor-arg标签
1 | <bean id="user1" class="com.example.pojo.entity.User"> |
可以通过调试查看实例化的值(首先确保你的User中有name和age这两个字段)
工厂方式实例化:
底层通过调用自定义的工厂方法对Bean进行实例化
- 静态工厂实例化
静态工厂方法实例化Bean,其实就是定义一个工厂类,提供一个静态方法用于生产Bean实例,在将该工厂类及其
静态方法配置给Spring即可
1 | //工厂类 |
1 | <bean id="user2" class="com.example.factory.UserFactoryBean" factory-method="getUser"> |
- 实例工厂实例化
1 | public class UserFactoryBean{ |
需要实例化工厂Bean,通过工厂Bean的非静态方法实例化User Bean
1 | <!--先实例化工厂--> |
- FactoryBean工厂实例化
Spring提供了FactoryBean的接口规范,
FactoryBean接口定义如下:
1 | public interface FactoryBean<T> { |
你可以实现这个接口,然后在xml中再配置User的Bean实例化
1 | //实现接口 |
1 | <!--配置FactoryBean交给Spring管理--> |
通过ApplicationContext实例化的对象通常存放在:ApplicationContext->beanFactory->singletonObjects(单例池)中,但如果通过实现FactoryBean作为自定义Bean工厂配置的实例化对象并不存储在singletonObjects中,而是先保存在factiryBeanObjectCache(初沉池)中,当调用getBean方法时,才将实例化对象从初沉池中拿出放入单例池。
你可以通过Debug验证它
:two:Bean的依赖注入配置
Bean的依赖注入有两种方式:
注入方式 | 配置方式 | 需求 |
---|---|---|
通过Bean的setter方法注入 | 需要实现对应的set方法 | |
通过构造Bean的方法进行注入 | 在构造方法中包含这个参数 |
其中, ref 是 reference 的缩写形式,翻译为:涉及,参考的意思,用于引用其他Bean的id。 value 用于注入普通
属性值。通常来说,依赖注入的数据类型有如下三种:
- 普通数据类型,例如: String、 int、 boolean等,通过value属性指定。
- 引用数据类型,例如: UserDaoImpl、 DataSource等,通过ref属性指定。
- 集合数据类型,例如: List、 Map、 Properties等。 在下面演示
集合类型数据
- List:为userServiceImpl加入list字段,并且提供一个公开的set方法。这里以stringList、userList作为演示
1 | <!--List集合的配置--> |
为了方便,以下的其他集合类型的演示就默认是在userService1 Bean标签下的
set:它与List几乎没有区别,只是将list标签换成set标签
Map:使用entry标签设置键值对
1 | <!--注入值为字符串的Map集合--> |
- properties:使用prop标签,并在标签中指定值:
1 | <property name="properties"> |
:three:Spring的其他配置标签
Spring的默认标签用到的是Spring的默认命名空间
1 |
|
该命名空间约束下的默认标签如下:
标签 | 作用 |
---|---|
一般作为 xml 配置根标签,其他标签都是该标签的子标签 | |
Bean的配置标签,上面已经详解了,此处不再阐述 | |
外部资源导入标签 | |
指定Bean的别名标签,使用较少 |
配置环境标签
在你的xml配置文件中,可以为beans指定环境
1 | <!--配置测试环境下需要加载的bean实例--> |
Spring默认启用默认的配置环境,如果你想启用测试环境下的Bean配置,可以使用以下两种方式指定被激活的环境:
使用命令行动态参数,虚拟机参数位置加载 -Dspring.profiles.active=test
使用代码的方式设置环境变量 System.setProperty(“spring.profiles.active”,”test”)
1
2
3
4 >System.setProperty("spring.profiles.active","test");
>//通过ApplicationContext获取实例化对象
>ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
>UserService userService = (UserService) context.getBean("userService3");
导入配置环境
即Import,通过以下方法指定导入,如果在beans2.xml中有其他的注入,也能通过ApplicationContext获取
1 | <!--在beans.xml中--> |
你可以在beanFactory->beanDefinitionMap的这个地方看到它的来源
导入非Spring的第三方标签
由于是第三方的,不能直接在Spring中使用,需要先引入项目依赖,再在根标签的name space和shemaLocation中导入
以java RPC框架 dubbo为例
- 引入依赖
1 | <!--dubbo--> |
- 在bean.xml的beans根标签中添加
1 |
|
现在可以在beans.xml中使用dubbo的标签了
###:four:实操(以druid数据库池为例)
- 在pom.xml导入项目依赖
1 | <!--druid池--> |
- 通过静态工厂实例化方法实例化DateSource对象
1 | <!--druid必备的driverName、url、username、password--> |
1 | //通过ApplicationContext获得实例对象 |
于是获得了一个DruidDataSource实例对象
:rainbow:Bean 实例化的流程(原理)
:one:读取配置文件并封装需要实例化的对象
Spring容器在进行初始化时,会将xml配置的 bean 的信息封装成一个 BeanDefinition 对象,所有的BeanDefinition存储到一个名为 beanDefinitionMap 的Map集合中去, Spring框架在对该Map进行遍历,使用反射创建Bean实例对象,创建好的Bean对象存储在一个名为 singletonObjects 的Map集合中,当调用getBean方法时则最终从该Map集合中取出Bean实例对象返回。
1 | //singletonObjects的底层 DefaultListableBeanFactory和其中维护的beanDefinitionMap |
Spring框架会取出beanDefinitionMap中的每个BeanDefinition信息,反射构造方法或调用指定的工厂方法生成Bean实例对象,所以只要将BeanDefinition注册到beanDefinitionMap这个Map中, Spring就会进行对应的Bean的实例化操作
:two:通过反射创建对象并存放到单例池中
Bean实例及单例池singletonObjects, beanDefinitionMap中的BeanDefinition会被转化成对应的Bean实例对象,存储到单例池singletonObjects中去,在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中,维护着singletonObjects单例池,源码如下:
1 | //DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry和其中维护的singletonObjects |
:coffee:总结:Bean 实例化的基本流程
- 加载xml配置文件,解析获取配置中的每个
的信息,封装成一个个的BeanDefinition对象; - 将BeanDefinition存储在beanDefinitionMap ==> Map<String,BeanDefinition>中;
- ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象;
- 创建好的Bean实例对象,存储到singletonObjects ==> Map<String,Object>中;
- 当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。
:rainbow:后处理器
后处理器 | 作用 |
---|---|
BeanProcessor | 在Bean实例化之后,填充到单例池singletonObjects之前执行 |
BeanFactoryPostProcessor | 在BeanDefinitionMap填充完成后,还没开始创建实例之前执行 |
BeanDefinitionRegistryPostProcessor | 用作向BeanDefinitionMap中注册BeanDefinition。它是BeanFactoryPostProcessor的子类,调用时机也和BeanFactoryPostProcessor相同 |
在使用后处理器时,需要实现对应的处理器接口,然后将处理器的实现配置到xml配置文件中作为Bean,这样Spring会自动的在相应的时间调用后处理器的方法。
:one:Bean工厂后处理器
:tea::以通过BeanDefinitionRegistryPostProcessor注册为例
1 | public class RegisterProcessor implements BeanDefinitionRegistryPostProcessor { |
通过xml配置存储在BeanDefinitionMap中的BeanDefinition是GenericBeanDefinition,但通过后处理器注册BeanDefinition推荐使用RootBeanDefinition
1 | <!--不要忘了将后处理器配置到Spring容器中--> |
此时,我们可以为之前的Spring实例化流程的流程图进行一些补充
:two:通过注解实例化
学习完bean后处理器后,我们知道了如何在Spring实例化的不同时间段加入一个新的类以将他实例化,这非常有用处。
我们很容易发现,通过配置xml文件来实例化对象非常麻烦,但如果将后处理器结合上ResourcePatternResolver
(一个用来扫描包下的所有类的Spring提供的工具类和它的实现:PathMatchingResourcePatternResolver
)就能够实现只需要为需要实例化的类加上一个注解即可实现类的实例化。
- 首先自定义一个注解,随便叫什么名字,我这里叫做
MyComponent
1 | //指定注解作用的类型,包括 Type、Field、Method、Package等,这里指定为Type,表示作用于类 |
- 为任何一个你想要实例化的类添加@MyComponent注解
1 |
|
- 接下来,编写一段代码来读取指定包(Package)下的所有添加了注解的类
1 | package com.example.springlean.utils; |
scanMyComponentAnnotation的参数表示源代码包,按照你的包结构传递对应的参数就可以获取对应包下所有加入了MyComponent注解的类。
- 在这之后就可以借助bean工厂后处理器将这些添加了注解的类加入到BeanDefinitionMap中了
1 | // |
需要在开始实例化之前将BeanDefinition注册,因此需要使用BeanFactoryPostProcessor或BeanDefinitionRegistryPostProcessor
1
2 ><!--不要忘了将后处理器配置到Spring容器中-->
><bean class="com.example.processor.RegisterProcessor"/>
之后就可以在spring容器中获取到oneBean了!🎉🎉🎉
1 | public static void main(String[] args){ |
:three:bean后处理器
beanPostProcessor
前面已经讲过,Bean被实例化后,到最终缓存到名为singletonObjects单例池之前,中间会经过Bean的初始化过程,例如:属性的填充、初始方法init的执行等,其中有一个对外进行扩展的点BeanPostProcessor,我们称为Bean后处理。跟上面的Bean工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor,会在流程节点上被Spring自动调用。
BeanPostProcessor的接口定义如下:
1 | public interface BeanPostProcessor { |
可以通过Bean后处理器+动态代理增强方法实现日志打印
1 | package com.example.springlean.processor; |
bean实例化的初始阶段的全过程:
:rainbow: xml配置第三方框架
:one: mybatisMapper
之前有通过Spring来xml配置来获取sqlSession的实例:跳转。现在演示另一种:sqlSessionFactoryBean+MapperScannerConfigurer的获取Mapper来操作数据库。
1 | <!--在 ApplicationContext.xml 中--> |
然后在mapper包下创建了一个UserMapper:
1 | public interface UserMapper{ |
为所需的Mapper配置一个xml文件,同时命名空间中指向这个Mapper。由于前面配置了扫描包,他会自己读取到其中的配置文件,并将SQL操作代理给Mapper的代理类,然后放到Spring容器中。
在resource包的mapper下新建一个UserMapper.xml配置文件,我这里随意写一个select语句
1 |
|
你可以通过Spring容器直接拿到UserMapper对象(实际上是一个代理对象)
1 | ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); |
这样就可以使用UserMapper的select方法从user表中读取数据了。
之后需要其他的Mapper,不需要做基本配置,在mapper包下新建一个对应的Mapper。然后在resources包下的mapper中也新建一个对应的.xml文件并将SQL操作写在配置文件中。
实际上开发中都是使用SpringBoot+MyBatisPlus的配置方法。但是这里是学习Spring,所以以此为例子而已。
SpringBoot+MyBatisPlus
首先确保导入了SpringBoot的MyBatisPlus依赖
1
2
3
4 <dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>在ApplicationContext中配置:
1
2
3
4 mybatis:
#mapper配置文件
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.entitymapper-locations 指定了mapper配置文件的路径
type-aliases-package 让你可以在这个包下不用使用全限定名
此后就只需要在对应的 mapper.xml 的命名空间中指向对应的类即可
:o:Bean的生命周期
————————————
本节参阅:
[Spring Bean 的生命周期 - spring 中文网 (springdoc.cn)](https://springdoc.cn/the-life-cycle-of-a-spring-bean/#:~:text=本文介绍了 Spri)
Spring Bean的生命周期是从 Bean 实例化之后,即通过反射创建出对象之后,到Bean成为一个完整对象,存储到单例池中,到Bean最后被销毁,这个过程被称为Spring Bean的生命周期。 Spring Bean的生命周期大体上分为四个阶段:实例化阶段、初始化阶段、完成阶段、销毁阶段。
- Bean的实例化阶段: Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的,是否不是延迟加载的,是否不是FactoryBean等,最终通过反射进行实例化singleton的Bean;
- Bean的初始化阶段: Bean创建之后还仅仅是个”半成品”,还需要对Bean实例的属性进行填充、执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。
该阶段是Spring最具技术含量和复杂度的阶段, Aop增强功能,Spring的注解功能等、Bean的循环引用问题都是在这个阶段体现的;
- Bean的完成阶段:经过初始化阶段, Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期。
- Bean销毁阶段
下图是Spring在创建一个Bean时的方法调用流程图,可以帮助理解Bean的生命周期
更多细节:
:rainbow: Aware接口
前面两个关于Spring生命周期的图中都涉及到了一些Aware接口,Aware接口提供了一个让程序员获得框架的一些对象的机会。Aware接口在不同的阶段被调用,并且将对应的对象通过参数注入给你,你所需要做的,就是实现对应的接口,然后在重新方法中实现编写预期逻辑。
以下是一些常见的Aware接口:
Aware接口 | 回调方法 | 作用 |
---|---|---|
ServletContextAware | setServletContext(ServletContext context) | Spring框架回调方法注入ServletContext对象, web环境下才生效 |
BeanFactoryAware | setBeanFactory(BeanFactory factory) | Spring框架回调方法注入beanFactory对象 |
BeanNameAware | setBeanName(String beanName) | Spring框架回调方法注入当前Bean在容器中 的beanName |
ApplicationContextAware | setApplicationContext(ApplicationContext applicationContext) | Spring框架回调方法注入applicationContext 对象 |
现在,终于知道了Spring构建Bean的完整流程:
:o: 基于注解的Spring应用
通过注解能够实现和xml配置相同的功能,而且更便捷更容易
:rainbow: 基本注解
:one: 和实例化相关的
这实际上相当于在xml中配置一个bean标签
@Component 用于注册到Spring容器,它的其他配置信息也可以通过注解来实现
xml配置 | 注解 | 描述 |
---|---|---|
@Scope | 在类上或使用了@Bean标注的方法上,标注Bean的作用范围,取值为 singleton或prototype | |
@Lazy | 在类上或使用了@Bean标注的方法上,标注Bean是否延迟加载,取值为 true和false | |
@PostConstruct | 在方法上使用,标注Bean的实例化后执行的方法 | |
@PreDestroy | 在方法上使用,标注Bean的销毁前执行方法 |
此外,还有几个注解和它作用相同,只是用于不同的应用常见。
@Component衍生注解 | 描述 |
---|---|
@Repository | 在Dao层类上使用 |
@Service | 在Service层类上使用 |
@Controller | 在Web层类上使用 |
:two: 依赖注入
注入属性值的注解有:
属性注入注解 | 描述 |
---|---|
@Value | 使用在字段或方法上,用于注入普通数据 |
@Autowired | 使用在字段或方法上,用于根据类型(byType)注入引用数据 |
@Qualifier | 使用在字段或方法上,结合@Autowired, 根据名称注入 |
@Resource | 使用在字段或方法上,根据类型或名称进行注入 |
这些注解可以用在方法的参数上,为参数自动注入对象或值
注:Value还可以通过$符指向在properties中配置的键值对,像前面xml中一样。前提是在配置了
<context:property-placeholder location=”classpath:jdbc.properties”/> 标签
这类标签也可以通过注解进行配置:注解配置非默认标签, 现在可以先在xml中配置验一下
1
2
3
4
5
6
7
8
9
10
11
12
private String username;
//同时,在jdbc.properties中
jdbc.username=root
private UserDao userDao;
//会自动将配置了@Component("userDao2")的类注入到方法的参数中
public void setUserDao(UserDao userDao){
System.out.println(userDao);
}
:three: 非自定义Bean的注解
@Bean注解
1 | //以MyBatis的dataSource举例 |
忘记的可以点击:跳转
之前自动注入的注解可以用在参数上
1 |
|
在方法上配置了@Bean时,如果方法中的参数也在Spring容器中,这个参数会被自动注入
1 |
|
:four: @Configuration注解
@Configuration 注解标识的类为配置类,替代原有xml配置文件,该注解第一个作用是标识该类是一个配置类,第
二个作用是具备@Component作用
@ComponentScan 组件扫描配置,替代原有xml文件中的<context:component-scan base-package=””/>
@PropertySource 注解用于加载外部properties资源配置,替代原有xml中的 <context:propertyplaceholder location=“” /> 配置
1 |
|
此时获取Spring容器就不再通过ClassPathXmlApplicationContext获取,而是AnnotationConfigApplicationContext
1 | ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ApplicationContextConfig.class); |
你可以通过它获取对应包下加入了@Component相关注解的实例对象
:five: 更多…
还有:
注解 | 作用 |
---|---|
@profile | 变量的环境 |
@import | 导入其他配置 |
… | … |
注解太多了,使用也不难,和xml方式的不同就是读取的是配置的注解而不是配置文件,其他的更底层的逻辑都是类似的。
不同注解的读取器也是通过Bean工厂后处理器等注册到Spring容器当中的,
1
2
3
4 //比如Autowired注解,通过实现Bean后处理器,将这个注解加入到Spring容器当中
public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
...
}