代理代理到底代理了啥?平时我们如何使用代理

97 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情

简介

  • 代理是常见的设计模式,简单的说他就是控制对一个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理 。 spring中的aop特性就是通过代理类实现的。

静态代理

  • 所谓的静态代理就是说在编译前就产生代理类。实际上就是我们手动提供一个实现类。实现类中转发到真实对象中。下面我们简单实现下静态代理

  • 逻辑分析: 我们实现逻辑很简单。真实实现类有两个方法一个获取用户名,一个注册用户。因为这里是为了模拟所以直接输出注册用户的日志了。我们的代理实现类构造需要一个真实的实现类。方便代理他所以需要传一个真实的实现类。然后在转发到真实方法前或者后我们自定义了一些自己的行为。getUserName中我们打印了一句获取用户名的日志。registerUser直接拦截了。不进行转发。

  • Test类

 ​
 public static void main(String[] args) {
         UserInterface userInterface = new UserInterfaceProxy(new UserInterfaceImpl());
         userInterface.getUserName();
         userInterface.registerUser(null);
    }
 ​
  • 日志

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();
    }
 }
 ​
  • 效果

  • 解析

  • 首先定义一个接口包含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中登录拦截到未登录并不是抛出异常而是进行自动登录。

总结


\