动态代理详解及示例

jdk动态代理

创建一个人类接口:两个功能 说话 奔跑

public interface Person {

    void speak();
    @MethodName("run") //注解表明对所需的方法进行拦截(后面使用)
    void run();
}

创建王先生类继承Person并实现了上述两个方法

public class Wang implements Person {
    @Override
    public void speak() {
        System.out.println("You are so nice!");
    }

    @Override
    public void run() {
        System.out.println("Running is good for you!");
    }
}

创建代理类:

  1. 代理类继承InvocationHandler接口
  2. Proxy.newInstance()方法创建代理类,参数含义:
    • 目标对象的ClassLoader
    • 需要代理的接口
    • InvocationHandler对象
  3. invoke方法,使用反射执行target对象的方法,方法前后可以加上我们需要切入的操作
public class TargetProxyA implements InvocationHandler{
    private Object target;

    public TargetProxyA(Object target) {
        this.target = target;
    }

    public static Object bind(Object target) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new TargetProxyA(target));
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("思考一波!");
        return method.invoke(target, args);
    }
}

调用TargetProxyA创建代理类对象p,执行speak(),发现动态加入了思考的操作

public class Client {
    public static void main(String[] args) {
        Person p = new Wang();
        p = (Person) TargetProxyA.bind(p);
        p.speak();
   }
}

优化上述代码

不足之处:

  1. 拦截逻辑被写死在invoke方法里面
  2. 接口中不是所有的方法都需要拦截

根据设计模式的迪米特法则,我们将目标类target,它的方法method, 参数args封装到Invocation,让代码更有结构和层次:

public class Invocation {
   private Object target;
   private Method method;
   private Object[] args;

   public Invocation(Object target, Method method, Object[] args) {
       this.target = target;
       this.method = method;
       this.args = args;
   }

   public Object proceed() throws InvocationTargetException, IllegalAccessException {
       return method.invoke(target, args);
   }

   public Object getTarget() {
       return target;
   }

   public void setTarget(Object target) {
       this.target = target;
   }

   public Method getMethod() {
       return method;
   }

   public void setMethod(Method method) {
       this.method = method;
   }

   public Object[] getArgs() {
       return args;
   }

   public void setArgs(Object[] args) {
       this.args = args;
   }
}

将拦截方法所需要的操作写到新的接口中, Invocation作为参数传入
拦截器上加注解,指定要拦截的方法:

@MethodName("speak")
public interface Interceptor {
    Object think(Invocation invocation) throws InvocationTargetException, IllegalAccessException;
}

在拦截器的实现类中切入我们需要的操作

public class InterceptorImpl implements Interceptor {

    @Override
    public Object think(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
        System.out.println("我是拦截器!");
        return invocation.proceed();
    }
}

代理类:

public class TargetProxy implements InvocationHandler{
    private Object target;
    private Interceptor interceptor;

    public TargetProxy(Object target, Interceptor interceptor) {
        this.target = target;
        this.interceptor = interceptor;
    }

    public static Object bind(Object target, Interceptor interceptor) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new TargetProxy(target, interceptor));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //获取拦截器上的注解,指定要拦截的方法
        MethodName methodName = this.interceptor.getClass().getAnnotation(MethodName.class);
        String name = methodName.value();
        //比较拦截方法名是否与拦截器指定方法相同
        if(name.equals(method.getName())) {
            return interceptor.think(new Invocation(target,
                    method, args));
        }
        return method.invoke(target, args);
    }
}
public class Client {
    public static void main(String[] args) {
        Interceptor interceptor = new InterceptorImpl();
        Person p = new Zhang();
        //创建代理对象,加上拦截器
        p = (Person) TargetProxy.bind(p, interceptor);
        p.speak();
    }
}

注:如果不在拦截器加注解指定要拦截的方法,那么可以在被拦截的方法上加注解

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //获取目标方法注解
        String name = method.getAnnotation(MethodName.class).value();
        //与目标方法名比较
        if(name.equals(method.getName())) {
            return interceptor.think(new Invocation(target,
                    method, args));
        }
        return method.invoke(target, args);
    }
public class Client {
    public static void main(String[] args) {
        Interceptor interceptor = new InterceptorImpl();
        Person p = new Zhang();
        //创建代理对象,加上拦截器
        //p = (Person) TargetProxy.bind(p, interceptor);
        p = (Person) TargetProxy1.bind(p, interceptor);
        //p.speak();
        p.run();
    }
}

cglib动态代理

原理: 对指定的业务类生成一个子类,并覆盖其中业务方法实现代理。因为采用的是继承,所以不能对final修饰的类进行代理。

JDK代理要求被代理的类必须实现接口,有很强的局限性。而CGLIB动态代理则没有此类强制性要求。简单的说,CGLIB会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。
在CGLIB底层,其实是借助了ASM这个非常强大的Java字节码生成框架。

生成代理类:

public class TargetProxy implements MethodInterceptor {
    //Enhancer是CGLIB的字节码增强器
    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy)
            throws Throwable {
        System.out.println("前置代理:思考下");
        Object result = methodProxy.invokeSuper(o, args);
        System.out.println("后置代理:吃饭了");
        return result;
    }
}

生成代理类对象的步骤:

  • 生成代理类的二进制字节码文件
  • 加载二进制字节码,生成Class对象(使用反射)
  • 通过反射机制获得实例构造,并创建代理类对象
public class Target {

    public void run() {
        System.out.println("中午吃啥?");
    }
}
public class Client {
    public static void main(String[] args) {
        TargetProxy targetProxy = new TargetProxy();
        Target proxy = (Target) targetProxy.getProxy(Target.class);
        proxy.run();
    }
}

猜你喜欢

转载自blog.csdn.net/LJJZJ/article/details/85253025