Spring6
学习 Spring6 时的 GitHub 地址:https://github.com/2022zhang125/Spring6.git
前期提要:
在这里,我将要展示一下,我学习这些技术时我认为比较重要的地方,由于篇幅问题不能涵盖全部,如有错误,欢迎指正。
高扩展,低耦合
Spring 通过对控制反转思想(IoC)的实现,以依赖注入(DI)的方式,去实现低耦合高扩展的形式,不仅满足了OCP的开闭原则更是对依赖倒置原则的满足,将层与层之间的耦合完全解除,不像以前代码强行 New 一个 Service 层的对象,导致 Service 对象发生改变时,需要重新编写代码,使用 Spring 框架就可让每个层都抽象化,让大家更加的灵活,也就是非侵入式设计。
Bean 类集中化管理
在 Spring 中,通过反射机制,用包名反射出 Class,然后通过代理对象去实例化 Class,省去的 New Class 的过程。使用 BeanFactory 的工厂模式,在运行了 new ClassPathXmlApplicationContext()的时候,就实例化好了。
多元化注入
Set 注入
通过 XML 中的 property 属性将 Set 方法 与 Bean 对象进行绑定,这样当通过 XML 的全限定包名定位到 Class 时,会自动调用其 Set 方法进行实例化对象。也要确保在 Service 中有 Set 方法。
<bean id="userServiceBean" class="com.believesun.spring6.service.UserService">
<!--
为了,让set方法能够被调用,这里需要配置property标签
这里的name的值的要求:
将set方法,去掉set,然后把剩下来的首字母变小写
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
这里
第一:要确定是哪个set方法
第二:传入你想注入的bean的id,用ref(references引用)
-->
<property name="userDao" ref="userDaoBean"/>
</bean>
Set 的多种注入形式
1、内部注入
<bean id="orderService2" class="com.believesun.spring6.service.OrderService">
<!--内部注入-->
<property name="orderDao">
<bean class="com.believesun.spring6.dao.OrderDao" />
</property>
</bean>
2、外部注入
<bean id="orderDaoBean" class="com.believesun.spring6.dao.OrderDao" />
<bean id="orderService" class="com.believesun.spring6.service.OrderService">
<!--使用外部注入-->
<property name="orderDao" ref="orderDaoBean" />
</bean>
3、基本类型的注入
<bean id="orderService3" class="com.believesun.spring6.service.OrderService">
<property name="变量名" value="值" />
</bean>
构造注入(Construct 注入)不常用
由于构造一个对象的方法不止是通过 Set 赋值,还可以通过构造方法进行赋值,在类实例化时直接复制。
第一种:根据下标注入
<!--根据下标进行注入-->
<bean id="csBean" class="com.believesun.spring6.service.CustomerService" >
<!--
这里使用constructor-agr标签,对构造方法的指定位置的指定属性进行注入操作。
-->
<constructor-arg index="0" ref="userDaoBean"/>
<constructor-arg index="1" ref="vipDaoBean"/>
</bean>
第二种:根据名字注入
<!--根据参数名字注入-->
<bean id="csBean2" class="com.believesun.spring6.service.CustomerService">
<constructor-arg name="userDao" ref="userDaoBean" />
<constructor-arg name="vipDao" ref="vipDaoBean" />
</bean>
第三种:靠spring自己推断(可读性太差不用)
<!--什么都不指定,直接用-->
<bean id="csBean3" class="com.believesun.spring6.service.CustomerService">
<constructor-arg ref="userDaoBean" />
<constructor-arg ref="vipDaoBean" />
</bean>
Scope
使用 Scope 去让 AOP 容器在每次 New 对象时都创建一个不同的对象。通过设置 Scope 的 Value 去让对象处于 单例singleton,原型prototype
如果设置 Scope 为singleton则每个线程都是同一个对象!及其不安全,因此我们可以使用prototype让每次生成的对象都不同,但这样又太乱,因此我们应该自定义 Scope
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--自定义scope-->
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="myThread">
<bean class="org.springframework.context.support.SimpleThreadScope" />
</entry>
</map>
</property>
</bean>
<bean id="sb" class="com.believesun.scope.bean.SpringBean" scope="myThread"/>
</beans>
通过自定义 Scope,让 Scope 类型指向 myThread,这样就可以使每次线程创建的对象都一致,而不同线程则不同。
生命周期
# Bean的生命周期(粗略版本)
五步:
第一步:实例化Bean对象(调用无参数构造方法)---> 构造方法
第二步:给Bean对象赋值(调用Set方法)---> setName() 调用方法赋值
BeanNameAware发力 ---> setBeanName()
BeanClassLoaderAware发力 ---> setBeanClassLoader()
BeanFactoryAware发力 ---> setBeanFactory()
检查是否实现了 BeanNameAware,BeanClassLoaderAware,BeanFactoryAware(Aware 明白,认识,察觉)
为了给于用户一些已知信息。
Bean后处理器before afterPropertiesSet()
检查是否实现了 InitializingBean接口
为了在初始化之前进行操作。
第三步:初始化Bean对象(调用initBean方法,自己写的,方法名随意。)
Bean后处理器after
第四步:使用Bean对象
检查是否实现了 DisposableBean接口
第五步:摧毁Bean对象(调用destroyBean方法,也是自己写的。)
## 注意
该生命周期只针对 singleton对象也就是单例对象,对于prototype对象,只会执行前八步(也就是会死在使用bean那一步,后面的DisposableBean
以及自己的destroyBean方法都不在执行)
# 半路上让Spring管理自己的对象
@Test
public void testRegisterBean(){
User user = new User();
System.out.println(user);
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
defaultListableBeanFactory.registerSingleton("userBean",user);
// 调用Spring容器中的对象
User userBean = defaultListableBeanFactory.getBean("userBean", User.class);
System.out.println(userBean);
}
循环依赖
什么是循环依赖问题? 在 Husband 类和 Wife 类中,Husband 类中存在 Wife 属性,Wife 类中也存在 Husband 属性。
A对象的B属性需要去动态创建,而创建B属性时因为循环依赖的关系,又需要A属性,而A属性的创建又需要B属性......
这样就导致一直递归,一直创建不出最初的对象,会出现异常!!!
解决方法
-
singleton + setter 模式 (可以)
解决方法:
当Spring容器去创建单例对象(singleton)时,是分为两步完成的
第一步:调用无参数构造方法实例化对象,并且立即 “曝光”
重点就是:实例化对象后立即 “曝光”
第二步:给对象进行赋值操作,当对象需要一个属性时,由于其singleton的原因,所以可以直接调用。
<!--配置Husband类-->
<bean id="husbandBean" class="com.believesun.bean.Husband" p:name="zhangsan" p:wife-ref="wifeBean" scope="singleton"/>
<!--配置wife类-->
<bean id="wifeBean" class="com.believesun.bean.Wife" p:name="李四" p:husband-ref="husbandBean" scope="singleton"/>
-
prototype + setter 模式 (不行)
prototype意味着,对象在getBean的时候进行创建并赋值操作,这两个操作时一起完成的。
这就会导致,A对象的B属性需要去动态创建,而创建B属性时因为循环依赖的关系,又需要A属性,而A属性的创建又需要B属性......
这样就导致一直递归,一直创建不出最初的对象,会出现异常!!!
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'husbandBean': Requested bean is currently in creation: Is there an unresolvable circular reference?
BeanCurrentlyInCreationException:Bean对象现在正在创建异常!!!
这个异常很重要,这个异常一出,就代表着循环依赖出现了问题,而且出现问题的两个类必须都是prototype类型的。
错误代码
<!--配置Husband类-->
<bean id="husbandBean" class="com.believesun.bean.Husband" p:name="zhangsan" p:wife-ref="wifeBean" scope="prototype"/>
<!--配置wife类-->
<bean id="wifeBean" class="com.believesun.bean.Wife" p:name="李四" p:husband-ref="husbandBean" scope="prototype"/>
Spring 解决循环依赖问题
-
第一步:实例化对象
-
Object bean = instanceWrapper.getWrappedInstance();
-
-
第二步:曝光出去
-
this.addSingletonFactory(beanName, () -> { return this.getEarlyBeanReference(beanName, mbd, bean); });-
如何进行曝光?
key:代表bean的id,就是Bean的名字
private final Map<String, Object> singletonObjects; 一级缓存(存放完整的Bean对象,已经赋过值的Bean对象)
private final Map<String, Object> earlySingletonObjects; 二级缓存(存放早期的Bean对象,还未来得及赋值的Bean对象)
private final Map<String, ObjectFactory<?>> singletonFactories; 三级缓存(存放制造该Bean对象的工厂对象,就是谁造出来这个Bean的工厂对象。)
这里从一级缓存开始查找该Bean对象,依次到三级缓存,
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
其中最后的三级缓存中创建对象,然后立马执行 this.earlySingletonObjects.put(beanName, singletonObject); 操作,将其曝光,也就是放到二级缓存中。最后删除三级缓存中的值
这样就实现了"曝光"的操作。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized(this.singletonObjects) {
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
-
-
-
第三步:填充属性值
-
this.populateBean(beanName, mbd, instanceWrapper);
-
AOP 切面编程
对于切面编程,更像是触发器,当程序进行到这里,检测到指定的特征后就执行某段代码。
-
引入依赖
-
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.22.1</version>
<scope>runtime</scope>
</dependency>
-
-
直接注解形式
-
@Aspect // 切面(写增强代码的地方,通知 + 切点) -
Advice 类
package com.believesun.spring6.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect // 切面(写增强代码的地方,通知 + 切点)
public class LogAdvice {
// 使用通用切点来代替 常量字符串
// private static final String pointCut = "execution(* com.believesun.spring6.service.UserService.*(..))";
@Pointcut("execution(* com.believesun.spring6.service..*(..))") // 切点
public void genericPointCut(){}
// 前置通知
@Before("genericPointCut()")
public void beforeAdvice(JoinPoint joinPoint){
// 获取 连接点 的签名,什么是签名?签名就是 修饰符列表 返回值类型 方法名 参数
System.out.println(joinPoint.getSignature().getName());
System.out.println("前置通知");
}
// 后置通知
@AfterReturning("genericPointCut()")
public void afterReturningAdvice(){
System.out.println("后置通知");
}
// 环绕通知(最大的范围,在前置之前,在后置之后)
@Around("genericPointCut()")
public void aroundAdvice(ProceedingJoinPoint joinPoint){
// 前环绕代码
System.out.println("前环绕");
// 调用目标代码
try {
joinPoint.proceed();
} catch (Throwable e) {
System.out.println("出现异常后的后环绕");
}
// 后环绕代码
System.out.println("后环绕");
}
// 异常通知
@AfterThrowing("genericPointCut()")
public void throwAdvice(){
System.out.println("异常通知");
}
// 最终通知
@After("genericPointCut()")
public void finallyAdvice(){
System.out.println("最终通知");
}
}
- 配置类,开启自动代理模式
package com.believesun.spring6;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.believesun.spring6")
@EnableAspectJAutoProxy(proxyTargetClass = true) // 开启Aspectj自动代理模式 proxyTargetClass = false时启用的是JDK的代理模式,当为true时启动的是CGLIB的代理模式。
public class SpringConfig {
}