"代理"是我们日常生活中非常常见的一个词一种身份,所谓“代理”简而言之就是充当职权充当责任委托,比如明星的经纪人或品牌的法定代言人,我们需要跟某明星签订演唱会的协议或其他的合作协议,不需要你费劲千辛万苦且徒劳无功去找这位明星,只需要联系他的经纪人就好了。
在Java中,代理分为静态代理和动态代理,所谓静态代理就是由程序员自定义代理的业务逻辑,在程序编译前就把接口、实现类和代理类都一次性定义好了,我们Java设计模式中的代理设计模式就是经典的静态代理;动态代理一般由第三方提供接口,调用者直接引入对应的依赖包,然后实现(implements)对应的接口并重写(@Override)对应的方法即可,对应需要代理补充的业务逻辑就可以在重写的方法里面实现,比如常见的JDK动态代理(InvocationHandler)与CGLIB动态代理(MethodInterceptor),下文将以这两种动态代理进行举例说明。
JDK动态代理只能针对接口进行代理,从早期的jdk1.6版本就开始有,在spring aop面向切面编程中使用较为广泛,在spring中当Bean实现接口时,Spring就会用JDK做动态代理,代理类主要重写InvocationHandler接口中的nvoke(Object proxy, Method method, Object[] args)方法,并在里面实现被代理对象的前置和后置逻辑,如下是详细的实例(一个加法计算器的例子,假如单位是美分):
1.1 计算器接口:
public interface Calculator {
public int add(int a,int b);
}
1.2 计算器代理类
public class LogHandler implements InvocationHandler {
Object obj;
public LogHandler(Object obj) {
this.obj = obj;
}
public LogHandler() {
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("进入代理对象invoke方法");
System.out.println("调用前");
System.out.println("method name:"+method.getName());
System.out.println("method args:"+ Arrays.toString(args));
Object ret=method.invoke(obj,args);
ret=Integer.parseInt(ret.toString())*100;//单位是美分,后置结果乘以*100
System.out.println("调用后");
return ret;
}
public static T newMapperProxy(Class mapperInterface,Object object) {
ClassLoader classLoader = mapperInterface.getClassLoader();
Class>[] interfaces = new Class[]{mapperInterface};
LogHandler proxy = new LogHandler();
proxy.obj=object;//object是被代理的实例对象,这个对象可以自定义实现需要代理的接口
return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
}
}
1.3 主函数:
public class MainDanamicProxy {
@Test
public void test1 (){
//自定义实现的计算器接口
Calculator newObject=new Calculator() {
@Override
public int add(int a, int b) {
return a+b;
}
};
Calculator calculator = LogHandler.newMapperProxy(Calculator.class,newObject);
int sum=calculator.add(10,20);
System.out.println("sum(美分):"+sum);
}
}
控制台打印的结果:
2 CGlib动态代理:
CGLIB(Code Generator Library)是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架,在spring框架中中当Bean没有实现接口时,就使用CGlib做动态代理,那为什么不直接使用Java动态代理,而要使用CGLIB呢?答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了,并且CGLIB代理主要通过对字节码的操作,比使用Java反射效率要高。代理类主要重写MethodInterceptor接口中的intercept(Object object, Method method, Object[] args, MethodProxy proxy)方法,并实现前置和后置业务逻辑,如下是详细的实例(一个加法计算器的例子,假如单位是美分):
2.1 被代理的类
public class CalculatorImpl {
public int add(int a, int b) {
System.out.println("进入被代理的方法");
int sum=a+b;
return sum;
}
}
2.2 代理类
public class CalculatorCgLIB implements MethodInterceptor {
//创建代理对象
public Object getInstance(Object target) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
// 设置回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("事务开始。。。");
System.out.println("object name:"+object.getClass());
System.out.println("method name:"+method.getName());
System.out.println("MethodProxy proxy:"+proxy.getSuperName());//代理的函数名称
System.out.println("method args:"+ Arrays.toString(args));
Object result = proxy.invokeSuper(object, args);//调用被代理的对象并传入对应的参数
System.out.println("事务结束。。。");
result=Integer.parseInt(result.toString())*100;//单位是美分,后置结果乘以*100
return result;
}
}
2.3 主函数
public class MainClass {
@Test
public void test1(){
CalculatorCgLIB cglib = new CalculatorCgLIB();
CalculatorImpl calculator = (CalculatorImpl) cglib.getInstance(new CalculatorImpl());//创建代理对象
int sum=calculator.add(10,20);
System.out.println("sum(美分):"+sum);
}
}
控制台打印的结果:
如上关于jdk动态代理和CGLIB动态代理示例就完全了, CGLIB动态代理需要注意asm.jar 版本的问题,如果版本不匹配,容易出现 java.lang.NoClassDefFoundError: org/objectweb/asm/Type 的错误,
本实例所使用的asm 3.3.1 jar可在这边下载https://download.csdn.net/download/aiyun5520342/10563508,
所使用的cglib-2.2.2.jar可在这边下载https://download.csdn.net/download/yufang131/8593431