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

news2024/11/24 17:21:58

【吃透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.coloradmin.cn/o/1649218.html

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

相关文章

Windows系统安装MySQL数据库详细教程

【确认本地是否安装mysql】 &#xff08;1&#xff09;按【winr】快捷键打开运行&#xff1b; &#xff08;2&#xff09;输入services.msc&#xff0c;点击【确定】&#xff1b; &#xff08;3&#xff09;在打开的服务列表中查找mysql服务&#xff0c;如果没有mysql服务&am…

计算机组成结构—虚拟存储器

目录 一、虚拟存储器的基本概念 二、页式虚拟存储器 1.页表 2.快表(TLB) 3.具有 TLB 和 Cache 的多级存储系统 三、段式虚拟存储器 四、段页式虚拟存储器 五、虚拟存储器和Cache比较 早期的计算机&#xff0c;CPU 是直接操作主存的&#xff0c;也就是运行程序时&#xf…

工业光源环形系列一高均匀条形光源特点

产品特点 ◆可以根据检测需求随意调整照射角度&#xff1a; ◆可以根据检测需求选择光源颜色&#xff1a; ◆多个条形光源可以自由组合&#xff1a; ◆使用贴片灯珠&#xff0c;均匀性更好。

使用Docker安装MySql数据库

大家好&#xff0c;今天给大家分享一下如何使用docker安装MySql数据库&#xff0c;关于docker的安装和常用命令&#xff0c;大家可以参考下面两篇文章&#xff0c;本文中不做过多描述。 Docker在Windows与CentOS上的安装 Docker常用命令 一、拉取MySql数据库镜像 docker pul…

远程桌面连接不上,远程桌面连接不上的专业解决策略

在信息技术领域&#xff0c;远程桌面连接是一种非常重要的工具&#xff0c;它允许用户从任何地点、任何时间访问和操作远程计算机。然而&#xff0c;当远程桌面连接出现问题时&#xff0c;可能会严重影响工作效率。以下是一些可能导致远程桌面连接不上的原因以及相应的解决方案…

usb个人总结

前言&#xff1a; 推荐快速上手资料&#xff1a; 《圈圈教你玩usb》讲用一个usb模块51如何让电脑识别出一个usb设备&#xff0c;也介绍了不少相关的字段内容。 其他例如uac\hid相关字段怎么看的建议去官网下载文档 USB-IF官方网站&#xff1a;Front Page | USB-IF USB中文…

AIGC绘画辅助网站

Midjourney风格样式 Midjourney Style Classifier | Andrei Kovalevs Midlibrary

系统维护启动盘 优启吧

优启吧-《优启时代系统维护盘》2025典藏版&#xff08;UD/ISO&#xff09;

STM32F4xx开发学习_SysTick

SysTick系统定时器 SysTick属于CM4内核外设&#xff0c;有关寄存器的定义和部分库函数都在core_cm4.h这个头文件中实现&#xff0c;可用于操作系统&#xff0c;提供必要的时钟节拍 SysTick简介 SysTick是一个 24 位向下定时器&#xff0c;属于CM4内核中的一个外设&#xff0c;…

Python类方法探秘:从单例模式到版本控制

引言&#xff1a; 在Python编程中&#xff0c;类方法作为一种特殊的实例方法&#xff0c;以其独特的魅力在众多编程范式中脱颖而出。它们不仅提供了无需实例即可调用的便捷性&#xff0c;还在设计模式、版本控制等方面发挥着重要作用。本文将通过几个生动的示例&#xff0c;带您…

八.吊打面试官系列-Tomcat优化-深入源码剖析Tomcat如何打破双亲委派

前言 上篇文章《Tomcat优化-深入Tomcat底层原理》我们从宏观上分析了一下Tomcat的顶层架构以及核心组件的执行流程。本篇文章我们从源码角度来分析Tomcat的类加载机制&#xff0c;且看它是如何打破JVM的ClassLoader双亲委派的 Tomcat ClassLoader 初始化 Tomcat的启动类是在…

5月7号(信息差)

&#x1f30d;首次&#xff0c;西湖大学用蛋白质语言模型定向改造碱基编辑器&#xff0c;登Cell子刊 https://www.jiqizhixin.com/articles/2024-05-07-10 &#x1f384; 哈马斯宣布同意停火提议 https://finance.eastmoney.com/a/202405073067687785.html ✨ 中国将对…

【陀螺仪JY61P维特智能】通过单片机修改波特率和角度参考的方法

根据官方文档&#xff1a; 修改波特率 1.解锁:FF AA 69 88 B5 1.1延时200ms 2.修改波特率:FF AA 04 06 00 2.1切换已修改的波特率然后重新发送解锁和保存指令 2.2解锁:FF AA 69 88 B5 2.3延时200ms 4.保存: FF AA 00 00 00 XY轴角度参考 角度参考是以传感器当前的实际位置&…

IDEA远程连接docker服务,windows版docker desktop

1.windows上安装docker desktop docker desktop下载地址&#xff1a;Docker Desktop: The #1 Containerization Tool for Developers | Docker 有的windows系统不支持安装docker desktop 安装完之后我们可以直接打开&#xff0c;可以选择不登录使用 我们用IDEA连接到docker …

python菜鸟级安装教程 -下篇(安装编辑器)

来来~接着上篇的来~ 安装好python.exe之后&#xff0c;我们可以根据cmd命令窗口&#xff0c;码代码。 这算最简单入门了~ 如果我们在安装个编辑器。是什么效果&#xff0c;一起体验一下吧 第一步&#xff0c;下载编辑器&#xff0c;选择官网&#xff0c;下载免费版本入门足…

语音识别--kNN语音指令识别

⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计3077字&#xff0c;阅读大概需要3分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x1f44f;关注&#x1f440;【文末】我的个人微信公众号&#xf…

【LAMMPS学习】八、基础知识(5.8)LAMMPS 中热化 Drude 振荡器教程

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语&#xff0c;以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各…

TriCore TC162 Archievture Volume 笔记

说明 本文是 英飞凌 架构文档 TriCore TC162P core archiecture Volume 1 of 2 (infineon.com) 的文笔&#xff0c;稍作整理方便查阅&#xff0c;错误之处&#xff0c;还请指正&#xff0c;谢谢 :) 1. Architecture 2. General Purpose & System Register 名词列表&#…

gin-vue-blog 前后端分离项目(已经部署)

gin-vue-blog 前台&#xff1a; 后台&#xff1a; 1.数据库设计&#xff1a;https://blog.csdn.net/m0_73337964/article/details/138137629?spm1001.2014.3001.5501 2.RESTFUL API路由实现&#xff1a;https://blog.csdn.net/m0_73337964/article/details/138321631?spm1…

基于Spring Boot的家具网站设计与实现

基于Spring Boot的家具网站设计与实现 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 系统前台主界面图&#xff0c;用户可进入家具网站可查看…