Spring入门心经之第二章 AOP详解-程序员宅基地

技术标签: spring  java  后端  # Spring源码  

什么是AOP

AOP(Aspect-Oriented Programming:面向切面编程),是Spring中一个重要内容,在AOP思想中例如权限控制日志管理事务控制等被定义为切面,它实际做的就是将核心功能与切面功能拆分,降低核心功能与切面功能的耦合度,然后在把核心功能和切面功能编织在一起。Spring是基于动态代理实现AOP的。Spring默认情况下在目标类实现接口时是通过JDK代理实现的,只有非接口的是通过Cglib代理实现的。
在这里插入图片描述

AOP 基础概念

  • 连接点(Join point):目标对象,每个成员方法都可以成为连接点。
  • 切点(Pointcut):连接点中被做增强操作的方法就叫切点。
  • 增强/通知(Advice):拦截到连接点之后要执行的代码。
  • 切面(Aspect):切点(Pointcut)+通知(Advice)。
  • 织入(Weaving):把切面加入到对象,生成代理对象的过程。
  • 目标(Target):被代理的对象。
  • 代理(Proxy):代理就是目标对象的增强后返回给用户的代理类。

Spring AOP和AspectJ AOP的区别

AOP除了Spring AOP实现外,还有比较出名的AspectJAOP,下面我们说说AspectJ AOP和Spring AOP的区别:

  • Spring AOP属于运行时增强,而AspectJ AOP是编译时增强。
  • Spring AOP基于代理(Proxying)实现,而AspectJ AOP则是基于字节码操作(Bytecode Manipulation)实现的。
  • AspectJ AOP相比于Spring AOP 功能更加强大,但是Spring AOP相对更简单;在切面不多的情况下,两种性能差异不大,但是切面很多时,则AspectJ AOP比Spring AOP性能强很多。

AspectJ AOP定义的通知类型

  • 前置通知(Before Advice):在目标对象的方法被调用前调用通过功能
  • 后置通知(After Advice):在目标对象的方法被调用之后调用通知功能
  • 返回通知(AfterReturning):在目标对象的方法调用完成,返回结果值后调用通知功能
  • 异常通知(AfterThrowing):在目标对象的方法抛出异常之后调用通知功能
  • 环绕通知(Aroud):在目标对象前后调用方法,甚至不调用目标对象的方法也能做到

源码分析

创建示例

为了更好的演示源码,我们首先简单创建一个示例,首先是目标类代码,新建AopDemoServiceImpl类

@Service("demoService")
public class AopDemoServiceImpl {
    

    public void doMethod1(){
    
        System.out.println("调用AopDemoServiceImpl.doMethod1()");
    }

    public String doMethod2(){
    
        System.out.println("调用AopDemoServiceImpl.doMethod2() 返回Hello World");
        return "Hello World";
    }

    public String doMethod3() throws Exception {
    
        System.out.println("调用AopDemoServiceImpl.doMethod3() 抛出Exception");
        throw new Exception("some exception");
    }
}

添加LogAspect切面

@Aspect
@Component
public class LogAspect {
    


    @Pointcut("execution(* com.example.springboot.spring.aop.*.*(..))")
    private void pointCutMethod() {
    
    }

    /**
     * 环绕通知
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointCutMethod()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
    
        System.out.println("-----------------------");
        System.out.println("环绕通知开始,方法名" + joinPoint.getSignature());
        Object o = joinPoint.proceed();
        System.out.println("环绕通知得到结果:" + String.valueOf(o));
        return o;
    }

    /**
     * 前置通知
     */
    @Before("pointCutMethod()")
    public void doBefore() {
    
        System.out.println("前置通知");
    }

    /**
     * 后置通知
     *
     * @param result return val
     */
    @AfterReturning(value = "pointCutMethod()", returning = "result")
    public void doAfterReturning(String result) {
    
        System.out.println("后置通知, 返回值: " + result);
    }

    /**
     * 异常通知
     *
     * @param e exception
     */
    @AfterThrowing(value = "pointCutMethod()", throwing = "e")
    public void doAfterThrowing(Exception e) {
    
        System.out.println("异常通知, 异常: " + e.getMessage());
    }

    /**
     * 最终通知
     */
    @After("pointCutMethod()")
    public void doAfter() {
    
        System.out.println("最终通知");
    }
}

spring-config.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">

    <!--aop相关配置-->
     <context:annotation-config/>
    <context:component-scan base-package="com.example.springboot.spring"/>
    <aop:aspectj-autoproxy proxy-target-class="true"/>

</beans>

程序入口:

public class Entrance {
    
    /**
     * 基于配置文件的依赖注入测试
     *
     * @param args
     */
      public static void main(String[] args) {
    
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        // retrieve configured instance
        AopDemoServiceImpl demoService = context.getBean("demoService", AopDemoServiceImpl.class);

        // use configured instance
        demoService.doMethod1();
        demoService.doMethod2();
        try {
    
            demoService.doMethod3();
        } catch (Exception e) {
    
            // e.printStackTrace();
        }
    }
}

Spring AOP工作流程

在结合上面代码进行源码分析前,先介绍下整个执行流程,整体分为三步。

  1. 前置处理,这一步遍历所有类,获取切面列表,将其存入aspectFactoryCache缓存中
  2. 后置处理,从缓存中获取对应demoService切面方法,结合demoService根据JDK或Cglib创建AOP代理对象。
  3. 执行切面方法。

在这里插入图片描述

AOP切面的实现

我们从AbstractApplicationContext#refresh()进入直奔finishBeanFactoryInitialization()方法,这个方法初始化所有的singleton beans(除了lazy-init)
在这里插入图片描述
beanFactory.preInstantiateSingletons()进入,开始前置处理。
在这里插入图片描述
我们在这里打个断点,设置下Condition:beanName.equals("demoService"),跳过其他beanName,只看demoService。
在这里插入图片描述
进入到getBean()方法中,点进doGetBean()后直奔创建bean的逻辑代码,如下图:
在这里插入图片描述
接下来,我们往下说说前置处理的工作流程,主要是遍历切面,将切面放入缓存中。
在这里插入图片描述
进入到create后,我们径直来到前置处理的入口resolveBeforeInstantiation(beanName, mbdToUse)
在这里插入图片描述
然后我们进入到resolveBeforeInstantiation(beanName, mbdToUse);,在 applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);开始前置处理,我们直接来到核心方法postProcessBeforeInstantiation,在这里面就是bean实例化前做的处理。
在这里插入图片描述笔者贴出了bean初始化前置处理器的postProcessBeforeInstantiation的核心逻辑,可以看到AbstractAutoProxyCreator里面会判断当前传入的bean是否是AOP类,如果是则将其生成通知器类然后放入缓存advisedBeans中。
在这里插入图片描述
判断是否跳过的逻辑也很简单,如下图所示,它会获取所有切面信息,判断当前这个bean是否跟里面某个切面名字一样,如果一样就返回true。
在这里插入图片描述
我们不妨看看findCandidateAdvisors的逻辑,可以看到它又会调用父类的findCandidateAdvisors查找所有的advisor
在这里插入图片描述
核心逻辑来了buildAspectJAdvisors,在这一步它主要用DCL双重锁的单例实现方式,拿到切面类里的切面方法,将其转换成advisor(并放入缓存中)。
在这里插入图片描述

判断是否是切面

在这里插入图片描述

获取切面列表

this.advisorFactory.getAdvisors中返回切面列表,我们进去方法里面看看详细

@Override
	public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
    
		Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
		String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
		validate(aspectClass);
		//忽略细节....
		List<Advisor> advisors = new ArrayList<>();
		//遍历方法列表
		for (Method method : getAdvisorMethods(aspectClass)) {
    
			Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
			if (advisor != null) {
    
				advisors.add(advisor);
			}
		}
		//忽略细节....
		return advisors;
	}

我们主要看getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName)里面内容,代码如下:

@Override
@Nullable
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
        int declarationOrderInAspect, String aspectName) {
    

    validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());

    AspectJExpressionPointcut expressionPointcut = getPointcut(
            candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
    if (expressionPointcut == null) {
    
        return null;
    }

    // 封装成advisor
    return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
            this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

InstantiationModelAwarePointcutAdvisorImpl中封装Advisor

public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut,
        Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory,
        MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
    

    this.declaredPointcut = declaredPointcut;
    this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
    this.methodName = aspectJAdviceMethod.getName();
    this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
    this.aspectJAdviceMethod = aspectJAdviceMethod;
    this.aspectJAdvisorFactory = aspectJAdvisorFactory;
    this.aspectInstanceFactory = aspectInstanceFactory;
    this.declarationOrder = declarationOrder;
    this.aspectName = aspectName;

    if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
    
        // Static part of the pointcut is a lazy type.
        Pointcut preInstantiationPointcut = Pointcuts.union(
                aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);

        // Make it dynamic: must mutate from pre-instantiation to post-instantiation state.
        // If it's not a dynamic pointcut, it may be optimized out
        // by the Spring AOP infrastructure after the first evaluation.
        this.pointcut = new PerTargetInstantiationModelPointcut(
                this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);
        this.lazy = true;
    }
    else {
    
        // A singleton aspect.
        this.pointcut = this.declaredPointcut;
        this.lazy = false;
        this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
    }
}

通过pointcut获取advice

	private Advice instantiateAdvice(AspectJExpressionPointcut pointcut) {
    
		Advice advice = this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pointcut,
				this.aspectInstanceFactory, this.declarationOrder, this.aspectName);
		return (advice != null ? advice : EMPTY_ADVICE);
	}

交由aspectJAdvisorFactory生成切面信息

@Override
@Nullable
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
        MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
    

    // 获取切面类
    Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
    validate(candidateAspectClass);

    // 获取切面注解
    AspectJAnnotation<?> aspectJAnnotation =
            AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
    
        return null;
    }

    // If we get here, we know we have an AspectJ method.
    // Check that it's an AspectJ-annotated class
    if (!isAspect(candidateAspectClass)) {
    
        throw new AopConfigException("Advice must be declared inside an aspect type: " +
                "Offending method '" + candidateAdviceMethod + "' in class [" +
                candidateAspectClass.getName() + "]");
    }

    if (logger.isDebugEnabled()) {
    
        logger.debug("Found AspectJ method: " + candidateAdviceMethod);
    }

    // 切面注解转换成advice
    AbstractAspectJAdvice springAdvice;

    switch (aspectJAnnotation.getAnnotationType()) {
    
        case AtPointcut: // AtPointcut忽略
            if (logger.isDebugEnabled()) {
    
                logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
            }
            return null;
        case AtAround:
            springAdvice = new AspectJAroundAdvice(
                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
        case AtBefore:
            springAdvice = new AspectJMethodBeforeAdvice(
                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
        case AtAfter:
            springAdvice = new AspectJAfterAdvice(
                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;
        case AtAfterReturning:
            springAdvice = new AspectJAfterReturningAdvice(
                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterReturningAnnotation.returning())) {
    
                springAdvice.setReturningName(afterReturningAnnotation.returning());
            }
            break;
        case AtAfterThrowing:
            springAdvice = new AspectJAfterThrowingAdvice(
                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
    
                springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
            }
            break;
        default:
            throw new UnsupportedOperationException(
                    "Unsupported advice type on method: " + candidateAdviceMethod);
    }

    // 最后将其它切面信息配置到advice
    springAdvice.setAspectName(aspectName);
    springAdvice.setDeclarationOrder(declarationOrder);
    String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
    if (argNames != null) {
    
        springAdvice.setArgumentNamesFromStringArray(argNames);
    }
    springAdvice.calculateArgumentBindings();

    return springAdvice;
}

AOP代理的创建

在这里插入图片描述
从上文切面信息处理完后,我们接着分析代理对象的创建,这一步主要是从缓存中拿切面,和demoService的方法匹配,并创建AOP代理对象。回到AbstractAutowireCapableBeanFactory#createBean,进入doCreateBean()方法。
在这里插入图片描述
径直来到初始化实例bean这段代码
在这里插入图片描述
在这里插入图片描述
进入applyBeanPostProcessorsAfterInitialization(bean, beanName);后,我们会看到本次的核心方法postProcessAfterInitialization(result, beanName);,这个方法总的来说主要负责将Advisor注入到合适的位置,然后以Cglib或者JDK形式创建代理,为后面给代理进行增强实现做准备。
在这里插入图片描述
AbstractAutoProxyCreator#postProcessAfterInitialization中如果bean被子类标识为代理,则使用配置的拦截器创建一个代理

	/**
	 * Create a proxy with the configured interceptors if the bean is
	 * identified as one to proxy by the subclass.
	 * @see #getAdvicesAndAdvisorsForBean
	 */
	@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;
	}

wrapIfNecessary方法主要用于判断是否需要创建代理,如果Bean能够获取到advisor才需要创建代理
在这里插入图片描述

获取切面

我们看下getAdvicesAndAdvisorsForBean方法是如何获取 demoService的切面列表。

在这里插入图片描述
通过findEligibleAdvisors方法获取advisor

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    
  // 和上文一样,获取所有切面类的切面方法生成Advisor
  List<Advisor> candidateAdvisors = findCandidateAdvisors();
  // 找到这些Advisor中能够应用于beanClass的Advisor
  List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
  // 如果需要,交给子类拓展
  extendAdvisors(eligibleAdvisors);
  // 对Advisor排序
  if (!eligibleAdvisors.isEmpty()) {
    
    eligibleAdvisors = sortAdvisors(eligibleAdvisors);
  }
  return eligibleAdvisors;
}

上文从findCandidateAdvisors中进入到buildAspectJAdvisors()方法中是为了将切面信息存入到缓存中,而本次则是从缓存中获取domeService切面列表
在这里插入图片描述
回到findEligibleAdvisors(),获取完切面列表来到findAdvisorsThatCanApply(),找到这些Advisor中能够增强demoService的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);
		}
}

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
    
		if (candidateAdvisors.isEmpty()) {
    
			return candidateAdvisors;
		}
		List<Advisor> eligibleAdvisors = new ArrayList<>();
		for (Advisor candidate : candidateAdvisors) {
    
			// 通过Introduction实现的advice
			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;
			}
			// 看看是否能够在demoSerce上增强
			if (canApply(candidate, clazz, hasIntroductions)) {
    
				eligibleAdvisors.add(candidate);
			}
		}
		return eligibleAdvisors;
}

在canApply中遍历demoSerivce所有方法,然后与增强器进行匹配,符合则返回true

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

	for (Class<?> clazz : classes) {
    
		Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
		//遍历demoSerivce所有方法
		for (Method method : methods) {
    
			//与增强器进行匹配
			if (introductionAwareMethodMatcher != null ?
					introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
					methodMatcher.matches(method, targetClass)) {
    
				return true;
			}
		}
	}

	return false;
}
创建代理对象

获取demoService相关切面列表后,就可以开始创建代理对象
在这里插入图片描述
createProxy方法主要是在内部创建要给ProxyFactory的实例,然后设置一些内容,最后通过ProxyFactory创建代理。

	protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
			@Nullable Object[] specificInterceptors, TargetSource targetSource) {
    
	
		if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
    
			AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
		}
		// 创建 ProxyFactory 实例
		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.copyFrom(this);
 
		// 判断是否代理目标类,默认是false
		if (proxyFactory.isProxyTargetClass()) {
    
			// Explicit handling of JDK proxy targets (for introduction advice scenarios)
			// 显式处理 JDK 代理目标
			if (Proxy.isProxyClass(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 {
    
			// 判断是否应该使用cglib代理
			if (shouldProxyTargetClass(beanClass, beanName)) {
    
				proxyFactory.setProxyTargetClass(true);
			}
			else {
    
				evaluateProxyInterfaces(beanClass, proxyFactory);
			}
		}
		// 构建增强器
		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
		// 添加增强器
		proxyFactory.addAdvisors(advisors);
		// 设置要代理的目标类
		proxyFactory.setTargetSource(targetSource);
		// 定制代理工厂
		customizeProxyFactory(proxyFactory);
		// 控制代理工厂被配置后 是否允许修改通知。
		// 默认false
		proxyFactory.setFrozen(this.freezeProxy);
		if (advisorsPreFiltered()) {
    
			proxyFactory.setPreFiltered(true);
		}
		// 初始化工作准备完毕,通过ProxyFactory创建代理
		return proxyFactory.getProxy(getProxyClassLoader());
	}

最后我们直接来到DefaultAopProxyFactory#createAopProxy方法,这里会根据条件判断创建代理(jdk或cglib)。
在这里插入图片描述

切面执行

代理对象创建完成后,终于来到最后一步,切面的执行;上文从创建完代理后调用CglibAopProxy#intercept方法
在这里插入图片描述
核心代码proceed()
在这里插入图片描述
proceed()是执行的核心,执行方法和执行通知都是在此处搞定的。 这里是递归调用的方式,执行所有的拦截器链。执行完递归后依次退出递归,整个流程结束。
在这里插入图片描述
结果输出
在这里插入图片描述

什么是动态代理

上文介绍了SpringAOP的切面实现和创建动态代理的过程,那么动态代理是如何工作的呢?

什么是代理

代理模式(Proxy pattern):通过代理对象访问目标对象,在目标对象的基础上,增加额外的操作,扩展目标对象的功能。

什么是动态代理

说白了就是在运行期间,为目标对象创建代理对象,目标对象不变,然后通过对方法进行动态拦截,进行前置或者后置功能执行等等增强操作,上文说的Cglib动态代理和JDK动态代理就是动态代理。

Cglib代理

什么是Cglib代理

Cglib是一个强大的、高性能的代码生成包。它使用ASM操作字节码,动态生成代理,对目标对象的方法进行增强操作,它广泛被许多AOP框架使用,为他们提供方法的拦截。
在这里插入图片描述

Cglib创建的案例

引入cglib依赖包

<dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
</dependency>

创建实体类User

public class User {
    
    private String name;

    private int age;

    public String getName() {
    
        return name;
    }

    public void setName(String name) {
    
        this.name = name;
    }

    public int getAge() {
    
        return age;
    }

    public void setAge(int age) {
    
        this.age = age;
    }


    public User() {
    
    }

    public User(String name, int age) {
    
        this.name = name;
        this.age = age;
    }


    @Override
    public String toString() {
    
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

目标类,Service类

public class UserServiceImpl {
    
    public List<User> findUserList() {
    
        return Collections.singletonList(new User("zayton", 24));
    }
}

cglib代理类

public class CglibProxy<T> implements MethodInterceptor {
    


    private static final Logger logger = LoggerFactory.getLogger(CglibProxy.class);

    private Object target;


    public  T getTargetClass(Object target) {
    
        //设置被代理的目标类
        this.target = target;
        // 创建加强器设置代理类以及回调,当代理类被调用时,callback就会去调用intercept
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        //返回代理类
        return (T) enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    
        logger.info("调用被代理对象的方法,代理对象:[{}],代理方法:[{}]", o.getClass().getName(), method.getName());
        Object result = methodProxy.invokeSuper(o, args);
        logger.info("代理调用结束,返回结果:[{}]", String.valueOf(result));

        return null;
    }
}

测试代码

public class CglibProxyDemo {
    
    public static void main(String[] args) {
    
        UserServiceImpl targetClass = new CglibProxy<UserServiceImpl>().getTargetClass(new UserServiceImpl());
        targetClass.findUserList();
    }
}

Cglib代理流程

如下图所示,通过Enhancer去配置各种代理类的参数,然后生成代理类;其中final类不能被代理,因为它无法被子类覆盖。
在这里插入图片描述

SpringAOP中Cglib代理实现

源码如下,大致流程跟我们写的示例代码差不多。

@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
    
  //忽略....

  try {
    
     //忽略....

    // 重点看这里,通过enhancer设置各种参数来构建代理对象
    Enhancer enhancer = createEnhancer();
    if (classLoader != null) {
    
      enhancer.setClassLoader(classLoader);
      if (classLoader instanceof SmartClassLoader &&
          ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
    
        enhancer.setUseCache(false);
      }
    }
    enhancer.setSuperclass(proxySuperClass);
    enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
    enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

    // 获取目标代理类中的方法
    Callback[] callbacks = getCallbacks(rootClass);
    Class<?>[] types = new Class<?>[callbacks.length];
    for (int x = 0; x < types.length; x++) {
    
      types[x] = callbacks[x].getClass();
    }
    enhancer.setCallbackFilter(new ProxyCallbackFilter(
        this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
    enhancer.setCallbackTypes(types);

    // 生成代理对象(重点)
    return createProxyClassAndInstance(enhancer, callbacks);
  }
  catch (CodeGenerationException | IllegalArgumentException ex) {
    
    //忽略....
  }
  catch (Throwable ex) {
    
   //忽略....
  }
}

JDK代理

什么是JDK代理

JDK动态代理是JDK自带的一种代理,我们通过继承InvocationHandler就可以实现。前提是目标类需要实现接口才能使用JDK代理。

JDK动态代理创建的案例

JDK动态代理不需要额外引用其他依赖,首先我们定义接口,实体类则沿用上文的User类

public interface UserService {
    
    List<User> findUserList();
}

创建实现类UserServiceImpl

public class UserServiceImpl implements UserService{
    

    @Override
    public List<User> findUserList() {
    
        return Collections.singletonList(new User("zayton", 24));
    }
}

JDK代理类

public class JDKProxy<T> {
    
    private static final Logger logger = LoggerFactory.getLogger(JDKProxy.class);

    private Object target;

    public JDKProxy(Object target) {
    
        this.target = target;
    }

    public T getTargetProxy() {
    
        UserService proxy;
        ClassLoader loader = target.getClass().getClassLoader();
        Class[] interfaces = new Class[]{
    UserService.class};
        //objProxy: 代理对象。 一般不使用该对象 method: 正在被调用的方法 args: 调用方法传入的参数
        InvocationHandler handler = (objProxy, method, args) -> {
    
            logger.info("代理方法被调用前,方法名称[{}]", method.getName());
            Object result = null;
            try {
    
                result = method.invoke(target, args);
            } catch (NullPointerException e) {
    
                e.printStackTrace();
            }
            logger.info("代理方法调用结束,方法名称[{}],返回结果:[{}]", method.getName(), String.valueOf(result));
            return result;
        };
        /**
         * loader: 代理对象使用的类加载器.
         * interfaces: 指定代理对象的类型. 即代理代理对象中可以有哪些方法.
         * h: 当具体调用代理对象的方法时, 应该如何进行响应, 实际上就是调用 InvocationHandler 的 invoke 方法
         */
        proxy = (UserService) Proxy.newProxyInstance(loader, interfaces, handler);
        return (T) proxy;
    }
}

测试代码

public class JDKProxyDemo {
    
    public static void main(String[] args) {
    

        JDKProxy<UserService> jdkProxy=new JDKProxy<>(new UserServiceImpl());
        UserService userService = jdkProxy.getTargetProxy();
        System.out.println(userService.findUserList());
		 //将JDK动态代理生成的类保存本地
        System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
    }
}

JDK代理流程

通过debug可以发现上述代码执行到这段代码,它会通过generateClassFile()方法生成一个ClassFile

public static byte[] generateProxyClass(final String name,
                                        Class<?>[] interfaces,
                                        int accessFlags)
{
    
    ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
    final byte[] classFile = gen.generateClassFile();
    ...
}

generateClassFile方法如下,可以看到它将代理类的所有方法都封装成ProxyMethod对象,然后写入class文件:

private byte[] generateClassFile() {
    

    /* 第一步:将所有方法包装成ProxyMethod对象 */
    
    // 将Object类中hashCode、equals、toString方法包装成ProxyMethod对象
    addProxyMethod(hashCodeMethod, Object.class);
    addProxyMethod(equalsMethod, Object.class);
    addProxyMethod(toStringMethod, Object.class);

    // 将代理类接口方法包装成ProxyMethod对象
    for (Class<?> intf : interfaces) {
    
        for (Method m : intf.getMethods()) {
    
            addProxyMethod(m, intf);
        }
    }

    // 校验返回类型
    for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
    
        checkReturnTypes(sigmethods);
    }

    /* 第二步:为代理类组装字段,构造函数,方法,static初始化块等 */
    try {
    
        // 添加构造函数,参数是InvocationHandler
        methods.add(generateConstructor());

        // 代理方法
        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
    
            for (ProxyMethod pm : sigmethods) {
    

                // 字段
                fields.add(new FieldInfo(pm.methodFieldName,
                    "Ljava/lang/reflect/Method;",
                        ACC_PRIVATE | ACC_STATIC));

                // 上述ProxyMethod中的方法
                methods.add(pm.generateMethod());
            }
        }

        // static初始化块
        methods.add(generateStaticInitializer());

    } catch (IOException e) {
    
        throw new InternalError("unexpected I/O Exception", e);
    }

    if (methods.size() > 65535) {
    
        throw new IllegalArgumentException("method limit exceeded");
    }
    if (fields.size() > 65535) {
    
        throw new IllegalArgumentException("field limit exceeded");
    }

    /* 第三步:写入class文件 */

    /*
        * Make sure that constant pool indexes are reserved for the
        * following items before starting to write the final class file.
        */
    cp.getClass(dotToSlash(className));
    cp.getClass(superclassName);
    for (Class<?> intf: interfaces) {
    
        cp.getClass(dotToSlash(intf.getName()));
    }

    /*
        * Disallow new constant pool additions beyond this point, since
        * we are about to write the final constant pool table.
        */
    cp.setReadOnly();

    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    DataOutputStream dout = new DataOutputStream(bout);

    try {
    
        /*
            * Write all the items of the "ClassFile" structure.
            * See JVMS section 4.1.
            */
                                    // u4 magic;
        dout.writeInt(0xCAFEBABE);
                                    // u2 minor_version;
        dout.writeShort(CLASSFILE_MINOR_VERSION);
                                    // u2 major_version;
        dout.writeShort(CLASSFILE_MAJOR_VERSION);

        cp.write(dout);             // (write constant pool)

                                    // u2 access_flags;
        dout.writeShort(accessFlags);
                                    // u2 this_class;
        dout.writeShort(cp.getClass(dotToSlash(className)));
                                    // u2 super_class;
        dout.writeShort(cp.getClass(superclassName));

                                    // u2 interfaces_count;
        dout.writeShort(interfaces.length);
                                    // u2 interfaces[interfaces_count];
        for (Class<?> intf : interfaces) {
    
            dout.writeShort(cp.getClass(
                dotToSlash(intf.getName())));
        }

                                    // u2 fields_count;
        dout.writeShort(fields.size());
                                    // field_info fields[fields_count];
        for (FieldInfo f : fields) {
    
            f.write(dout);
        }

                                    // u2 methods_count;
        dout.writeShort(methods.size());
                                    // method_info methods[methods_count];
        for (MethodInfo m : methods) {
    
            m.write(dout);
        }

                                        // u2 attributes_count;
        dout.writeShort(0); // (no ClassFile attributes for proxy classes)

    } catch (IOException e) {
    
        throw new InternalError("unexpected I/O Exception", e);
    }

    return bout.toByteArray();
}

然后我们来看下通过 System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");命令生成的代码,可以看它实现了UserSerivce,然后通过创建的Proxy调用InvocationHandler的invoke方法,执行我们自定义的invoke方法。

public final class $Proxy0 extends Proxy implements UserService {
    
    private static final Method m0;
    private static final Method m1;
    private static final Method m2;
    private static final Method m3;

    public $Proxy0(InvocationHandler var1) {
    
        super(var1);
    }

    public final int hashCode() {
    
        try {
    
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
    
            throw var2;
        } catch (Throwable var3) {
    
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean equals(Object var1) {
    
        try {
    
            return (Boolean)super.h.invoke(this, m1, new Object[]{
    var1});
        } catch (RuntimeException | Error var2) {
    
            throw var2;
        } catch (Throwable var3) {
    
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() {
    
        try {
    
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
    
            throw var2;
        } catch (Throwable var3) {
    
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final List findUserList() {
    
        try {
    
            return (List)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
    
            throw var2;
        } catch (Throwable var3) {
    
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
    
        try {
    
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.example.springboot.spring.aop.jdk.UserService").getMethod("findUserList");
        } catch (NoSuchMethodException var2) {
    
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
    
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup var0) throws IllegalAccessException {
    
        if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {
    
            return MethodHandles.lookup();
        } else {
    
            throw new IllegalAccessException(var0.toString());
        }
    }
}

SpringAOP中JDK代理流程

执行源码如下:

@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
    
	if (logger.isTraceEnabled()) {
    
		logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
	}
	// 调用 JDK 的 Proxy#newProxyInstance(..) 方法创建代理对象
  		// 传入的参数就是当前 ClassLoader 类加载器、需要代理的接口、InvocationHandler 实现类
	return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}

@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
   Object oldProxy = null;
   boolean setProxyContext = false;

   TargetSource targetSource = this.advised.targetSource;
   Object target = null;

   try {
    
       // 执行的是equal方法
       if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
    
           // The target does not implement the equals(Object) method itself.
           return equals(args[0]);
       }
       // 执行的是hashcode方法
       else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
    
           // The target does not implement the hashCode() method itself.
           return hashCode();
       }
       // 如果是包装类,则dispatch to proxy config
       else if (method.getDeclaringClass() == DecoratingProxy.class) {
    
           // There is only getDecoratedClass() declared -> dispatch to proxy config.
           return AopProxyUtils.ultimateTargetClass(this.advised);
       }
       // 用反射方式来执行切点
       else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
               method.getDeclaringClass().isAssignableFrom(Advised.class)) {
    
           // Service invocations on ProxyConfig with the proxy config...
           return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
       }

       Object retVal;

       if (this.advised.exposeProxy) {
    
           // Make invocation available if necessary.
           oldProxy = AopContext.setCurrentProxy(proxy);
           setProxyContext = true;
       }

       // Get as late as possible to minimize the time we "own" the target,
       // in case it comes from a pool.
       target = targetSource.getTarget();
       Class<?> targetClass = (target != null ? target.getClass() : null);

       // 获取拦截链
       List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

       // Check whether we have any advice. If we don't, we can fallback on direct
       // reflective invocation of the target, and avoid creating a MethodInvocation.
       if (chain.isEmpty()) {
    
           // We can skip creating a MethodInvocation: just invoke the target directly
           // Note that the final invoker must be an InvokerInterceptor so we know it does
           // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
           Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
           retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
       }
       else {
    
           // We need to create a method invocation...
           MethodInvocation invocation =
                   new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
           // Proceed to the joinpoint through the interceptor chain.
           retVal = invocation.proceed();
       }

       // Massage return value if necessary.
       Class<?> returnType = method.getReturnType();
       if (retVal != null && retVal == target &&
               returnType != Object.class && returnType.isInstance(proxy) &&
               !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
    
           // Special case: it returned "this" and the return type of the method
           // is type-compatible. Note that we can't help if the target sets
           // a reference to itself in another returned object.
           retVal = proxy;
       }
       else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
    
           throw new AopInvocationException(
                   "Null return value from advice does not match primitive return type for: " + method);
       }
       return retVal;
   }
   finally {
    
       if (target != null && !targetSource.isStatic()) {
    
           // Must have come from TargetSource.
           targetSource.releaseTarget(target);
       }
       if (setProxyContext) {
    
           // Restore old proxy.
           AopContext.setCurrentProxy(oldProxy);
       }
   }
}

参考文献

https://www.pdai.tech/md/spring/spring-x-framework-aop-source-3.html
https://www.pdai.tech/md/spring/spring-x-framework-aop-source-4.html
https://javadoop.com/post/spring-aop-source
https://juejin.cn/post/7153214385236738055

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ad031097/article/details/132043966

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签