【吃透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 {
    @Autowired
    private 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 {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception {
  /*      System.out.println(beanName+"初始化方法之前");
        if(beanName.equals("userService")) {
            System.out.println("userService初始化方法之前");
        }*/
        return bean;
    }

    @Override
    public 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 {
    @Autowired
    private 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 processed
   continue;
  }
  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 loader
 ClassLoader 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 {
    @Bean
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(dataSource());
    }
    @Bean
    public PlatformTransactionManager transactionManager() {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource());
        return dataSourceTransactionManager;
    }
    @Bean
    public 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 {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public 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 {
    @Bean
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(dataSource());
    }
    @Bean
    public PlatformTransactionManager transactionManager() {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource());
        return dataSourceTransactionManager;
    }
    @Bean
    public 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 {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public 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 {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private UserService userService;

    @Transactional
    public 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.mfbz.cn/a/606097.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

华为ensp中BFD和OSPF联动(原理及配置命令)

作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年5月6日20点26分 BFD通常指的是双向转发检测。BFD是一个旨在快速检测通信链路故障的网络协议&#xff0c;提供了低开销、短延迟的链路故障检测机制。它主要用于监测两个…

start.spring.io不支持java8,idea使用阿里云又报错

做项目的时候&#xff0c;我们可以发现&#xff0c;访问https://start.spring.io/ 创建脚手架项目的时候&#xff0c;最低是java 17了 但是对于很多项目来说&#xff0c;还是在用java8&#xff0c;这该怎么办呢&#xff1f; 值得庆幸的是&#xff0c;阿里云也同样有相同功能的…

VASP_AIMD+VASPKIT计算含温力学性质

材料的力学性质一直是DFT计算的重要方向&#xff0c;笔者在以往已有针对于静态结构的力学性质诸如弹性常数的相关计算&#xff0c;同时可通过VASPKIT借助相关方程导出力学性能。 bashvaspvaspkit能量应变计算弹性常数 vaspkit计算弹性常数的对称性指定 vaspkit计算弹性常数脚…

visual studio 2017重命名解决方案或项目名称

1.解决方案->右键->重命名->新的名字 2.项目->右键->重命名->新的名字 3.修改程序集和命名空间名称 项目->右键->属性->修改程序集名称和命名空间名称 4.搜索换名 Ctrl-F->输入旧名称->搜索->将所有旧名称改为新名称&#xff08;注意是整…

C++向函数传递对象

C语言中&#xff0c;对象作为函数的参数和返回值的传递方式有 3 种&#xff1a;值传递、指针传递和引用传递。 1. 对象作为函数参数 把实参对象的值复制给形参对象&#xff0c;这种传递是单向的&#xff0c;只从实参到形参。因此&#xff0c;函数对形参值做的改变不会影响到实…

使用Docker安装Whistle Web Debugging Proxy

大家好&#xff0c;继续给大家分享如何使用docker来安装Whistle Web Debugging Proxy&#xff0c;关于Whistle Web Debugging Proxy的介绍和使用&#xff0c;大家可以参考下面文章&#xff0c;希望本文能够给大家的工作带来一定帮助。 Whistle Web Debugging Proxy介绍及使用 …

《二十一》QT QML编程基础

QML概述 QML&#xff08;Qt Meta-Object Language&#xff09;是一种声明性语言&#xff0c;它被用于描述Qt框架中用户界面的结构和行为。QML提供了一种简洁、灵活的方式来创建动态和交互式的界面。 QML基于JavaScript语法&#xff0c;通过使用QML类型和属性来定义界面的元素…

Codeforces Round 941 (Div. 2)(A,B,C,D,E)

比赛链接 这场难度不高&#xff0c;基本没考算法&#xff0c;全是思维题。B是推结论&#xff0c;C是博弈&#xff0c;D是构造&#xff0c;需要对二进制有一定理解&#xff0c;E是思维题&#xff0c;2300分的暴力和模拟。 A. Card Exchange 题意&#xff1a; 您有 n n n 张牌…

【思科战报】2024.5月最新CCNP考试战报

【福利】思科CCNP考试介绍&#xff08;附CCNP题库下载&#xff09;-CSDN博客思科 CCNP&#xff08;企业基础架构&#xff09;&#xff0c;需考 2 门https://blog.csdn.net/XMWS_IT/article/details/138609138?spm1001.2014.3001.5501【福利】思科CCNP考试介绍&#xff08;附CC…

CSS-盒子模型

盒子模型的重要组成部分 内容区域content&#xff1a;width , height 内边距&#xff1a;内边框和内容区域的距离Padding边框线&#xff1a;Border外边距&#xff1a;Margin Border (边框线) 属性&#xff1a;Border 属性值&#xff1a;边框线粗细px 线条样式 颜色(不区分…

从零开始的软件测试学习之旅(八)jmeter线程组参数化及函数学习

jmeter线程组参数化及函数学习 Jmeter基础基本使用流程组件与元件 线程组线程的执行方式Jmeter组件执行顺序 常见属性设置查看结果数的作用域举例 Jmeter参数化实现方式1.用户定义参数2.用户参数3.函数4.csv数据文件设置 每日复习 Jmeter基础 基本使用流程 启动项目案例 启动…

华为OD机试【全量和已占用字符集】(java)(100分)

1、题目描述 给定两个字符集合&#xff0c;一个是全量字符集&#xff0c;一个是已占用字符集&#xff0c;已占用字符集中的字符不能再使用。 2、输入描述 输入一个字符串 一定包含&#xff0c;前为全量字符集 后的为已占用字符集&#xff1b;已占用字符集中的字符一定是全量…

Run ‘conda init‘ before ‘conda activate‘

使用conda activate 虚拟环境名称的时候提示&#xff1a;Run conda init before conda activate 解决办法&#xff1a; 首先需要确保是管理员身份运行这个cmd窗口。 然后&#xff0c;现在执行一下&#xff1a;conda init 命令&#xff0c;最后再执行&#xff1a;conda activate…

vue3+ts+vant选择器选中文字效果

所需要的样式: 选中某个选项后文字有放大和改变颜色的效果 主要就是在van-picker上加class, 给对应的style样式即可 <van-pickerclass"custom-picker":title"pickerData.titleText"v-if"pickerData.ispicker"show-toolbar:columns"col…

【Java orm 框架比较】九 新增wood框架对比

【Java orm 框架比较】九 新增wood框架对比 本次新增wood 框架测试 测试数据存储、分页查询&#xff0c;文档及框架比较稳定半天时间加入测试使用 迁移到&#xff08;https://gitee.com/wujiawei1207537021/spring-orm-integration-compare&#xff09; orm框架使用性能比较…

Python中的`return`语句详解

Python中的return语句详解 对于初学Python或任何编程语言的人来说&#xff0c;理解函数如何返回值是非常重要的。在Python中&#xff0c;return语句用于从函数中返回结果。本篇博客将详细介绍return语句的基本用法&#xff0c;以及如何在不同情境中有效使用它。 什么是return…

我独自升级崛起怎么刷初始装备等级属性 我独自升级崛起攻略分享

我独自升级崛起怎么刷初始装备等级属性 我独自升级崛起攻略分享 我独自升级崛起是由同名漫画改编的RPG游戏&#xff0c;支持PC和移动两端。讲述了世界中出现了次元传送门&#xff0c;觉醒的猎人在其中和次元传送门传送来的怪物进行对抗&#xff0c;保护人类的安全。在游戏中玩…

探索数字社交的奇迹:解读Facebook的革命性影响

1. 社交互动的全新模式 Facebook的出现不仅仅是一个社交媒体平台的诞生&#xff0c;更是一种全新的社交互动模式的开启。传统的社交模式主要依赖于面对面的交流&#xff0c;而Facebook则将社交推向了全新的数字化平台&#xff0c;使得人们可以在虚拟的世界里建立和维系社交关系…

AI绘画Stable Diffusion 插件篇:智能标签提示词插件sd-danbooru-tags-upsampler

大家好&#xff0c;我是向阳。 关于智能标签提示词插件&#xff0c;在很早之前就介绍过很多款了&#xff0c;今天再给大家介绍一款智能标签提示词插件sd-danbooru-tags-upsampler。该智能提示词插件是今年2月23号才发布的第一版V0.1.0&#xff0c;算是比较新的智能提示词插件。…

Java 区块链应用 | 割韭菜之假如K线涨跌可随意变动修改的实现

大家好&#xff0c;我是程序员大猩猩。 我一直在想&#xff0c;币圈这个行情时涨时跌&#xff0c;不断的割韭菜&#xff0c;不是由市场决定的&#xff01;而是由交易所直接输入一个数值后点击确定按钮而变化的&#xff0c;那么是不是很恐怖的行为。 为了验证这么一个想法&…
最新文章