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

【吃透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 事务及传播机制
    • 7.1 引入依赖
    • 7.2 配置JDBC
    • 7.3 修改UserService逻辑
    • 7.4 Test
    • 7.5 @Configuration
    • 7.6 事务传播逻辑


6 AOP模拟实现

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

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

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

6.1 AOP工作流程

  • Spring容器启动

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

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

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

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

  • 获取bean执行方法

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

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

6.2 定义dao接口与实现类

创捷com.zhouyu.service.UserService接口

public interface UserService {public void test();
}

修改其实现类

@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逻辑的

@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

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

UserService

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

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

创建com.zhouyu.aspect.ZhouyuAspect

@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");}
}

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

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

6.4.4 测试类和运行结果

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 了呢?

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 中进行处理的

@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 的处理:

@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 的创建
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)

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

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

    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:

    @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:

@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 去获取到所有的切面,继续:

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:

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

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

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

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

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 匹配:

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:

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,因此这里最终会走入到最后一个分支中:

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 方法进行判断:

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 方法,这个方法用来创建一个代理对象:

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
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 方法中:

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 方法,如下:

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 出来,如下:

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

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

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

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

6.5.6 CGLIB 动态代理

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>

7.2 配置JDBC

在com.zhouyu.AppConfig配置JDBC

@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

@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

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时,就能成功回滚

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

在这里插入图片描述

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中

@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中添加一个方法

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

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

在这里插入图片描述

因为

@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自己注入自己
@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.mzph.cn/news/832871.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java多线程编程之synchronizaed和锁分类

并发编程第三周 1 锁的分类 1.1 可重入锁&#xff0c;不可重入锁 Java提供的synchronized&#xff0c;ReentrantLock,ReentrantReadWriteLock都是可重入锁 可重入&#xff1a;当前线程获取到A锁&#xff0c;在获取之后尝试再次获取A锁是可以直接拿到的。 不可重入:当前线程…

美业SaaS系统多门店收银系统源码-【分润常见问题】讲解(二)

美业管理系统源码 博弈美业SaaS系统 连锁多门店美业收银系统源码 多门店管理 / 会员管理 / 预约管理 / 排班管理 / 商品管理 / 促销活动 PC管理后台、手机APP、iPad APP、微信小程序 ▶ 分润常见问题&#xff1a; 4、若产品的服务方分润>0&#xff0c;则销售方分润和服…

Unity Shader中获取像素点深度信息

1.顶点着色器中对深度进行计算 v2f vert(appdata v) {v2f o;o.pos UnityObjectToClipPos(v.vertex);o.uv TRANSFORM_TEX(v.uv, _MainTex);o.depth (o.pos.z / o.pos.w 1.0) * 0.5; // Normalize depth to [0, 1]return o; }但是达不到预期&#xff0c;最后返回的值一直大于…

2024智能电网与能源系统国际学术会议(ICSGES2024)

2024智能电网与能源系统国际学术会议&#xff08;ICSGES2024) 会议简介 我们诚挚邀请您参加将在南京隆重举行的2024年智能电网与能源系统国际学术会议&#xff08;ICSGES2024&#xff09;。南京&#xff0c;一座历史与现代交织的城市&#xff0c;将为这场盛会提供独特的学术…

力扣刷题--数组--第一天

一、数组 数组特点&#xff1a; 连续内存空间存储得数据元素类型一致数组可以通过下标索引查找数据元素&#xff0c;可以删除、替换、添加元素等 1.1 二分查找 使用二分查找需满足得条件&#xff1a; 数组是有序的&#xff1b;数组中没有重复元素&#xff1b;查找的target…

论文辅助笔记:TimeLLM

1 __init__ 2 forward 3 FlattenHead 4 ReprogrammingLayer

暗区突围进不去/游戏无法启动/掉帧卡顿/报错的解决方法

暗区突围是一款高拟真硬核射击手游&#xff0c;打造了全新的沉浸式暗区战局体验&#xff0c;发行商是腾讯公司。这个游戏名词虽然看起来有些陌生&#xff0c;但其本身的玩法内核毫无疑问的是&#xff0c;这款游戏在画面质量和枪械操作方面&#xff0c;都是手游市场上同类游戏中…

springboot模块以及非springboot模块构成的多模块maven项目最佳构建方式

文章目录 背景一般的实现使用spring-boot-dependencies 更优雅的实现. 背景 有时候构建一个多模块maven项目其中某一个模块是web-service需要使用spring boot,其他模块跟spring boot 完全无关,本文总结一下在这个场景下maven项目最佳构建方式. 一般的实现 网上应该也看到过很…

我独自升级崛起下载教程 我独自升级崛起怎么一键下载

定于5月8日全球盛大发布的动作RPG力作《我独自升级崛起》&#xff0c;基于备受追捧的同名动画及网络漫画&#xff0c;誓为热情洋溢的游戏爱好者们呈献一场深度与广度兼具的冒险盛宴。这款游戏巧妙融合网络武侠元素&#xff0c;其创意十足的设计框架下&#xff0c;核心叙述聚焦于…

记录一次给PCAN升级固件pcan_canable_hw-449dc73.bin

方法一:网页升级 首先将3.3V与BOOT短接,插入电脑USB接口,识别为STM32 BOOTLOADER,芯片进入DFU模式。 如果电脑没有识别到STM32 BOOTLOADER,或无法驱动,则需要安装ImpulseRC_Driver_Fixer修复工具。 推荐使用Google浏览器打开网页升级选择PCAN固件,点Connect and Update,…

一部手机实现全行业的AI实景自动无人直播软件:为商家提供更便捷的推广方式

随着人工智能技术的快速发展&#xff0c;AI实景自动无人直播软件成为了商家推广产品的新宠。这款软件结合了智能讲解、一键开播、智能回复等多项功能&#xff0c;为商家提供了一种全新的直播方式。 首先&#xff0c;智能讲解功能让专业主播录制直播脚本&#xff0c;并通过软件自…

如何从零开始学习数据结构?

在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「数据结构的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;数据结构 算法&#xff1d;程…

Navicat Data Modeler Ess for Mac:强大的数据库建模设计软件

Navicat Data Modeler Ess for Mac是一款专为Mac用户设计的数据库建模与设计工具&#xff0c;凭借其强大的功能和直观的界面&#xff0c;帮助用户轻松构建和管理复杂的数据库模型。 Navicat Data Modeler Ess for Mac v3.3.17中文直装版下载 这款软件支持多种数据库系统&#x…

MySQL之查询 拿下 * 。*

DQL数据查询语言 对上述的的查询操作进行代码演示&#xff08;续上一篇学生表代码进行处理&#xff09; 下面是上一篇的代码分享 下面进行简单的查询操作 字符串如果强行进行算数运算默认只为0 查询时常用的单行函数列举 未完待续

什么牌子的骨传导耳机质量好?五大宝藏热门机型测评对比!

我作为一名音乐发烧友&#xff0c;对各类耳机产品都有深入的了解&#xff0c;最近也经常被人问及骨传导耳机哪个牌子好。通过交流&#xff0c;我发现很多人在选择骨传导耳机的时候&#xff0c;都有出现踩坑的情况&#xff0c;这也难怪&#xff0c;随着骨传导耳机热度逐渐增加&a…

Google搜索广告怎么开户?谷歌广告开户投放引流技巧、账户搭建、谷歌ads广告推广投放策略 #搜索引擎 #谷歌广告#互联网营销

Google搜索广告开户步骤&#xff1a; 选择代理商&#xff1a;首先&#xff0c;您需要选择一个经验丰富、信誉良好的Google广告代理商。可以选择上海上弦来广告开户和代运营。 初步咨询&#xff1a;与代理商进行初步沟通&#xff0c;了解他们的服务内容、成功案例、收费标准等。…

RCLAMP0854P.TCT ESD抑制器 静电和浪涌保护 应用领域

RCLAMP0854P.TCT 是一款电路保护器件&#xff0c;属于Transient Voltage Suppressor (TVS) 系列产品。它是一种低电容TVS阵列&#xff0c;具有 RailClamp 标志性技术&#xff0c;旨在为电子设备提供高效防护&#xff0c;免受高电压瞬变和静电放电&#xff08;ESD&#xff09;的…

22_Scala集合Seq

文章目录 Seq序列1.构建集合2.List集合元素拼接&&集合拼接3.可变Seq&&List3.1 ListBuffer创建3.2 增删改查3.3 相互转化 Appendix1.Scala起别名2.Seq底层3.关于运算符操作: :4.空集合的表示 Seq序列 –Seq表示有序&#xff0c;数据可重复的集合 1.构建集合 …

一致性评价政策加速行业仿制药洗牌,惯爱为代表的新锐品牌崭露头角

从印度神油到以形补形&#xff0c;男人的问题&#xff0c;从古至今一直困扰着很多人&#xff0c;大多人都羞于启齿。然而&#xff0c;沉默的背后&#xff0c;隐藏着令人震惊的数据&#xff1a;据统计显示&#xff0c;ED&#xff08;勃起功能障碍&#xff09;是男性生殖系统发病…

抖音短视频矩阵系统技术源头/源代码开发部署/SaaS贴牌/源码api代开发

抖音短视频矩阵系统技术源头/源代码开发部署/SaaS贴牌/源码官方平台api授权代开发 一、短视频矩阵系统源码开发步骤 短视频矩阵系统的源头开发步骤通常包括以下几个关键阶段&#xff1a; 1.需求分析&#xff1a;明确系统的目标用户、功能需求、性能要求等。 2.系统设计&…