目录
1.前言
AOP面向切面的编程,是各种框架的核心特性之一。不管是Spring还是Jfinal,对于AOP都有比较好的支持。
对于AOP的理解,我的简单理解是,对于一类方法的执行会自动的操作额外的动作。自动执行的额外动作就是AOP的目标。举两个比较经典的应用场景:
(1)数据库的事物管理,委托类仅仅处理数据库的操作,代理类对操作做事物保证,出现错误了,回退保证数据的准确性。
(2)日志操作,委托类只做业务的处理,代理类可以委托类相关方法的调用做无侵入的日志打印功能。
不管Spring系列的框架也好,Jfinal框架也好,底层对于AOP实现都是用的代理模式,区别在于组织方式不同,对外提供的规则有所差别,另外JFinal主要使用cglib代理,而Spring同时支持jdk动态代理和cglib动态代理。
代理分为动态代理和静态代理,它们的主要区别在于:
静态代理:相关类、代理类等等都是已经编译好的。在代理类、委托类运行之前,代理类已经编译好了。
动态代理:在程序运行的过程中,由反射机制动态的创建代理类和委托类。
下面记录下不同代理的实现。
2.静态代理
2.1 定义一个接口类
以前写代码的时候,喜欢直接写类文件,不写interface,总觉得多一个文件麻烦。但其实,接口和实现类分开来写的方式代码的兼容性和可扩展性要强很多,其实仔细想想现在的微服务架构方式,不就是通过接口进行服务之间的调用和耦合,服务的升级,只需要修改和调整实现类,接口类不变,这样也不会影响调用者(消费方)的版本变更。
所以,对于服务的开发,最好的方式就是先声明接口,然后再去做实现类的开发,从编码设计角度来说,接口类的开发也是构建开发思路的设计过程。
/**
* 静态代理 接口
*/
public interface Count
{
public void query();
}
2.2 定义一个接口实现类
我们可以定义多个接口实现类,这就是接口的优势。
public class ACount implements Count
{
@Override public void query()
{
System.out.println("ACount查询中---");
}
}
2.3 定义一个代理类
对于静态代理而言,我们定义的代理类一定是有针对性的,即这个代理类是针对哪个实现类的代理类,这就是静态代理的特性决定的。例如:我们在调用ACount的query方式时,希望在方法执行前打印一行提示,使用后打印一行提示。按照我们最直观的想法,就是直接修改query的实现算了,但是这样就完全把ACount原本的意思修改了,这时候,我们就需要代理模式来生成代理类,无侵入的实现这样的需求。
public class AServiceProxy implements Count
{
private ACount aCount;
public AServiceProxy(ACount aCount)
{
this.aCount = aCount;
}
@Override public void query()
{
System.out.println("调用之前---");
aCount.query();
System.out.println("调用之后---");
}
}
2.4 测试
public static void testStaticProxy()
{
ACount aCount = new ACount();
AServiceProxy aProxy = new AServiceProxy(aCount);
aProxy.query();
}
测试结果显示:
ACount是委托类,aProxy是代理类,代理类一定会完成委托类的方法,但是在完成的同时,可以根据需要进行其他无侵入的操作。
以上就是静态代理,相比动态代理,最大的区别就是代理类和委托类在运行的过程中,都已经是编译过好的,并非动态生成的。
3.jdk动态代理
首先jdk动态代理一定是基于接口类的,即只能对接口生成代理类。这是和cglib最大的区别。同样的动态代理要先创建一个接口类,然后是接口的实现类和代理类,接口类和实现类,同静态代理一样,我们不在赘述,下面看一下代理类是怎么编写的。
/**
* 实现动态代理的CallBack方法类,可以简单理解为切面上同一操作的类
* InvocationHandler是一种实现方法,他有回调方法,可以用在
* java动态代理,也可以用于cglib动态代理
*/
public class AopInvocationHandler implements InvocationHandler
{
private Object target;
public AopInvocationHandler(Object target)
{
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
System.out.println("切面逻辑,在实际方法调用之前调用--------");
Object result = method.invoke(target,args);
System.out.println("切面逻辑,在实际调用方法之后调用--------");
return result;
}
}
这里定义个了一个基于Invocationhandler的类,重载其invoke方法,然后测试的时候,代码如下;
public static void testJdkProxy()
{
Service aService = new AService();
AopInvocationHandler handler = new AopInvocationHandler(aService);
Service aServiceProxy = (Service) Proxy.newProxyInstance(
aService.getClass().getClassLoader(),
aService.getClass().getInterfaces(),
handler);
aServiceProxy.add();
}
这里重点看Proxy.newProxyInstance方法,传入的handler就是我们自己定义的回调方法类。测试效果如下:
4.cglib动态代理
我们先定义一个实现MethodInterceptor接口的类,如下:
public class CglibProxy implements MethodInterceptor
{
@Override
public Object intercept(Object o, Method method,
Object[] objects, MethodProxy methodProxy) throws Throwable
{
// 添加切面逻辑(advise),此处是在目标类代码执行之前,即为MethodBeforeAdviceInterceptor。
System.out.println("before-------------");
// 执行目标类add方法
methodProxy.invokeSuper(o, objects);
// 添加切面逻辑(advise),此处是在目标类代码执行之后,即为MethodAfterAdviceInterceptor。
System.out.println("after--------------");
return null;
}
}
使用时候的代码如下
public static void testCglibProxy()
{
CglibProxy cglibProxy = new CglibProxy();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(CService.class);
enhancer.setCallback(cglibProxy);
CService cService = (CService)enhancer.create();
cService.add();
}
通过Enhancer生成创建代理类,然后调用代理类的相关方法时,会自动在方法调用前和调用后打印相关内容。这里需要注意的是,它默认是拦截增强类的所有方法。
PS:Jfinal框架管理拦截器、事物的实现根本原理就是cglib的动态代理,区别在于对代理类的intercept方法中做了更多的调整,例如获取方法的注解,根据注解不同进行不同的操作等等。
5.关于效率
(1) 使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前,是比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
(2) 在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,在对JDK动态代理与CGlib动态代理的代码实验中看,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理。1W次执行下,JDK7及8的动态代理性能比CGlib要好20%左右。