spring-aop

一、基础概念

1 aop定义

AOP技术利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即切面。

所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

AOP代表的是一个横向的关系,相比于oop的垂直关系而言。
如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;

那么面向方面编程的方法,就仿佛把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。

自己的理解:其实就是一种面向切面的编程思想。对应原来面向对象的方法,通过代理的方式(将不会影响原来的逻辑)将公用的部分抽离出来,进行功能增强如前置或后置,已达到减少代码重复冗余,解耦合的目的。

2 AOP体系

在这里插入图片描述

3.AOP的核心组件

1.@Aspect注解

作用是把当前类标识为一个切面供容器读取。如果没有声明,将不会生成代理。

2.JoinPoint :连接点。
即代表被切入的具体方法,可以通过它来获取对应方法的一些信息。

如常用的方法如下:

public interface JoinPoint {

/**
     * <p> Returns the target object.  This will always be
     * the same object as that matched by the <code>target</code> pointcut
     * designator.  Unless you specifically need this reflective access,
     * you should use the <code>target</code> pointcut designator to
     * get at this object for better static typing and performance.</p>
     *
     * <p> Returns null when there is no target object.</p>

     */
    Object getTarget();

    /**
     * <p>Returns the arguments at this join point.</p>
     */
    Object[] getArgs();

	 /** Returns the signature at the join point.
     *
     * <code>getStaticPart().getSignature()</code> returns the same object
     */
    Signature getSignature();


}

3.切入点(Pointcut)
指定一个通知将被引发的一系列连接点的集合,用了匹配要影响的类及方法。即配置要被代理的类
如:

	@Pointcut("execution (* com.dw.backstage.service..*.*(..))")
	public void pointcut(){
	}

4.通知(Advice): 在特定的连接点,AOP框架执行的动作。常用的通知组件有:

1.前置通知: @Before
执行目标方法前拦截到的方法。没有特殊注意的地方,只需要一个连接点,JoinPoint,即可获取拦截目标方法以及请求参数。

2.后置通知:@After 
在方法执行之后执行的代码. 无论该方法是否出现异常,类似于finally,且在@AfterReturning返回值之前

3.返回通知:@AfterReturning
在方法正常结束后才会执行的代码(有异常则不会执行),返回通知是可以访问到方法的返回值的!

4.异常通知:@AfterThrowing
在目标方法出现异常时才会执行的代码,可以访问到异常对象; 且可以指定在出现特定异常时才会执行通知代码

5.环绕通知:@Around
* 环绕通知需要携带 ProceedingJoinPoint 类型的参数. 
* 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.
* 且环绕通知必须有返回值, 返回值即为目标方法的返回值

4.应用场景

1.参数校验如校验是否存在sql注入,参数为空判断等
2.日志管理,如参数打印,接口访问耗时等
3.读写分离
4.事务管理
5.设置缓存如Spring-cache

二、动态代理

1.JDK动态代理

JDK动态代理的实现机制是创建一个继承java.lang.reflect.Proxy类,并实现了要代理目标接口的class文件,即通过实现接口来完成代理。

JDK动态代理具体实现思路:

通过实现InvocationHandlet接口创建自己的调用处理器;

通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理

通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型

通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;

Proxy 创建代理对象时序图:

在这里插入图片描述

2.Cglib原理

CGLib动态代理是通过字节码底层技术继承要代理的类,来并重写要代理的类的所有方法来实现代理功能。

思考

1.JDK 和 CGLib动态代理主要区别

1.JDK动态代理是面向接口的,目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。

2.Cglib原理是针对目标类生成一个子类,覆盖其中的所有方法,所以目标类和方法不能声明为final类型,而jdk可以代理被声明为final的代理类。

2.JDK动态代理为什么只能代理接口?

参看jdk动态代理生成的代理对象字节码文件

public final class $Proxy0 extends Proxy implements ProxyInterface {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
 
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
 
    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
 
    public final String toString() throws  {
        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 void targetMethod() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
 
    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
 
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.gupao.designpattern.proxy.jdk.ProxyInterface").getMethod("targetMethod", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

生成的代理对象已经继承了java.lang.reflect.Proxy类,不能再通过继承的方式来实现多态特性,故使用接口的方式来实现多态特性。(成功返回的是object类型,要获取原类,只能继承/实现,或者就是那个目标类)

三、Spring AOP底层实现原理

Spring aop中 @Around注解中的使用其实就是动态代理模式的一种实现。

aop使用到的设计模式有:

1.代理模式

2.工厂模式
代理工厂来创建的具体的代理类

3.责任链模式

bean在调用时,会把pointcut匹配的方法加入拦截器链。然后在链中通过反射依次匹配并调用方法

使用AspectJ的编译时增强实现AOP的方式参考:

Spring AOP的实现原理 http://www.importnew.com/24305.html

3.1 Spring AOP的实现原理

与AspectJ的静态代理不同,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象。

spring借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,该对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

个人理解:就是在运行期被切入点(Pointcut)声明的类或类中的方法,在执行时将调用的是代理对象,并且持有原来的对象来进行功能增强处理,如前置通知,后置通知等

3.2 流程设计分析

可以将 AOP 分成 2 个部分来分析
第一:代理的创建;
第二:代理的调用。

3.2.1 创建代理对象流程

1.首先,需要创建代理工厂,代理工厂需要 3 个重要的信息:拦截器数组,目标对象接口数组,目标对象。

2.创建代理工厂时,默认会在拦截器数组尾部再增加一个默认拦截器 —— 用于最终的调用目标方法。

3.当调用 getProxy 方法的时候,会判断bean是接口还是类,然后确定使用JDK or Cglib

注意:创建代理对象时,同时会创建一个外层拦截器,这个拦截器就是 Spring 内核的拦截器。用于控制整个 AOP 的流程。

Spring aop创建的代理对象的时序图如下:

在这里插入图片描述
源码分析

代理的创建思路

代理的创建分为两个过程即匹配和创建。

而对应在spring的生命周期里是在实例化Bean和完成依赖注入后,会判断当前的Bean是否需要代理,如果需要,就生成代理类把原始类替换掉。

核心思想为:通过循环beanNames实例化Bean对象,判断此对象是否与pointcut表达式匹配。如果匹配就根据advice生成不同的advisor对象,然后调用JDK或者CGLIB的方法生成代理类返回。

代码分析:

创建代理的入口是AbstractAutoProxyCreator类它实现了BeanPostProcessor接口,会在bean的生命周期类执行创建代理流程。

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

wrapIfNecessary方法则是真正产生代理的地方,我们先看下它的内部实现。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // Create proxy if we have advice.
    //先看它的注释,大意说:如果有通知,就创建代理。
    //这里面其实又分为两个步骤:
    //第一,在Bean工厂找到所有的Advisor 第二,根据beanClass和Pointcut去做匹配
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        //真正创建代理并返回,这时候返回的就是代理类了,把真实的bean替换掉
        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. 匹配

先是找到所有的Advisor,这个比较简单。因为我们知道,在解析的时候就把配置的通知封装成advisor注册了进去。首先拿到beanDefinitionNames容器所有beanName,循环判断bean的类型是不是advisor接口的类型,符合条件返回。

public List<Advisor> findAdvisorBeans() {
    // Determine list of advisor bean names, if not cached already.
    String[] advisorNames = null;
    
    //先拿到beanName
    advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
            this.beanFactory, Advisor.class, true, false);
        
    List<Advisor> advisors = new LinkedList<Advisor>();
    for (String name : advisorNames) {
        if (isEligibleBean(name)) {     
            try {
                //再从Bean工厂中拿bean的实例
                advisors.add(this.beanFactory.getBean(name, Advisor.class));
            }
        }
    }
    //返回的advisors就是配置文件中所有的advice和advisor
    return advisors;
}

找到advisors并未结束,还要跟pointcut做匹配,看这些advisor符不符合表达式条件。它最终调用到Pointcut的match方法。这个方法嵌套的太深,就不贴代码了,核心思想就是判断class和class对应的method是否与pointcut表达式匹配。

2. 创建

设置代理工厂

经过上面查找匹配后,确定当前的bean确实需要代理,就调用createProxy方法。

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy源码如下

protected Object createProxy(Class<?> beanClass, 
        String beanName, Object[] specificInterceptors, TargetSource targetSource) {
    //创建代理工厂
    ProxyFactory proxyFactory = new ProxyFactory();
    // Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
    proxyFactory.copyFrom(this);

    //将beanClass上的接口设置到代理工厂
    evaluateProxyInterfaces(beanClass, proxyFactory);
        
    //设置Advisor到代理工厂
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    for (Advisor advisor : advisors) {
        proxyFactory.addAdvisor(advisor);
    }
    //设置目标对象
    proxyFactory.setTargetSource(targetSource);
    return proxyFactory.getProxy(this.proxyClassLoader);
}

创建代理对象

创建代理分为JDK的代理和Cglib的代理,这里我们先关注JDK的代理。getProxy方法就调用到JdkDynamicAopProxy类的方法。这个类还实现了InvocationHandler接口,说明它同时还是调用处理程序。即在调用代理类的invoke方法时,实际上就会调用到JdkDynamicAopProxy.invoke()。

public Object getProxy(ClassLoader classLoader) {
    //这里又给代理工厂加了两个接口 SpringProxy和Advised
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
    
    //这个方法就比较熟悉了,正是JDK动态代理的方法
    //这里的this就是JdkDynamicAopProxy实例。
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

3.2.2 代理对象的调用流程

1.当对代理对象进行调用时,就会触发去获取目标方法外层拦截器。

2.代理对象会将拦截器构造成拦截器执行链。执行链中执行处理器时,会根据表达式判断当前拦截是否匹配这个拦截器。而这个执行链所用的设计模式就是职责链模式。

3.当整个链条执行到最后时,就会触发创建代理时那个尾部的默认拦截器,从而调用目标方法。最后返回。

设计流程图如下:

在这里插入图片描述

Spring 创建了代理对象后,当你调用目标对象上的方法时,将都会被代理到实现InvocationHandler接口的invoke 方法中执行。在这里 JdkDynamicAopProxy 类实现了 InvocationHandler 接口。

Spring aop调用拦截器的时序图

在这里插入图片描述

源码分析

代理类的调用

在业务方法里面调用的时候,就会调用到JdkDynamicAopProxy.invoke()。在执行invoke的时候,我们又可以分为两个步骤:

  1. 获取方法的拦截链
  2. 执行拦截链中处理器

核心思想为:调用JDK或者CGLIB的invoke方法,查询advisor的调用链。链式调用,根据通知类型调用不同的advice实现增强。

org.springframework.aop.framework.JdkDynamicAopProxy#invoke
源码如下:

/**
	 * Implementation of {@code InvocationHandler.invoke}.
	 * <p>Callers will see exactly the exception thrown by the target,
	 * unless a hook method throws an exception.
	 */
	@Override
	@Nullable
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		MethodInvocation invocation;
		Object oldProxy = null;
		boolean setProxyContext = false;

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

		try {
			if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
				// The target does not implement the equals(Object) method itself.
				return equals(args[0]);
			}
			else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				// The target does not implement the hashCode() method itself.
				return hashCode();
			}
			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);

			// Get the interception chain for this method.
			//(这里开始获取拦截器链)
			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...
				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);
			}
		}
	}
1. 获取并设置方法的拦截链

首先从代理工厂中拿到所有的advisor,然后判断是不是PointcutAdvisor类型,其次先matches一下targetClass,再matches一下method,证明这个类的方法在pointcut范围内,加入interceptorList,最后返回。

org.springframework.aop.framework.AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice源码如下:

/**
	 * Determine a list of {@link org.aopalliance.intercept.MethodInterceptor} objects
	 * for the given method, based on this configuration.
	 * @param method the proxied method
	 * @param targetClass the target class
	 * @return a List of MethodInterceptors (may also include InterceptorAndDynamicMethodMatchers)
	 */
	public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
		MethodCacheKey cacheKey = new MethodCacheKey(method);
		List<Object> cached = this.methodCache.get(cacheKey);
		if (cached == null) {
			cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
					this, method, targetClass);
			this.methodCache.put(cacheKey, cached);
		}
		return cached;
	}

org.springframework.aop.framework.DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice源码如下

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
            Advised config, Method method, Class<?> targetClass) {
    List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);
    //config就是代理工厂的实例
    for (Advisor advisor : config.getAdvisors()) {
        if (advisor instanceof PointcutAdvisor) {
            PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
            if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
                MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
                MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) {
                    interceptorList.addAll(Arrays.asList(interceptors));
                }
            }
        }
    }
    return interceptorList;
}
2. 调用

拿到方法的拦截链,然后调用。

调用的时候有个地方比较有意思。它先创建了一个对象ReflectiveMethodInvocation。这个对象还实现了org.aopalliance.intercept.Joinpoint方法。

这个对象有两个参数
org.springframework.aop.framework.ReflectiveMethodInvocation源码如下:

/**
	 * List of MethodInterceptor and InterceptorAndDynamicMethodMatcher
	 * that need dynamic checks.
	 */
	protected final List<?> interceptorsAndDynamicMethodMatchers;

	/**
	 * Index from 0 of the current interceptor we're invoking.
	 * -1 until we invoke: then the current interceptor.
	 */
	private int currentInterceptorIndex = -1;

然后看它的调用方法。
org.springframework.aop.framework.ReflectiveMethodInvocation#proceed源码如下:

@Override
	@Nullable
	public Object proceed() throws Throwable {
		//	We start with an index of -1 and increment early.
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// Evaluate dynamic method matcher here: static part will already have
			// been evaluated and found to match.
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// Dynamic matching failed.(没有匹配则找下一个链)
				// Skip this interceptor and invoke the next in the chain.
				return proceed();
			}
		}
		else {
			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

基于注解的实现与上面类似,可参考其他或类似文章

@see Spring源码分析(七)SpringAOP生成代理类及执行的过程 https://www.jianshu.com/p/cd1e8537c035

思考

1.为何会有拦截器链?

答:一个目标对象,还有可能被其他方式拦截,或者其他方式代理,就像你写应用代码时,你也可能会写多个切面,而其中可能会有部分会代理同一个类或者方法。使用拦截器器链可以将这些代理统一管理起来,并按对应的顺序依次执行。

四、使用总结

AspectJ 切面获取参数名称和参数

    Map<String, Object> nameAndArgs=new HashMap<>();
        Object[] args = joinPoint.getArgs();// 参数值
        String[] parameterNames = ((MethodSignature) signature).getParameterNames();// 参数名称

@see AspectJ 切面获取参数名称和参数https://blog.csdn.net/qq_36020545/article/details/53191822

获取参数上的注解值

 Parameter[] parameters = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameters();
            if (parameters!=null){
                Integer k=null;
                for (int i = 0; i <parameters.length ; i++) {
                    Parameter parameter = parameters[i];
                    String name = parameter.getName();
                    Annotation[] annotations = parameter.getAnnotations();
                    Param param = parameter.getAnnotation(Param.class);
                    System.out.println(name);
                    if (param!=null){
                        String value = param.value();
                        System.out.println(value);
                        if ("startId".equals(value)){
                            Long startId = DataUtil.dateToShardingJdbcKey(DataUtil.getLastAmountMonthDayStart(--count));
                            k=i;
                            args[i]=startId;
                            continue;
                        }
                        if ("endId".equals(value)){
                            Long endId = DataUtil.dateToShardingJdbcKey(DataUtil.getMonthDayEnd());
                            args[i]=endId;
                            continue;
                        }
                    }
                }

参考资料

1.spring AOP的实现原理 http://www.importnew.com/24305.html
2.Spring AOP源码解析——AOP动态代理原理和实现方式 https://my.oschina.net/zhaojia/blog/778455
3.《深入分析Java Web技术内幕》许令波著
4.Spring之AOP原理详解 https://yq.aliyun.com/ziliao/352300
5.问烂的 Spring AOP 原理、SpringMVC 过程 https://www.jianshu.com/p/e18fd44964eb

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值