代理升级-动态代理

310 阅读5分钟

动态代理

  • 因为静态代理的麻烦,静态代理类只能为一个接口服务,所以我们的动态代理就产生了。动态代理是通过反射机制进行方法的代理。这大大方便我们代码的编写。下面我们简单通过两种方式实现动态代理。

jdk动态代理

  • jdk动态代理是Java提供的一种方式,最大的特点是保留了静态代理的风格。jdk动态代理只能代理接口。这也是他和cglib的区别,也是他的缺点。jdk动态代理通过Proxy进行创建代理对象的。通过java.lang.reflect.InvocationHandler接口控制方法代理拦截的。

  • 接口类


public interface Developer {

    /**
     * 编码
     */
    void code();

    /**
     * 解决问题
     */
    void debug();
}

  • 真实类

public class JavaDeveloper implements Developer {
    @Override
    public void code() {
        System.out.println("java code");
    }

    @Override
    public void debug() {
        System.out.println("java debug");
    }
}

  • 代理类

public class JavaDynamicProxy {
    public static void main(String[] args) {
        JavaDeveloper jDeveloper = new JavaDeveloper();
        Developer developer = (Developer) Proxy.newProxyInstance(jDeveloper.getClass().getClassLoader(), jDeveloper.getClass().getInterfaces(), (proxy, method, params) -> {
            if (method.getName().equals("code")) {
                System.out.println("我是一个特殊的人,code之前先分析问题");
                return method.invoke(jDeveloper, params);
            }
            if (method.getName().equals("debug")) {
                System.out.println("我没有bug");

            }
            return null;
        });
        developer.code();
        developer.debug();
    }
}

  • 效果

002.jpg

  • 解析
  • 首先定义一个接口包含code、bug方法。
  • 然后我们定义一个实现类是Java程序员的工作,对应的功能是java code 、 java bug
  • 然后我们不需要提供代理类,这里通过Proxy进行创建代理对象。但是我们需要一个方法拦截器。这个拦截器在Proxy.newProxyInstance方法中传入。这个方法有三个参数:
    • classLoader : 类加载器(通过接口获取)
    • interface : 接口
    • 拦截器 : 需要我们继承InvocationHandler , 上述代码直接通过lombad表达式书写的。
  • 我们可以看到在拦截器里我们就可以控制方法转发前后的节点了。

cglib动态代理

  • 有了jdk动态代理我们开发代理就很方便了。这个时候还有一个问题。我们平时开发中不可能每个类都有继承接口的。这个时候我们的普通类如果想实现代理改如何实现呢。我们看看spring中aop的类也是没有实现接口的。这个是如何实现的呢。下面我们用cglib告诉你

  • 接口


public class HelloService {
    public HelloService() {
        System.out.println("HelloService构造");
    }

    final public String sayHello(String name) {
        System.out.println("HelloService:sayOthers>>"+name);
        return null;
    }

    public void sayHello() {
        System.out.println("HelloService:sayHello");
    }
}

  • 拦截器

public class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("======插入前置通知======");
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("======插入后者通知======");
        return object;
    }
}

  • Test

public static void main(String[] args) {
    //代理类class文件存入本地磁盘方便我们反编译查看源代码
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/root/code");
    //通过CGLIB动态代理获取代理对象过程
    Enhancer enhancer = new Enhancer();
    //设置enhancer对象的父类
    enhancer.setSuperclass(HelloService.class);
    // 设置enhancer的回调对象
    enhancer.setCallback(new MyMethodInterceptor());
    //创建代理对象
    HelloService helloService = (HelloService) enhancer.create();
    //通过代理对象调用目标方法
    helloService.sayHello();
}

  • cglib可以代理任何类。这是他的优点。他的创键也很简单。通过Enhancer将类与拦截器进行关联,我们的拦截器是实现net.sf.cglib.proxy.MethodInterceptor的。在intercept方法中我们可以通过methodProxy.invokeSuper进行转发方法。在转发前我们可以进行节点的控制。
    • object : 表示增强的对象,具体的实现类
    • method : 表示被拦截的方法
    • args : 参数
    • methodProxy : 父类的方法对象。 这里值得注意的是我们要通过methodProxy.invokeSuper.如果换成method调用将会一直递归拦截造成内存溢出。

使用场景运用

  • 上述我们简单介绍了静态代理,两种动态代理的方式。下面我们结合具体案例进行实现代理。
  • 我们平时开发有些方法需要进行验证是否有权限调用。最简单的就是需要登录。如果我们不用spring。那么将会在每个方法中进行逻辑判断是否已经登录了。这样会造成代码的冗余。今天我们通过动态代理的方式对类里的方法进行登录拦截。
  • 为了方便阅读,在创建代理的地方及类就不展示了。我们值展示两种代理方式的拦截器部分就行了。

jdk动态代理运用


public class BlogHolder extends AbstractBlogHolder {

    private static Logger logger = LogManager.getLogger(BlogHolder.class);

    public BlogHolder(Object blog) {
        super(blog);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Blog blog = getBlog();
        BlogLogin annotation = method.getAnnotation(BlogLogin.class);
        if (annotation!=null&&annotation.needLogin()==true&&!blog.isLogin()) {
            logger.info("当前对象未登录");
            throw new LoginException("当前对象未登录");
        }
        return method.invoke(blog, args);
    }
}

  • 在invoke方法中我们通过获取方法上是否有BlogLogin注解,和needLogin标识来判断当前方法是否需要登录拦截。如果需要拦截且未登录则我们会抛出异常。否则我们进行方法的转发。在实现上有点类似aop的编写。

Cglib动态代理


public class BlogInterceptor extends AbstractBlogInterceptor {

    private static Logger logger = LogManager.getLogger(BlogInterceptor.class);

    private ResponseBodyAdvice advice = new ResponseBodyHandler();
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        BlogLogin annotation = method.getAnnotation(BlogLogin.class);
        if (annotation!=null&&annotation.needLogin()== true) {
            //判断是否登录
            judgeLogin(annotation,o);
        }else if(annotation==null) {
            //判断继承的接口中是否有BlogLogin注解
            BlogLogin blogLogin = getBlogLoginByParent(method, methodProxy);
            if (blogLogin!=null&&blogLogin.needLogin() == true) {
                judgeLogin(blogLogin,o);
            }
        }
        Object o1 = null;
        try {
            o1 = methodProxy.invokeSuper(o, objects);
        } catch (Exception e) {
            advice.beforeBodyWrite(method,o,e);
        }
        advice.beforeBodyWrite(method,o,o1);
        return o1;
    }

    private void judgeLogin(BlogLogin blogLogin , Object o) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, LoginException {
        Method isLogin = o.getClass().getMethod("isLogin");
        Object invoke = isLogin.invoke(o);
        if (!"true".equals(invoke.toString())) {
            logger.info("当前对象未登录");
            if (!advice.autoLogin(blogLogin,o)) {
                throw new LoginException("当前对象未登录");
            }
        }
    }

    public BlogLogin getBlogLoginByParent(Method method, MethodProxy methodProxy) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException {
        Field field = methodProxy.getClass().getDeclaredField("createInfo");
        field.setAccessible(true);
        Object createInfo = field.get(methodProxy);
        if (createInfo == null) {
            return null;
        }
        Field sourceClassName = createInfo.getClass().getDeclaredField("c1");
        sourceClassName.setAccessible(true);
        Class sourceClass = (Class) sourceClassName.get(createInfo);
        Class[] interfaces = sourceClass.getInterfaces();
        for (Class anInterface : interfaces) {
            Method interfaceMethod = anInterface.getMethod(method.getName(),method.getParameterTypes());
            BlogLogin annotation = interfaceMethod.getAnnotation(BlogLogin.class);
            if (annotation != null) {
                return annotation;
            }
        }
        return null;
    }
}

  • 和jdk代理逻辑一样,先根据方法上BlogLogin注解判断是否要进行登录拦截。这里因为代理的是对象本身所以这里除了获取类上方法的BlogLogin注解,还会获取接口上BlogLogin注解,接口的注解起到默认作用。类上的注解会起到覆盖默认的作用。因为项目的需求我在cglib中登录拦截到未登录并不是抛出异常而是进行自动登录。

总结

  • spring框架中简直大量使用代理实现的,你可以看看哦