一、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代理