潍坊网站建设如何,安卓版下载,佛山免费建站找哪家,沈阳网页设计公司排名Spring面试重点
AOP
前置通知#xff08;Before#xff09;#xff1a;在⽬标⽅法运行之前运行#xff1b;后置通知#xff08;After#xff09;#xff1a;在⽬标⽅法运行结束之后运行#xff1b;返回通知#xff08;AfterReturning#xff09;#xff1a;在⽬标…Spring面试重点
AOP
前置通知Before在⽬标⽅法运行之前运行后置通知After在⽬标⽅法运行结束之后运行返回通知AfterReturning在⽬标⽅法正常返回之后运行异常通知AfterThrowing在⽬标⽅法出现异常是运行环绕通知Around手动推荐⽬标⽅法运行proceedingJoinPoint.proceed()。
通用AOP日志切面类
!-- springboot-aop 技术 --
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId
/dependencyimport org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;import java.util.Arrays;/*** Title通用日志切面* Description* author WZQ* version 1.0.0* date 2020/12/1*/
Aspect
public class LogAspects {/*** 公共的切⼊入点表达式* 1、本类引⽤* 2、其他的切⾯引⽤* author wzq* date 2018/11/1*/Pointcut(value execution(public int com.wzq.spring.study.service.CalcService.*(..)))public void pointCut() {}/*** 前置通知* pointCut切点指定方法* param joinPoint joinPoint参数⼀一定要出现在参数列列表第⼀一位放在后⾯面会报错*/Before(value pointCut())public void logStart(JoinPoint joinPoint) {String methodName joinPoint.getSignature().getName();Object[] args joinPoint.getArgs();System.out.println(Before--方法前前置通知 ,方法名 methodName 参数列列表是 Arrays.toString(args));}/*** 后置通知*/After(value pointCut())public void logEnd() {System.out.println(After--方法后后置通知);}/*** 返回通知* param joinPoint* param result*/AfterReturning(value pointCut(), returning result)public void logReturn(JoinPoint joinPoint, Object result) {String methodName joinPoint.getSignature().getName();System.out.println(AfterReturning--返回通知方法名 methodName 计算结果 result);}/*** 异常通知* param joinPoint* param exception*/AfterThrowing(value pointCut(), throwing exception)public void logException(JoinPoint joinPoint, Exception exception) {String methodName joinPoint.getSignature().getName();System.out.println(AfterThrowing--异常通知方法名 methodName 异常信息 exception);}/*** 环绕通知* param proceedingJoinPoint* return* throws Throwable*/Around(value pointCut())public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {Object retValue null;System.out.println(Around--我是环绕通知之前AAA);retValue proceedingJoinPoint.proceed();System.out.println(Around--我是环绕通知之后BBB);return retValue;}}import com.wzq.spring.study.service.CalcService;
import com.wzq.spring.study.service.impl.CalcServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;/*** aop* 在程序运⾏期间动态的将某段代码切⼊到指定⽅方法指定位置进⾏运行的编程⽅式* EnableAspectJAutoProxy开启基于注解的aop动态代理** author wzq* date 2018/11/1*/
Configuration
EnableAspectJAutoProxy
public class ConfigOfAOP {/*** 业务逻辑类加⼊入容器中* author wzq* date 2018/11/1*/Beanpublic CalcService calculator() {return new CalcServiceImpl();}/*** 切面类加⼊入容器中* author wzq* date 2018/11/1*/Beanpublic LogAspects logAspects() {return new LogAspects();}
}//也可以不需要此类直接在切面类LogAspects加Component注解注入容器即可因为配置了切点aop注解执行顺序
业务逻辑
public class CalcServiceImpl implements CalcService {Overridepublic int div(int x, int y) {int result x / y;System.out.println(CalcServiceImpl被调用了,我们的计算结果result);return result;}}测试
Resourceprivate CalcService calcService;Testvoid aopTest1() {System.out.println(spring版本 SpringVersion.getVersion()\tSpringBoot版本 SpringBootVersion.getVersion());System.out.println();calcService.div(10,2);}spring5.2.8后正常顺序和异常顺序注意实际是SpringBoot2.3.3版本之后
spring版本5.2.8.RELEASE SpringBoot版本2.3.3.RELEASEAround--我是环绕通知之前AAA
Before--方法前前置通知,方法名div参数列列表是[10, 2]
CalcServiceImpl被调用了,我们的计算结果5
AfterReturning--返回通知方法名div计算结果5
After--方法后后置通知
Around--我是环绕通知之后BBBAround--我是环绕通知之前AAA
Before--方法前前置通知,方法名div参数列列表是[10, 0]
AfterThrowing--异常通知方法名div异常信息java.lang.ArithmeticException: / by zero
After--方法后后置通知java.lang.ArithmeticException: / by zerospring4spring5前期正常顺序和异常顺序
spring版本4.3.13.RELEASE SpringBoot版本1.5.9.RELEASEAround--我是环绕通知之前AAA
Before--方法前前置通知,方法名div参数列列表是[10, 2]
CalcServiceImpl被调用了,我们的计算结果5
Around--我是环绕通知之后BBB
After--方法后后置通知
AfterReturning--返回通知方法名div计算结果5spring版本5.2.2.RELEASE SpringBoot版本2.2.2.RELEASEAround--我是环绕通知之前AAA
Before--方法前前置通知,方法名div参数列列表是[10, 2]
CalcServiceImpl被调用了,我们的计算结果5
Around--我是环绕通知之后BBB
After--方法后后置通知
AfterReturning--返回通知方法名div计算结果5spring版本4.3.13.RELEASE SpringBoot版本1.5.9.RELEASEAround--我是环绕通知之前AAA
Before--方法前前置通知,方法名div参数列列表是[10, 0]
After--方法后后置通知
AfterThrowing--异常通知方法名div异常信息java.lang.ArithmeticException: / by zerojava.lang.ArithmeticException: / by zeroSpring循环依赖
循环依赖多个bean之间相互依赖形成了一个闭环。 比如:A依赖于B、B依赖于c、c依赖于A
通常来说如果问spring容器内部如何解决循环依赖 一定是指默认的单例Bean中属性互相引用的场景
也就是说Spring的循环依赖是Spring容器注入时候出现的问题类似死锁现象 官网说明https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans 官网可知解决循环依赖使用setter注入bean不能使用构造方法注入bean。
我们AB循环依赖问题只要A的注入方式是setter且singleton, 就不会有循环依赖问题
spring容器循环依赖报错演示BeanCurrentlylnCreationException
循环依赖现象在Spring容器中 注入依赖的对象有2种情况构造器方式注入依赖和以set方式注入依赖
构造器方式注入
无法解决循环依赖问题构造器注入没有办法解决循环依赖 你想让构造器注入支持循环依赖是不存在的
Component
public class ServiceA {private ServiceB serviceB;public ServiceA(ServiceB serviceB) {this.serviceB serviceB;}
}Component
public class ServiceB {private ServiceA serviceA;public ServiceB(ServiceA serviceA) {this.serviceA serviceA;}
}/*** 通过构造器的方式注入依赖构造器的方式注入依赖的bean下面两个bean循环依赖** 测试后发现构造器循环依赖是无法解决的*/
public class ClientConstructor {public static void main(String[] args) {//A构造函数依赖B注入,B构造函数依赖A注入,一直反复无法结束new ServiceA(new ServiceB(new ServiceA(new ServiceB()))); ....}
}set方式注入
不会报错
Component
public class ServiceA {private ServiceB b;public ServiceB getB() {return b;}public void setB(ServiceB b) {this.b b;System.out.println(A 里面设置了B);}}Component
public class ServiceB {private ServiceA a;public ServiceA getA() {return a;}public void setA(ServiceA a) {this.a a;System.out.println(B 里面设置了A);}}public class ClientSet {public static void main(String[] args) {//创建serviceAServiceA serviceA new ServiceA();//创建serviceBServiceB serviceB new ServiceB();//将serviceA注入到serviceB中serviceB.setServiceA(serviceA);//将serviceB注入到serviceA中serviceA.setServiceB(serviceB);}
}spring容器
默认的单例(singleton)的set方式注入场景是支持循环依赖的不报错
原型(Prototype)的场景是不支持循环依赖的报错多例下会报循环依赖异常BeanCurrentlylnCreationException
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/tx!--1.spring容器默认的单例模式可以解决循环引用单例默认支持2.spring容器原型依赖模式scopeprototype多例模式下不能解决循环引用--!--depends-on 的意思就是当前这个bean如果要完成先看depends-on指定的bean是否已经完成了初始化--!--scopeprototype代表每次都要新建一次对象--bean ida classcom.wzq.spring.study.service.impl.ServiceA scopesingletonproperty nameb refb//beanbean idb classcom.wzq.spring.study.service.impl.ServiceB scopesingletonproperty namea refa//bean/beans/*** nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:* Error creating bean with name a: 578624778* Requested bean is currently in creation: Is there an unresolvable circular reference?*** 只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题因为单例的时候只有一份随时复用那么就放到缓存里面* 而多例的bean每次从容器中获取都是一个新的对象都会重新创建 * 所以非单例的bean是没有缓存的不会将其放到三级缓存中。*/
Test
public void deTest() { ApplicationContext context new ClassPathXmlApplicationContext(applicationContext.xml);ServiceA a context.getBean(a, ServiceA.class); ServiceB b context.getBean(b, ServiceB.class);System.out.println(a); System.out.println(b);
}spring三级缓存
DefaultSingletonBeanRegistry(re zi si g redʒɪstri)
所谓的三级缓存其实就是spring容器内部用来解决循环依赖问题的三个map 第一级缓存也叫单例池singletonObjects:存放已经经历了完整生命周期的Bean对象第二级缓存: earlySingletonObjects存放早期暴露出来的Bean对象Bean的生命周期未结束属性还未填充完整第三级缓存: MapString, ObiectFactory? singletonFactories存放可以生成Bean的工厂 spring源码分析
实例化/初始化
实例化是堆内存中申请一块内存空间初始化是属性填充
spring利用单例set注入解决循环依赖的三级缓存四大方法 四大方法
getSingleton希望从容器里面获得单例的bean没有的话doCreateBean: 没有就创建beanpopulateBean: 创建完了以后要填充属性addSingleton: 填充完了以后再添加到容器进行使用
三级缓存
第一层singletonObjects存放的是已经初始化好了的Bean,第二层earlySingletonObjects存放的是实例化了但是未初始化的Bean第三层singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类而是A类产生的Bean 三级缓存简单理解AB单例依赖set注入
A创建过程中需要B于是A将自己放到三级缓存里面去实例化BB实例化的时候发现需要A于是B先查一级缓存没有再查二级缓存还是没有再查三级缓存找到了A然后把三级缓存里面的这个A放到二级缓存里面实例但未初始化并删除三级缓存里面的AB顺利初始化完毕将自己放到一级缓存里面此时B里面的A依然是创建中状态然后回来接着创建A此时B已经创建结束直接从一级缓存里面拿到B然后完成创建并将A自己放到一级缓存里面。
总结spring解决循环依赖
Spring创建bean主要分为两个步骤创建原始bean对象接着去填充对象属性和初始化。
每次创建bean之前我们都会从缓存中查下有没有该bean因为是单例只能有一个。
当我们创建 beanA的原始对象后并把它放到三级缓存中接下来就该填充对象属性了这时候发现依赖了beanB接着就又去创建beanB同样的流程创建完 beanB填充属性时又发现它依赖了beanA又是同样的流程。
不同的是 这时候可以在三级缓存中查到刚放进去的原始对象beanA所以不需要继续创建用它注入beanB完成beanB的创建。
既然 beanB创建好了所以beanA就可以完成填充属性的步骤了接着执行剩下的逻辑闭环完成 Spring解决循环依赖依靠的是Bean的“中间态这个概念而这个中间态指的是已经实例化但还没初始化的状态……半成品。
实例化的过程又是通过构造器创建的如果A还没创建好出来怎么可能提前曝光所以构造器的循环依赖无法解决。
Spring为了解决单例的循环依赖问题使用了三级缓存 其中一级缓存为单例池( singletonObjects) 二级缓存为提前曝光对象( earlySingletonObjects) 三级缓存为提前曝光对象工厂( singletonFactories。
假设A、B循环引用实例化A的时候就将其放入三级缓存中接着填充属性的时候发现依赖了B同样的流程也是实例化后放入三级缓存接着去填充属性时又发现自己依赖A这时候从缓存中查找到早期暴露的A没有AOP代理的话直接将A的原始对象注入B完成B的初始化后进行属性填充和初始化这时候B完成后就去完成剩下的A的步骤如果有AOP代理就进行AOP处理获取代理后的对象A注入B走剩下的流程。
源码流程图如下 debug步骤–》Spring解决循环依赖过程
调用doGetBean()方法想要获取beanA于是调用getSingleton()方法从缓存中查找beanA在getSingleton()方法中从一级缓存中查找没有返回nulldoGetBean()方法中获取到的beanA为null于是走对应的处理逻辑调用getSingleton()的重载方法参数为ObjectFactory的)在getSingleton()方法中先将beanA_name添加到一个集合中用于标记该bean正在创建中。然后回调匿名内部类的creatBean方法进入AbstractAutowireCapableBeanFactory#doCreateBean先反射调用构造器创建出beanA的实例然后判断。是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中〈即是否在第四步的集合中)。判断为true则将beanA添加到【三级缓存】中对beanA进行属性填充此时检测到beanA依赖于beanB于是开始查找beanB调用doGetBean()方法和上面beanA的过程一样到缓存中查找beanB没有则创建然后给beanB填充属性此时beanB依赖于beanA调用getsingleton()获取beanA依次从一级、二级、三级缓存中找此时从三级缓存中获取到beanA的创建工厂通过创建工厂获取到singletonObject此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA这样beanB就获取到了beanA的依赖于是beanB顺利完成实例化并将beanA从三级缓存移动到二级缓存中随后beanA继续他的属性填充工作此时也获取到了beanBbeanA也随之完成了创建回到getsingleton()方法中继续向下执行将beanA从二级缓存移动到一级缓存中