【吃透Java手写】Spring(下)-AOP-事务及传播原理

embedded/2024/10/21 3:52:18/

【吃透Java手写】Spring(下)AOP-事务及传播原理

  • 6 AOP模拟实现
    • 6.1 AOP工作流程
    • 6.2 定义dao接口与实现类
    • 6.3 初始化后逻辑
    • 6.4 原生Spring的方法
      • 6.4.1 实现类
      • 6.4.2 定义通知类,定义切入点表达式、配置切面
      • 6.4.3 在配置类中进行Spring注解包扫描和开启AOP功能
      • 6.4.4 测试类和运行结果
      • 6.4.5 现象
    • 6.5 AOP原理解析
      • 6.5.1 doCreateBean
      • 6.5.2 postProcessAfterInitialization
      • 6.5.3 getAdvicesAndAdvisorsForBean
        • 6.5.3.1 findCandidateAdvisors
        • 6.5.3.2 findAdvisorsThatCanApply
        • 6.5.3.3 extendAdvisors
      • 6.5.4 createProxy
        • 6.5.4.1 buildProxy
      • 6.5.5 JDK 动态代理
      • 6.5.6 CGLIB 动态代理
  • 7 事务及传播机制


AOP_5">6 AOP模拟实现

面向切面编程,通过预编译方 式和运行期动态代理实现程序功能的统一维护的一种技术。在不惊动原始设计的基础上为其进行功能增强强。

简单的说就是在不改变方法源代码的基础上对方法进行功能增强。

使用AOP从Spring容器中取出的对象应该是一个代理对象,先执行代理逻辑,再执行业务逻辑。

AOP_13">6.1 AOP工作流程

  • Spring容器启动

  • 读取所有切面配置中的切入点

  • 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点

​ 匹配失败,创建原始对象

​ 匹配成功,创建原始对象(目标对象)的代理对象

  • 获取bean执行方法

​ 获取的bean是原始对象时,调用方法并执行,完成操作

​ 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

6.2 定义dao接口与实现类

创捷com.zhouyu.service.UserService接口

java">public interface UserService {public void test();
}

修改其实现类

java">@Component("userService")
//@Scope("prototype")
public class UserServiceImpl implements UserService {@Autowiredprivate OrderService orderService;//private String beanName;//BeanNameAware模拟实现
//    @Override
//    public void setBeanName(String name) {
//        beanName = name;
//    }//InitializingBean模拟实现
//    @Override
//    public void afterPropertiesSet() throws Exception {
//        System.out.println("初始化方法进行时!!!");
//    }public void test() {System.out.println(orderService);//System.out.println("userService beanName: " + beanName);}
}

6.3 初始化后逻辑

AOP是在初始化后进行操作的,也就是在BeanPostProcessor接口的实现类ZhouyuBeanPostProcessor中的postProcessAfterInitialization方法来进行AOP逻辑的

java">@Component("zhouyuBeanPostProcessor")
public class ZhouyuBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception {/*      System.out.println(beanName+"初始化方法之前");if(beanName.equals("userService")) {System.out.println("userService初始化方法之前");}*/return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {//System.out.println(beanName+"初始化方法之后");//匹配对应的bean进行代理。。。if(beanName.equals("userService")) {//返回一个代理对象Object proxyInstance = Proxy.newProxyInstance(ZhouyuBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), (proxy, method, args) -> {//System.out.println("代理逻辑");//找切入点。。。return method.invoke(bean, args);});return proxyInstance;}return bean;}
}

输出

代理逻辑
com.zhouyu.service.OrderService@5d099f62

先执行代理逻辑,再执行原型逻辑

6.4 原生Spring的方法

6.4.1 实现类

OrderService

java">@Component("orderService")
public class OrderService {
}

UserService

java">@Component("userService")
public class UserService {@Autowiredprivate OrderService orderService;public void test() {System.out.println(orderService);}
}

6.4.2 定义通知类,定义切入点表达式、配置切面

创建com.zhouyu.aspect.ZhouyuAspect

java">@Aspect
@Component
public class ZhouyuAspect {@Before("execution(public void com.zhouyu.service.UserService.test(..))")public void before(JoinPoint joinPoint) {System.out.println("before");}@After("execution(* com.zhouyu.service.UserService.test(..))")public void after(JoinPoint joinPoint) {System.out.println("after");}
}

AOP_158">6.4.3 在配置类中进行Spring注解包扫描和开启AOP功能

java">@ComponentScan("com.zhouyu")
@EnableAspectJAutoProxy
public class AppConfig {
}

6.4.4 测试类和运行结果

java">public class Test {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);UserService userService = (UserService)context.getBean("userService");userService.test();}
}

6.4.5 现象

在这里插入图片描述

可以看到,获取到的 UserService 是一个代理对象,那么注入到 Spring 容器中的 UserService,为什么在获取的时候变成了一个代理对象,而不是原本的 UserService 了呢?

AOP_186">6.5 AOP原理解析

Spring Bean 的生命周期分为四个阶段,分别是:

  1. 实例化。
  2. 属性赋值。
  3. 初始化。
  4. 销毁。

6.5.1 doCreateBean

AOP 代理对象的创建是在初始化这个过程中完成的一共是执行了四个方法,也都是非常常见的 Bean 初始化方法

  1. invokeAwareMethods:执行 Aware 接口下的 Bean。
  2. applyBeanPostProcessorsBeforeInitialization:执行 BeanPostProcessor 中的前置方法。
  3. invokeInitMethods:执行 Bean 的初始化方法 init。
  4. applyBeanPostProcessorsAfterInitialization:执行 BeanPostProcessor 中的后置方法。

这四个方法我们在2、3、4、5已经讲解过了,不再赘述

创建的 AOP 对象基本上都是在 applyBeanPostProcessorsAfterInitialization 中进行处理的

java">@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)throws BeansException {Object result = existingBean;for (BeanPostProcessor processor : getBeanPostProcessors()) {Object current = processor.postProcessAfterInitialization(result, beanName);if (current == null) {return result;}result = current;}return result;
}

6.5.2 postProcessAfterInitialization

BeanPostProcessor processor : getBeanPostProcessors()拿出所有的BeanPostProcessor的实现类

BeanPostProcessor 有一个实现类 AbstractAutoProxyCreator,在 AbstractAutoProxyCreator 的 postProcessAfterInitialization 方法中,进行了 AOP 的处理:

java">@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// Create proxy if we have advice.Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}
  1. 首先会尝试去缓存中获取代理对象,如果缓存中没有的话,则会调用 wrapIfNecessary 方法进行 AOP 的创建
java">public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}
  1. 如果是一个切面 Bean 的话,则执行第一个方法 isInfrastructureClass 就可以返回 true 了。

    如果是一个普通 Bean 的话,则第一个方法会返回 false,此时就会执行第二个方法 shouldSkip(虽然该方法也会返回 false)

java">if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}
  1. 来看 isInfrastructureClass 方法,先来看切面 Bean 是怎么处理的,重点关注 isInfrastructureClass 方法,这个方法用来判断当前类是否是一个 Aspect:
java">@Override
protected boolean isInfrastructureClass(Class<?> beanClass) {return (super.isInfrastructureClass(beanClass) ||(this.aspectJAdvisorFactory != null && this.aspectJAdvisorFactory.isAspect(beanClass)));
}
  1. 这里的判断主要是两方面:

    4.1 调用父类的方法去判断当前类是否和 AOP 相关:

    java">protected boolean isInfrastructureClass(Class<?> beanClass) {boolean retVal = Advice.class.isAssignableFrom(beanClass) ||Pointcut.class.isAssignableFrom(beanClass) ||Advisor.class.isAssignableFrom(beanClass) ||AopInfrastructureBean.class.isAssignableFrom(beanClass);return retVal;
    }
    

    4.2 调用 aspectJAdvisorFactory.isAspect 方法去判断当前类是否包含 @Aspect 注解

    AbstractAspectJAdvisorFactory#isAspect:

    java">@Override
    public boolean isAspect(Class<?> clazz) {return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
    }
    private boolean hasAspectAnnotation(Class<?> clazz) {return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
    }
    

    如果我们的类上包含 @Aspect 注解,那么最终就会在将当前类名加入到 advisedBeans Map 中,在 advisedBeans 这个 Map 中,key 是当前 Bean 的名称,value 则是 false 是一个标记,表示当前类不需要生成代理类。

  2. 普通Bean

    很明显 isInfrastructureClass 方法会返回 false,这就会导致 shouldSkip 方法去执行

6.5.3 getAdvicesAndAdvisorsForBean

就是查找各种 Advice(通知/增强) 和 Advisor(切面)

AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean:

java">@Override
@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);if (advisors.isEmpty()) {return DO_NOT_PROXY;}return advisors.toArray();
}

从这里可看到,这个方法主要就是调用 findEligibleAdvisors 去获取到所有的切面,继续:

java">protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {List<Advisor> candidateAdvisors = findCandidateAdvisors();List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {eligibleAdvisors = sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;
}

这里一共有三个主要方法:

  • findCandidateAdvisors:这个方法是查询到所有候选的 Advisor,说白了,就是把项目启动时注册到 Spring 容器中所有切面都找到,由于一个 Aspect 中可能存在多个 Advice,每个 Advice 最终都能封装为一个 Advisor,所以在具体查找过程中,找到 Aspect Bean 之后,还需要遍历 Bean 中的方法。
  • findAdvisorsThatCanApply:这个方法主要是从上个方法找到的所有切面中,根据切点过滤出来能够应用到当前 Bean 的切面。
  • extendAdvisors:这个是添加一个 DefaultPointcutAdvisor 切面进来,这个切面使用的 Advice 是 ExposeInvocationInterceptor,ExposeInvocationInterceptor 的作用是用于暴露 MethodInvocation 对象到 ThreadLocal 中,如果其他地方需要使用当前的 MethodInvocation 对象,直接通过调用 currentInvocation 方法取出即可。
6.5.3.1 findCandidateAdvisors

AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors:

java">@Override
protected List<Advisor> findCandidateAdvisors() {List<Advisor> advisors = super.findCandidateAdvisors();if (this.aspectJAdvisorsBuilder != null) {advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());}return advisors;
}

这个方法的关键在于通过 buildAspectJAdvisors 构建出所有的切面,这个方法有点复杂:

java">@Override
protected List<Advisor> findCandidateAdvisors() {List<Advisor> advisors = super.findCandidateAdvisors();if (this.aspectJAdvisorsBuilder != null) {advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());}return advisors;
}

这个方法的关键在于通过 buildAspectJAdvisors 构建出所有的切面,这个方法有点复杂:

java">public List<Advisor> buildAspectJAdvisors() {List<String> aspectNames = this.aspectBeanNames;if (aspectNames == null) {synchronized (this) {aspectNames = this.aspectBeanNames;if (aspectNames == null) {List<Advisor> advisors = new ArrayList<>();aspectNames = new ArrayList<>();String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);for (String beanName : beanNames) {if (!isEligibleBean(beanName)) {continue;}// We must be careful not to instantiate beans eagerly as in this case they// would be cached by the Spring container but would not have been weaved.Class<?> beanType = this.beanFactory.getType(beanName, false);if (beanType == null) {continue;}if (this.advisorFactory.isAspect(beanType)) {aspectNames.add(beanName);AspectMetadata amd = new AspectMetadata(beanType, beanName);if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {MetadataAwareAspectInstanceFactory factory =new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);if (this.beanFactory.isSingleton(beanName)) {this.advisorsCache.put(beanName, classAdvisors);}else {this.aspectFactoryCache.put(beanName, factory);}advisors.addAll(classAdvisors);}else {// Per target or per this.if (this.beanFactory.isSingleton(beanName)) {throw new IllegalArgumentException("Bean with name '" + beanName +"' is a singleton, but aspect instantiation model is not singleton");}MetadataAwareAspectInstanceFactory factory =new PrototypeAspectInstanceFactory(this.beanFactory, beanName);this.aspectFactoryCache.put(beanName, factory);advisors.addAll(this.advisorFactory.getAdvisors(factory));}}}this.aspectBeanNames = aspectNames;return advisors;}}}if (aspectNames.isEmpty()) {return Collections.emptyList();}List<Advisor> advisors = new ArrayList<>();for (String aspectName : aspectNames) {List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);if (cachedAdvisors != null) {advisors.addAll(cachedAdvisors);}else {MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);advisors.addAll(this.advisorFactory.getAdvisors(factory));}}return advisors;
}

这个方法第一次进来的时候,aspectNames 变量是没有值的,所以会先进入到 if 分支中,给 aspectNames 和 aspectBeanNames 两个变量赋值。具体过程就是首先调用 BeanFactoryUtils.beanNamesForTypeIncludingAncestors 方法,去当前容器以及当前容器的父容器中,查找到所有的 beanName,将返回的数组赋值给 beanNames 变量,然后对 beanNames 进行遍历。

遍历时,首先调用 isEligibleBean 方法,这个方法是检查给定名称的 Bean 是否符合自动代理的条件的,接下来根据 beanName,找到对应的 bean 类型 beanType,然后调用 advisorFactory.isAspect 方法去判断这个 beanType 是否是一个 Aspect。

如果当前 beanName 对应的 Bean 是一个 Aspect,那么就把 beanName 添加到 aspectNames 集合中,并且把 beanName 和 beanType 封装为一个 AspectMetadata 对象。

接下来会去判断 kind 是否为 SINGLETON,这个默认都是 SINGLETON,所以这里会进入到分支中,进来之后,会调用 this.advisorFactory.getAdvisors 方法去 Aspect 中找到各种通知和切点并封装成 Advisor 对象返回,由于一个切面中可能定义多个通知,所以最终返回的 Advisor 是一个集合,最后把找到的 Advisor 集合存入到 advisorsCache 缓存中。

再从 advisorsCache 中找到某一个 aspect 对应的所有 Advisor,并将之存入到 advisors 集合中,然后返回集合。

6.5.3.2 findAdvisorsThatCanApply

接下来 findAdvisorsThatCanApply 方法主要是从众多的 Advisor 中,找到能匹配上当前 Bean 的 Advisor,小伙伴们知道,每一个 Advisor 都包含一个切点 Pointcut,不同的切点意味着不同的拦截规则,所以现在需要进行匹配,检查当前类需要和哪个 Advisor 匹配:

java">protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {ProxyCreationContext.setCurrentProxiedBeanName(beanName);try {return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);}finally {ProxyCreationContext.setCurrentProxiedBeanName(null);}
}

这里实际上就是调用了静态方法 AopUtils.findAdvisorsThatCanApply 去查找匹配的 Advisor:

java">public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {if (candidateAdvisors.isEmpty()) {return candidateAdvisors;}List<Advisor> eligibleAdvisors = new ArrayList<>();for (Advisor candidate : candidateAdvisors) {if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {eligibleAdvisors.add(candidate);}}boolean hasIntroductions = !eligibleAdvisors.isEmpty();for (Advisor candidate : candidateAdvisors) {if (candidate instanceof IntroductionAdvisor) {// already processedcontinue;}if (canApply(candidate, clazz, hasIntroductions)) {eligibleAdvisors.add(candidate);}}return eligibleAdvisors;
}

这个方法中首先会去判断 Advisor 的类型是否是 IntroductionAdvisor 类型,IntroductionAdvisor 类型的 Advisor 只能在类级别进行拦截,灵活度不如 PointcutAdvisor,所以我们一般都不是 IntroductionAdvisor,因此这里最终会走入到最后一个分支中:

java">public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {if (advisor instanceof IntroductionAdvisor ia) {return ia.getClassFilter().matches(targetClass);}else if (advisor instanceof PointcutAdvisor pca) {return canApply(pca.getPointcut(), targetClass, hasIntroductions);}else {// It doesn't have a pointcut so we assume it applies.return true;}
}

IntroductionAdvisor 类型的 Advisor 只需要调用 ClassFilter 过滤一下就行了。而 PointcutAdvisor 类型的 Advisor 则会继续调用 canApply 方法进行判断:

java">public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {if (!pc.getClassFilter().matches(targetClass)) {return false;}MethodMatcher methodMatcher = pc.getMethodMatcher();if (methodMatcher == MethodMatcher.TRUE) {// No need to iterate the methods if we're matching any method anyway...return true;}IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;if (methodMatcher instanceof IntroductionAwareMethodMatcher iamm) {introductionAwareMethodMatcher = iamm;}Set<Class<?>> classes = new LinkedHashSet<>();if (!Proxy.isProxyClass(targetClass)) {classes.add(ClassUtils.getUserClass(targetClass));}classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));for (Class<?> clazz : classes) {Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);for (Method method : methods) {if (introductionAwareMethodMatcher != null ?introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :methodMatcher.matches(method, targetClass)) {return true;}}}return false;
}

这里就是先按照类去匹配,匹配通过则继续按照方法去匹配,方法匹配器要是设置的 true,那就直接返回 true 就行了,否则就加载当前类,也就是 targetClass,然后遍历 targetClass 中的所有方法,最后调用 introductionAwareMethodMatcher.matches 方法去判断方法是否和切点契合。

就这样,我们就从所有的 Advisor 中找到了所有和当前类匹配的 Advisor 了。

6.5.3.3 extendAdvisors

添加一个 DefaultPointcutAdvisor 切面进来,这个切面使用的 Advice 是 ExposeInvocationInterceptor,ExposeInvocationInterceptor 的作用是用于暴露 MethodInvocation 对象到 ThreadLocal 中,如果其他地方需要使用当前的 MethodInvocation 对象,直接通过调用 currentInvocation 方法取出即可。

6.5.4 createProxy

通过 getAdvicesAndAdvisorsForBean 方法,我们已经找到了适合我们的 Advisor,接下来继续看 createProxy 方法,这个方法用来创建一个代理对象:

java">protected Object createProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {return buildProxy(beanClass, beanName, specificInterceptors, targetSource, false);
}
private Object buildProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) {if (this.beanFactory instanceof ConfigurableListableBeanFactory clbf) {AutoProxyUtils.exposeTargetClass(clbf, beanName, beanClass);}ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);if (proxyFactory.isProxyTargetClass()) {// Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.for (Class<?> ifc : beanClass.getInterfaces()) {proxyFactory.addInterface(ifc);}}}else {// No proxyTargetClass flag enforced, let's apply our default checks...if (shouldProxyTargetClass(beanClass, beanName)) {proxyFactory.setProxyTargetClass(true);}else {evaluateProxyInterfaces(beanClass, proxyFactory);}}Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);customizeProxyFactory(proxyFactory);proxyFactory.setFrozen(this.freezeProxy);if (advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}// Use original ClassLoader if bean class not locally loaded in overriding class loaderClassLoader classLoader = getProxyClassLoader();if (classLoader instanceof SmartClassLoader smartClassLoader && classLoader != beanClass.getClassLoader()) {classLoader = smartClassLoader.getOriginalClassLoader();}return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));
}
6.5.4.1 buildProxy
java">private Object buildProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) {//...Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);//...return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));
}

这里有一个 buildAdvisors 方法,这个方法是用来处理 Advisor 的,我们自定义的 DogIntroductionAdvisor 将在这里被读取进来,然后将之添加到 proxyFactory 对象中,在添加的过程中,会进行一些额外的处理,proxyFactory#addAdvisors 最终会来到 AdvisedSupport#addAdvisors 方法中:

java">public void addAdvisors(Collection<Advisor> advisors) {if (!CollectionUtils.isEmpty(advisors)) {for (Advisor advisor : advisors) {if (advisor instanceof IntroductionAdvisor introductionAdvisor) {validateIntroductionAdvisor(introductionAdvisor);}this.advisors.add(advisor);}adviceChanged();}
}

在这里会遍历所有的 Advisor,判断类型是不是 IntroductionAdvisor 类型的,我们自定义的 DogIntroductionAdvisor 恰好就是 IntroductionAdvisor 类型的,所以会进一步调用 validateIntroductionAdvisor 方法,如下:

java">private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {advisor.validateInterfaces();Class<?>[] ifcs = advisor.getInterfaces();for (Class<?> ifc : ifcs) {addInterface(ifc);}
}
public void addInterface(Class<?> intf) {if (!this.interfaces.contains(intf)) {this.interfaces.add(intf);adviceChanged();}
}

advisor.getInterfaces(); 实际上就调用到我们自定义的 DogIntroductionAdvisor 中的 getInterfaces 方法了,所以这里会返回 Animal 接口,然后这里会把 Animal 接口存入到 interfaces 这个变量中,将来在生成 AOP 对象的时候会用到。

现在回到 buildProxy 方法中,该方法最终会执行到 proxyFactory.getProxy 方法,该方法最终执行的时候,要么是 JDK 动态代理,要么是 CGLIB 动态代理,我们分别来说一下。

6.5.5 JDK 动态代理

如果是 JDK 动态代理,那么 proxyFactory.getProxy 方法就需要构建一个 JdkDynamicAopProxy 出来,如下:

java">public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {this.advised = config;this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
}

最后,调用 JdkDynamicAopProxy#getProxy 方法生成代理对象,如下:

java">@Override
public Object getProxy(@Nullable ClassLoader classLoader) {return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}

生成的代理对象不仅仅是UserServcie的实例,也是 SpringProxy 等的实例。

6.5.6 CGLIB 动态代理

java">public CglibAopProxy(AdvisedSupport config) throws AopConfigException {this.advised = config;this.advisedDispatcher = new AdvisedDispatcher(this.advised);
}
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {return buildProxy(classLoader, false);
}
private Object buildProxy(@Nullable ClassLoader classLoader, boolean classOnly) {//...enhancer.setSuperclass(proxySuperClass);enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));//...// Generate the proxy class and create a proxy instance.return (classOnly ? createProxyClass(enhancer) : createProxyClassAndInstance(enhancer, callbacks));
}

从 advised 中提取出来接口设置进去,advised 也是在 CglibAopProxy 对象构建的时候传入进来的。

就是在生成代理对象的时候,把我们在 Advisor 中设置好的接口也考虑进去,这样生成的代理对象同时也是该接口的实现类,当然,在我们提供的 Advice 中,必须也要实现该接口,否则代理对象执行接口中的方法,找不到具体实现的时候就会报错了。

7 事务及传播机制

7.1 引入依赖

<dependencies><!--spring核心依赖,会将spring-aop传递进来--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.19</version></dependency><!--切入点表达式依赖,目的是找到切入点方法,也就是找到要增强的方法--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>6.0.13</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>6.0.13</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency>
</dependencies>

JDBC_778">7.2 配置JDBC

在com.zhouyu.AppConfig配置JDBC

java">@ComponentScan("com.zhouyu")
@EnableTransactionManagement
public class AppConfig {@Beanpublic JdbcTemplate jdbcTemplate() {return new JdbcTemplate(dataSource());}@Beanpublic PlatformTransactionManager transactionManager() {DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource());return dataSourceTransactionManager;}@Beanpublic DataSource dataSource() {DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/tuling?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8");driverManagerDataSource.setUsername("root");driverManagerDataSource.setPassword("123sjbsjb");return driverManagerDataSource;}
}

tuling中有一张t1的表

7.3 修改UserService逻辑

修改com.zhouyu.service.UserService

java">@Component("userService")
public class UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;@Transactionalpublic void test() {jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1')");throw new NullPointerException("test");}
}

7.4 Test

java">public class Test {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);UserService userService = (UserService)context.getBean("userService");userService.test();}
}

输出当然报空指针错误。但是我们查看数据库虽然已经加上了@Transactional事务,但是数据库还是插入成功。并没有我们希望的回滚。

在这里插入图片描述

这是因为与我们的@Configuration有关,当我们加上@Configuration时,就能成功回滚

java">@ComponentScan("com.zhouyu")
@EnableTransactionManagement
@Configuration
public class AppConfig {

在这里插入图片描述

Configuration_857">7.5 @Configuration

在Spring中,@Configuration注解用于标记一个类,表明这个类是一个配置类,它可以包含@Bean注解,用于定义Spring容器中的bean。当一个类被@Configuration注解标记后,Spring会在运行时使用CGLIB(Code Generation Library)创建该类的代理对象,并将这个代理对象纳入Spring容器的管理中。

对于被@Configuration注解标记的类中定义的@Bean方法,Spring会在容器启动时调用这些方法,将它们的返回值纳入到Spring容器中管理,并将代理对象的实例化过程放在了Spring容器的内部。

由于Spring容器在创建代理对象时会进行一些额外的处理(如事务增强、AOP增强等),因此获取到的代理对象实际上是Spring容器管理的单例bean实例。即使在多次调用获取代理对象的方法时,也会返回同一个代理对象实例,因为Spring容器中管理的是单例bean实例,保证了获取的代理对象是同一个。

所以,当一个带有@Configuration注解的类中定义了一个被@Bean注解标记的方法,并且这个方法返回一个代理对象时,Spring会确保在整个应用程序中只有一个实例被创建和管理,从而保证了获取的代理对象是同一个。

在com.zhouyu.AppConfig中

java">@ComponentScan("com.zhouyu")
@EnableTransactionManagement
@Configuration
public class AppConfig {@Beanpublic JdbcTemplate jdbcTemplate() {return new JdbcTemplate(dataSource());}@Beanpublic PlatformTransactionManager transactionManager() {DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource());return dataSourceTransactionManager;}@Beanpublic DataSource dataSource() {DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/tuling?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8");driverManagerDataSource.setUsername("root");driverManagerDataSource.setPassword("123sjbsjb");return driverManagerDataSource;}
}

@Configuration保证获取的DataSource都来自于相同的代理对象,如果没有@Configuration注解,就意味着这个类不再是一个配置类,也就不再由Spring容器进行特殊处理,而是一个普通的Java类。在这种情况下,如果这个类中的某个方法返回一个代理对象,那么这个代理对象不会被纳入Spring容器的管理中,也不会被当作单例bean实例来对待。每次调用这个方法,都会创建一个新的代理对象实例。因此,即使你在多个地方获取这个代理对象,每次都会得到一个新的实例,而不是同一个实例。

7.6 事务传播逻辑

如果在UserService中添加一个方法

java">@Component("userService")
public class UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;@Transactionalpublic void test() {jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1')");test2();}@Transactional(propagation = Propagation.NEVER)public void test2() {jdbcTemplate.execute("insert into t1 values(2,2,2,2,'2')");}
}

Propagation.NEVER表示,如果当前存在一个事务,则不应该在该方法中开启一个新的事务。如果方法被调用时已经存在一个事务,则会抛出一个异常。这种传播行为通常用于要求方法在没有事务的环境下执行,以避免创建新的事务

但是我们运行发现还是正常进行插入的,这是为什么呢?

在这里插入图片描述

因为

java">@Transactional
public void test() {jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1')");test2();
}

调用test2();是普通对象userService进行调用的,自然不会与事务控制什么事,只有代理对象去调用test2才有额外的切面逻辑。

如果想要代理对象调用test2,有两种方法

  1. 创建一个新的Bean对象,把test2作为对象的方法,然后让UserService植入新的Bean对象,从而达到使用代理对象调用test2的逻辑
  2. UserService自己注入自己
java">@Component("userService")
public class UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;@Autowiredprivate UserService userService;@Transactionalpublic void test() {jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1')");userService.test2();}@Transactional(propagation = Propagation.NEVER)public void test2() {jdbcTemplate.execute("insert into t1 values(2,2,2,2,'2')");}
}

这样就保证了test2()是被代理对象所调用的了。


http://www.ppmy.cn/embedded/37228.html

相关文章

Spring IOC 和 AOP

相关注解可参考&#xff1a;http://t.csdnimg.cn/yfUKt 1.什么是IOC和AOP Spring框架中的IOC&#xff08;Inverse of Control&#xff0c;控制反转&#xff09;和AOP&#xff08;Aspect-Oriented Programming&#xff0c;面向切面编程&#xff09;是其两大核心特性&#xff0c…

(CDA数据分析师笔记)第六章 业务分析方法五

产品库存类指标 库存手进货与销售双方向的影响。 期初库存&#xff1a;单位时间的起点处库存量。 期末库存&#xff1a;单位时间的终点处库存量。 常见指标&#xff1a; 库存数量与库存金额&#xff1a;可以是某时点或者某时段平均值。 有两个计算方法&#xff1a; 计算方…

leetcode 547.省份数量

思路&#xff1a;dfs 或者这道题用bfs也是可以的。 这道题有点迷惑性&#xff0c;这里的数组给的是无向图的数组&#xff0c;而并不是地图&#xff0c;这里需要着重注意一下。 而后&#xff0c;这里的状态数组st没必要是二维的&#xff0c;我们并不会去遍历所给的数组&#…

如何根据索引删除数组中的元素,并保证删除的正确性

使用 splice() 方法来删除这些索引处的数据 var array [1, 2, 3, 4, 5]; var indexesToDelete [1, 3]; // 需要删除的索引// 将需要删除的索引按照从大到小的顺序排序&#xff0c;以避免删除元素后索引发生变化 indexesToDelete.sort((a, b) > b - a);// 遍历需要删除的索…

计算机行业AI前沿报告:混合专家模型技术(MoE)

今天分享的是****AI系列深度研究报告&#xff1a;《计算机行业AI前沿报告&#xff1a;混合专家模型技术(MoE)》。&#xff08;报告出品方&#xff1a;中信建投证券&#xff09; 精选内容来源公众&#xff1a;见鹿报告 报告共计: 24页 [报告内容摘要如下] **•核心观点&…

JavaScript百炼成仙自学笔记——2

一、循环遍历&#xff1a; 方式一 for(var i0;i<10;i){console.log(i); }方式二 var i 0; while(i < 100){console.log(i);i; }细看代码就是 先定义变量i&#xff0c;再执行{}中的代码&#xff0c;最后改循环变量的值 二、遍历 什么事遍历&#xff1f; 什么时候会用…

UE5 UMG

锚点 切换面板 先将制作好的面板放在屏幕外面&#xff0c;然后点击播放动画按钮利用位移动画将panel移动到屏幕中间 背包 创建物品的结构体&#xff0c;储存物品的信息 创建自定义事件&#xff0c;初始化背包格子的图片显示&#xff0c;然后文本直接绑定就可以&#xff0c;可…

资料总结分享:SAM,bam,bed文件格式

目录 sam文件 bam文件 bed 文件 sam文件 SAM&#xff08;Sequence Alignment/Map&#xff09;文件是存储测序数据比对结果的一种常见格式。SAM文件通常用于存储DNA或RNA测序数据在参考基因组上的比对结果。 SAM文件由多行文本组成&#xff0c;每一行代表一个比对结果。SAM文…