【吃透Java手写】2-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/bicheng/8394.shtml

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

相关文章

Blender动画与云渲染:创造高质量作品的未来路径

Blender作为开源的3D图形软件&#xff0c;在多个领域广受欢迎。但随着项目复杂度提升&#xff0c;传统渲染方式受限。云渲染技术的兴起突破了这些限制&#xff0c;为创作者提供了更自由、高效的创作环境。 一、Blender动画项目的挑战 传统上&#xff0c;Blender动画渲染需要依…

代码审计-php篇之某CRM系统多处sql注入

&#x1f31f; ❤️ 作者&#xff1a;yueji0j1anke 首发于公号&#xff1a;剑客古月的安全屋 字数&#xff1a;3516 阅读时间: 35min 声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果…

实战 | 实时手部关键点检测跟踪(附完整源码+代码详解)

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

苹果平板HOME键成历史,全面屏时代到来?2024平板电脑市场趋势分析

近期苹果公司在“放飞吧”发布会上推出了新款iPad Pro和iPad Air平板电脑&#xff0c;并下架了最后一款带有实体Home按键的iPad 9。这一变化标志着Home键在苹果iPad产品线中成为了历史&#xff0c;引起了不少网友的怀念和感慨。 与此同时&#xff0c;今年3月线上平板电脑市场迎…

【驱动】I2C读写时序

1、I2C总线 I2C使用两条线在主控制器和从机之间通信,SCL(串行时钟线)和SDA(串行数据线),这两条线需接5~10欧上拉电阻,总线空闲空闲时,SCL和SDA处于高电平,I2C总线标准模式速度可以达到100K/S,快速模式可以达到400K/S。 2、状态 I2C总线有四种状态:空闲、启动、忙碌、…

Unity与C#的关系

第一&#xff0c;我们首先需要知道Unity与C#的关系是什么&#xff1f; 第二&#xff0c;我们要明白为什么Unity会使用C#&#xff0c;而不是C&#xff1f; 第三&#xff0c;我们需要知道Unity是怎么使用C#的&#xff1f; 第一点&#xff1a; 先说结论&#xff1a;C#是Unity用…

LabVIEW波浪发电平台浮筒取能效率数据采集系统

LabVIEW波浪发电平台浮筒取能效率数据采集系统 随着化石能源的逐渐减少以及能源价格的上升&#xff0c;寻找可替代的、可再生的、清洁的能源成为了世界各国的共识。波浪能作为一种重要的海洋能源&#xff0c;因其巨大的潜力和清洁性&#xff0c;近年来受到了广泛关注。开发了一…

Blender修改器

修改器 Modifier&#xff0c;对模型进行修改&#xff0c;相当于一个函数。 修改器图标是界面右下角的扳手样式 每个修改器的顶部都有如下样式&#xff0c;从左到右分别为&#xff1a;展开/折叠&#xff0c;修改器类型&#xff0c;修改器名称&#xff0c;编辑模式按钮&#xff…

TCP三次握手四次挥手 UDP

TCP是面向链接的协议&#xff0c;而UDP是无连接的协议 TCP的三次握手 三次传输过程是纯粹的不涉及数据&#xff0c;三次握手的几个数据包中不包含数据内容。它的应用层&#xff0c;数据部分是空的&#xff0c;只是TCP实现会话建立&#xff0c;点到点的连接 TCP的四次挥手 第四…

Python生成文学编程风格文档库之pycco使用详解

概要 Pycco是一个Python库,用于生成文学编程风格的文档。它受到了Docco(一个快速生成源代码文档的工具)的启发,并通过解析源代码旁边的注释来创建一个美观的文档页面,使代码的解释与代码本身并排显示。 安装 安装Pycco非常简单,可以通过Python的包管理器pip进行安装: …

vue3与js的router基本使用方式

title: vue3与js的router基本使用方式 tags: vue3js abbrlink: ‘57270957’ date: 2024-04-17 18:54:47 第一步快捷引入的别名 使用路由需要大量在src文件中引用所需要的地址&#xff0c;并且组件中也需要很多的包的引用&#xff0c;将快速跳转到src这一文件的步骤进行简化操…

Redis实际应用中的解决方案

Redis缓存使用问题 1数据一致性 分析一下几种方案&#xff1a; 1&#xff1a;先更新缓存&#xff0c;再更新数据库 2&#xff1a;先更新数据库&#xff0c;在更新缓存 3&#xff1a;先删除缓存&#xff0c;后更新数据库 4&#xff1a;想更新数据库&#xff0c;后删除缓存 …

前端 Android App 上架详细流程 (Android App)

1、准备上架所需要的材料 先在需要上架的官方网站注册账号。提前把手机号&#xff0c;名字&#xff0c;身份证等等材料准备好&#xff0c;完成开发者实名认证&#xff1b;软著是必要的&#xff0c;提前准备好&#xff0c;软著申请时间比较长大概需要1-2周时间才能下来&#xf…

需求文档怎么写?

1. 导言 我也来个导言: 写这篇博客的目的就是来解答一下下面几个问题&#xff1a; 需求文档怎么写&#xff1f;需求文档都应该包含哪些内容&#xff1f;怎样才算一个合格的需求文档&#xff1f; 产品需求文档&#xff08;Product requriement document&#xff09;&#xff…

全网首发亲测有用:python免费将chatgpt机器人接入个人微信(同时支持钉钉、QQ 以及别的语言模型如文心一言等)

一、获得免费chatgptAPI https://github.com/chatanywhere/GPT_API_free?tab=readme-ov-file 点击这个就可以获取一个免费的CHATGPT API key 或者觉得不够用的话,也可以付费购买在里面,看着价格很便宜,个人没有买过 tips: 转发API无法直接向官方接口api.openai.com发起…

笔试强训Day20 动态规划 模拟

经此一役小红所向无敌 题目链接&#xff1a;A-经此一役小红所向无敌_牛客小白月赛37 (nowcoder.com) 思路&#xff1a; 水题 直接跟思路即可。 AC code&#xff1a; #include<iostream> using namespace std; typedef long long LL; LL a1,a2,b1,b2,t1,t2,sum; int m…

专题五_位运算(3)

目录 137. 只出现一次的数字 II 解析 题解 面试题 17.19. 消失的两个数字 解析 题解 137. 只出现一次的数字 II 137. 只出现一次的数字 II - 力扣&#xff08;LeetCode&#xff09; 解析 注意这里指的是比特位上的01来进行统计的 题解 class Solution { public:int sin…

深入理解分布式事务⑧ ---->MySQL 事务的实现原理 之 MySQL 事务流程(MySQL 事务执行流程 和 恢复流程)详解

目录 MySQL 事务的实现原理 之 MySQL 事务流程&#xff08;MySQL 事务执行流程 和 恢复流程&#xff09;详解MySQL 事务流程1、MySQL 事务执行流程1-1&#xff1a;MySQL 事务执行流程如图&#xff1a; 2、MySQL 事务恢复流程2-1&#xff1a;事务恢复流程如下图&#xff1a; MyS…

基于V4L2框架的摄像头从上层到底层开发

文章目录 一、V4L2应用开发1、识别摄像头2、查看摄像头设备的能力3、查看支持视频格式4、设置视频格式5、申请帧缓冲6、启动采集7、出队取一帧图像8、入队归还帧缓冲9、停止视频采集10、退出释放资源 二、V4L2框架源码分析1、struct video_device2、struct v4l2_device *v4l2_d…

C#核心之面向对象-继承

面向对象-继承 文章目录 1、继承的基本规则1、基本概念2、基本语法3、示例4、访问修饰符的影响5、子类和父类的同名成员 2、里氏替换原则1、基本概念2、is和as3、基本实现 3、继承中的构造函数1、基本概念2、父类的无参构造函数3、通过base调用指定父类构造 4、万物之父和装箱拆…