SpringAOP

发布时间 2023-06-09 10:56:00作者: 谭五月

一、proxy增强

1、基于JDK

java自带的代理功能,只能针对接口,目标类与代理类为平级关系

public class JDKProxy{
	interface Foo{
		void foo();
	}
	
	static class Target implements Foo{
		public void foo(){
			System.out.println("target foo");
		}
	}
	
	public static void main(String[] param){
	
	Target target = new Target();
	
		ClassLoader loader = JDKProxy.class.getClassLoader();
		Foo proxy = (Foo)Proxy.newProxyInstance(loader, new Class[]{Foo.class}, new InvocationHandler(){
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
				System.out.println("before...");
				Object result = method.invoke(target, args);
				System.out.println("after...");
				
				//返回目标方法执行的结果
				return result;
			}
		});
		
		proxy.foo();
	}
}

2、基于Cglib

目标类与代理类是父子关系,所以目标类不应该为final,而代理方法代理目标方法是基于方法重写,所以目标方法也不能为final

public class CglibProxy{
	static class Target{
		public void foo(){
			System.out.println("target foo");
		}
	}
	
	public static void main(String[] param){
		Target target = new Target();
		
		Target proxy = (Target) Enhancer.create(Target.class, new MethodInterceptor(){
			@Override
			public Object intercept(Object proxyObject, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable{
				System.our.println("before...);
				
				//1、通过反射调用方法(针对每一个方法产生一个代理对象,前16次通过反射,从第17次开始使用对象直接调用)
				Object result = method.invoke(target, args);
				
				//内部没有用反射,实际上,既然intercept的参数已经包括了一个方法的完整签名,又有实现类,所以已经具备了通过实例对象直接调用方法的条件,methodProxy.invoke就是做了这样的一个实现,它内部通过FastClass为每一个方法生成一个方法代理,并且将目标类中的每个Method和实例中的方法一一对应起来,所以在intercept中执行此语句的时候,在内部可以直接用实例调用方法,没有反射,所以效率更高,且这种方式只为每一个目标类生成两个代理对象,较反射调用产生的对象更少,Spring中就是用的这个方式
				//Object result = methodProxy.invoke(target, args);
				
				//与methodProxy.invoke方法的原理基本一致,只是不需要目标实例
				//Object result = methodProxy.invokeSuper(proxyObject, args);
				
				System.our.println("after...);
				
				return result;	//返回方法执行结果
			}
		});
		
		proxy.foo();
	}
}

二、ajc增强

可以使用Aspectj的ajc编译器,通过在编译期修改目标类的class文件,直接将增强代码写入目标方法,这个方法和Spring容器无关,可以突破proxy方式的一些限制,比如可以对静态方法增强
修改pom.xml,安装插件

<build>
	<plugins>
		<plugin>
			<groupId>org.codehaus.mojo</groupId>
			<artifactId>aspectj-maven-plugin</artifactId>
			<version>1.14.8</version>
			<configuration>
				<compilianceLevel>1.8</compilianceLevel>
				<source>8</source>
				<target>8</target>
				<showWeaveInfo>true</showWeaveInfo>
				<verbose>true</verbose>
				<Xlint>ignore</Xlint>
				<encoding>UTF-8</encoding>
			</configuration>
			<executions>
				<execution>
					<goals>
						<goal>compile</goal>
						<goal>test-compile</goal>
					</goals>
				</execution>
			</executions>
		</plugin>
	</plugins>
</build>

三、agent增强

待补充

四、Aspect应用

@Aspect
@Component
public class MyAspect {
	//1、方法规则式拦截,直接编写增强逻辑,符合execution表达式的所有方法都会被拦截并织入代码
	@Before("execution(* com.example.aop.DemoMethodService.*(..))")
	public void before(JoinPoint joinPoint) {
		MethodSignature signature = (MethodSignature)joinPoint.getSignature();
		Method method = signature.getMethod();
		System.out.println("方法规则式拦截," + method.getName());
	}
	
	//2.1、定义一个切点,匹配所有被标注了@Action注解的方法,此规则适用于目标方法上有注解的情况
	@Pointcut("@annotation(com.example.aop.Action)")
	public void pointcut_1(){}
	
	//2.2、定义一个切点,根据方法签名来匹配方法
	@Pointcut("execution(* com.example.aop.DemoMethodService.*(..))")
	public void pointcut_2(){}
	
	//2.2、为符合条件的Pointcut编写增强逻辑
	//可以使用布尔运算 (&&、||、!)等用来丰富匹配条件,如@After("pointcut_2() && !pointcut_1()") 匹配符合pointcut_2方法签名,但是没有被标注@Action注解的方法
	@After("pointcut_1()")
	public void afterAdd(JoinPoint joinPoint) {
		MethodSignature signature = (MethodSignature)joinPoint.getSignature();
		Method method = signature.getMethod();
		Action action = method.getAnnotation(Action.class);
		System.out.println("after*******************注解式拦截: " + action.name());
	}
}

五、Aspect底层

aspect属于高级切面,spring在解析时,会把Aspect高级切面转化成advisor低级切面,原理就是扫描被@Aspect注解的类,拿到被@Pointcut标注的方法,进一步获取@Pointcut的值,使用如下例所示的AspectJExpressionPointcut创建切点,...,最后进行增强

pubic class MyAdvice{
	public static void main(String[] args){
		//切点
		AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
		pointcut.setExpression("execution(* foo())");	//只有foo方法会被增强,其底层实际是使用pointcut.matches()来判断方法是否符合要求
		
		//通知
		//org.aopalliance.intercept.MethodInterceptor
		MethodInterceptor advice = new MethodInterceptor(){
			@Override
			public Object invoke(MethodInvocation invocation) throws Throwable{
				System.out.println("before...");
				Object result = invocation.proceed();
				System.out.println("after...");
				
				return result;
			}
		}
		
		//切面
		DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
		
		//创建代理
		Target1 target = new Target1();
		ProxyFactory factory = new ProxyFactory();
		factory.setTarget(target);
		factory.addAdvisor(advisor);
		factory.setInterfaces(target.getClass().getInterfaces());	//告诉代理工厂,创建的代理要实现哪些接口
		I1 proxy = (I1)factory.getProxy();	//Spring自动选择使用jdk代理或cglib代理
		
		//调用方法
		proxy.foo();
		proxy.bar();
	}
	
	interface I1{
		void foo();
		void bar();
	}
	
	static class Target1 implements I1{
		public void foo(){System.out.println("target1 foo")}
		public void bar(){System.out.println("target1 bar")}
	}
	
	static class Target2{
		public void foo(){System.out.println("target2 foo")}
		public void bar(){System.out.println("target2 bar")}
	}
}

高级切面转低级切面

AspectInstanceFactory factory = new AspectInstanceFactory(new MyAspect());
//遍历高级切面的方法,收集所有解析好的低级切面
List<Advisor> advisorList = new ArrayList<>();
for(Method method : MyAspect.class.getDeclaredMethods()){
	//解析被@Before注解的方法,转化成低级切面
	if(method.isAnnotationPresent(Before.class)){
		//解析切点
		String expression = method.getAnnotation(Before.class).value();
		AspectJExpressionPoinntcut pointcut = new AspectJExpressionPointcut();
		pointcut.setExpression(expression);
		//通知类,与@Before注解对应,使用AspectJMethodBeforeAdvice
		AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
		//切面
		Advisor advisor = new DefaultPoitcutAdvisor(pointcut, advice);
		
		advisorList.add(advisor);
	}
}

AspectJMethodBeforeAdvice类似的通知还有:

  • AspectJAroundAdvice(环绕通知)
  • AspectJAfterReturningAdvice
  • AspectJAfterThrowingAdvice(环绕通知)
  • AspectJAfterAdvice(环绕通知)

注意:这些通知最后执行时又会被统一转化为环绕通知(MethodIntercept接口,已经是环绕通知的不必再次转换),先按优先级从高到底调用前置通知,前置通知执行完后,按优先级从低到高反序执行后置通知,(一层一层嵌套,和过滤器类似,在Aspect切面类上可以使用@Order指定优先级,值越小,优先级越高,最后生成的环绕通知层级越靠外),实际就是递归

通知统一转换为环绕通知

Target target = new Target();
ProxyFactory proxyFactory = new ProxyFactory();
proxy.setTarget(target);
proxy.addAdvisors(advisorList);

//统一转换为环绕通知
List<Object> methodInterceptorList = proxyFactory.getInterceptorAndDynamicInterceptionAdvicer(Target.class.getMethod("foo"), Target.class);

六、Spring选择代理

  • ProxyFactory.proxyTargetClass = false,而且目标实现了接口,则使用jdk代理
  • ProxyFactory.proxyTargetClass = false,但是目标没有实现接口,则使用cglib代理
  • ProxyFactory.proxyTargetClass = true,不管目标有没有实现接口,都使用cglib代理