动态代理是java中非常有用的特性之一,目前Spring作为MVC框架的主流选择,主要归功于其最重要的两个特性:Ioc和AOP,他们使得项目模块可以以一种非常松散的耦合的关系组织起来,大大减轻了开发者的负担。AOP正是动态代理实现的典型案例之一,动态代理目前主要有两种方式,JDK动态代理以及CGlib动态代理,下面以代码为例一一讲解。CGlib需要用到cglib和asm的jar包。
首先定义接口:
1 public interface SayHello { 2 3 @MyAnnotation("annotation declared in interface") 4 public void sayHello(); 5 }
再定义实现类:
1 public class SayHelloImpl implements SayHello{ 2 3 @Override 4 @MyAnnotation(value="annotation declared in method") 5 public void sayHello() { 6 System.out.println("Hello!"); 7 } 8 }
为了测试对注解的支持,定义一个注解MyAnnotation,@Retention注解表示注解的保存位置,@Target注解则表示该注解可以标记的目标。
1 @Retention(RetentionPolicy.RUNTIME) 2 @Target(ElementType.METHOD) 3 public @interface MyAnnotation { 4 String value(); 5 }
JDK动态代理类,该类实现了InvocationHandler接口,需要在类中实现invoke方法。在本实例中invoke方法中获取了被代理方法中的@MyAnnotation注解的value值,并打印;若注解值为空串,则抛出异常。
1 /** 2 * 实现JDK动态代理接口类 3 * @author rui.chen 4 * 5 */ 6 public class JDKProxy<T> implements InvocationHandler{ 7 private T obj; 8 9 public JDKProxy(T obj){ 10 this.obj = obj; 11 } 12 13 @Override 14 public Object invoke(Object proxy, Method method, Object[] args) 15 throws Throwable { 16 MyAnnotation ann = method.getAnnotation(MyAnnotation.class); 17 if(ann!=null){ 18 String value = ann.value(); 19 if("".equals(value)){ 20 System.out.println("You input a null value"); 21 throw new NullPointerException(); 22 }else{ 23 System.out.println("Annotation:"+value); 24 return method.invoke(obj, args); 25 } 26 }else{ 27 return null; 28 } 29 } 30 31 }
实现CGlib动态代理类需要实现MethodInterceptor接口,该接口中有一个类似于invoke的方法intercept,从名字中可以看出,CGlib的AOP意味比JDK更浓,InvocationHandler更像一个纯代理接口。
1 /** 2 * 实现CGlib动态代理接口类 3 * @author rui.chen 4 * 5 */ 6 public class CglibProxy<T> implements MethodInterceptor{ 7 8 private T target; 9 10 public Object getInstance(T target){ 11 this.target = target; 12 Enhancer enhancer = new Enhancer(); 13 enhancer.setSuperclass(target.getClass()); 14 enhancer.setCallback(this); 15 return enhancer.create(); 16 } 17 18 @Override 19 public Object intercept(Object proxy, Method method, Object[] args, 20 MethodProxy methodProxy) throws Throwable { 21 MyAnnotation ann = method.getAnnotation(MyAnnotation.class); 22 if(ann!=null){ 23 String value = ann.value(); 24 if("".equals(value)){ 25 System.out.println("You input a null value"); 26 throw new NullPointerException(); 27 }else{ 28 System.out.println("Annotation:"+value); 29 return methodProxy.invoke(target, args); 30 } 31 }else{ 32 return null; 33 } 34 } 35 36 }
测试类。在这里要特别注意,使用JDK动态代理时,由于其只支持接口,因此注解必须标记在接口上面才会生效,而且Proxy.newProxyInstance方法的第二个参数必须保证是一个接口class数组,否则将导致被代理类无法正常被加载并实例化。而在CGlib代理时,SayHelloImpl类不必实现任何接口。在这里也一并说说二者实现方式的区别,JDK方式是通过生成一个实现了代理接口的实现类来完成的代理,而CGLib则是通过继承被代理类,生成一个子类并覆盖子类的实现方法来完成的,因此,使用CGLib时,被代理对象不能被声明为final,否则无法实现继承。
1 public class Run { 2 3 /** 4 * JDK动态代理测试 5 */ 6 public static void JDKProxyTest(){ 7 SayHello t = new SayHelloImpl(); 8 InvocationHandler handler = new JDKProxy<SayHello>(t); 9 10 SayHello p = (SayHello) Proxy.newProxyInstance(SayHello.class.getClassLoader(), 11 new Class[]{SayHello.class},handler); 12 /* 13 * SayHello.class.getInterfaces()会导致无法实例化类,必须使用SayHelloImpl.class.getInterfaces() 14 SayHello p = (SayHello) Proxy.newProxyInstance(SayHello.class.getClassLoader(), 15 SayHello.class.getInterfaces(),handler); */ 16 p.sayHello(); 17 } 18 19 /** 20 * CGlib动态代理测试 21 */ 22 public static void CGlibProxyTest(){ 23 SayHelloImpl t = new SayHelloImpl(); 24 25 CglibProxy<SayHelloImpl> cglib = new CglibProxy<SayHelloImpl>(); 26 SayHelloImpl p = (SayHelloImpl) cglib.getInstance(t); 27 p.sayHello(); 28 } 29 30 /** 31 * @param args 32 */ 33 public static void main(String[] args) { 34 JDKProxyTest(); 35 CGlibProxyTest(); 36 } 37 38 }
运行结果:
1 Annotation:annotation declared in interface 2 Hello! 3 Annotation:annotation declared in method 4 Hello!