浅谈Java代理技术

一、代理模式

1、定义

 为其他对象提供一种代理以控制对这个对象的访问。

 代理模式是常用的Java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等;

 代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

2、组成

 抽象角色(Subject):通过接口或抽象类声明真实角色实现的业务方法

 真实角色(RealSubject):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用

 代理角色(Proxy):实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作

 Client是通过Proxy来访问RealSubject的。
在这里插入图片描述

3、优点

 增强目标对象

 保护目标对象

 目标隔离,降低系统耦合度

 方便扩展

4、缺点

 类的数目增多

 增加代理对象会导致请求处理速度变慢

 增加系统复杂度

5、分类

 静态代理:在程序运行前就已经存在的编译好的代理类是为静态代理

 动态代理:在程序运行期间根据需要动态创建代理类及其实例来完成具体的功能是为动态代理

 动态代理又分为:JDK动态代理、CGLIB动态代理

二、静态代理

 静态代理的实现比较简单,代理类通过实现与目标对象相同的接口,并在类中维护一个代理对象。通过构造器传入目标对象赋值给代理对象,进而执行代理对象实现的接口方法,并实现前拦截,后拦截等所需的业务功能。

1、示例

public interface LoginService {
    boolean checkUser();
}
public class LoginServiceImpl implements LoginService {
    @Override
    public boolean checkUser() {
        System.out.println("LoginServiceImpl checkUser");
        return true;
    }
}
public class StaticProxyTest {
    public static void main(String[] args) {
        //创建目标对象
        LoginService loginService = new LoginServiceImpl();

        //创建代理对象
        LoginService staticProxy = new StaticProxy(loginService);

        staticProxy.checkUser();
    }

    public static class StaticProxy implements LoginService {
        private LoginService target;

        public StaticProxy(LoginService target) {
            this.target = target;
        }

        @Override
        public boolean checkUser() {
            System.out.println("**********静态代理方法执行前**********");
            Boolean result = target.checkUser();
            System.out.println("**********静态代理方法执行后**********");
            return result;
        }
    }
}

2、优点

 在不对目标对象进行修改的前提下,对目标对象进行功能的扩展和拦截

3、缺点

 因为代理对象需要实现与目标对象一样的接口,会导致代理类十分繁多,不易维护

 同时一旦接口增加方法,则目标对象和代理类都需要维护

三、JDK动态代理

1、原理

 JDK动态代理就是在运行时生成一个类,这个类会实现指定的一组接口,且这个类没有.java文件,你不用去关心它是什么类型的,只需要知道它实现了哪些接口即可。下面是最基本的用法示例:

Class[] interfaces = {LoginService.class};

InvocationHandler h = new InvocationHandler() {
    @Override
 	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	....
        return ...;
    }
};

LoginService service = (MyInterface)Proxy.newProxyInstance(loader, interfaces, h);
service.checkUser();

 上面代码中,Proxy类的静态方法newProxyInstance()生成了一个对象,这个对象实现了interfaces数组中指定的接口。

newProxyInstance方法有三个参数:

ClassLoader loader:它是类加载器类型,通过MyInterface.class.getClassLoader()就可以获取到ClassLoader对象;

Class[] interfaces:指定newProxyInstance()方法返回的对象要实现哪些接口,可以指定多个接口;

InvocationHandler h:它是最重要的一个参数,InvocationHandler是一个接口,名字叫调用处理器。上面代码中service对象是MyInterface接口的实现类对象,其实无论你调用service对象中的什么方法,它都是在调用InvocationHandler的invoke()方法,该方法也是所有代理对象所有方法的唯一实现。

 service对象可以想象为下面的实现:

public class X implements LoginService {
    private InvocationHandler h;

    public X(InvocationHandler h) {
        this.h = h;
    }

    @Override
    public boolean checkUser() {
        return h.invoke();
    }
}

 事实上,可以通过设置JVM参数来得到动态生成的class文件,然后反编译可以看出该代理类继承了Proxy类,这也正是为什么JDK动态代理只能实现接口代理而不能实现类代理的原因,因为JAVA不允许多重继承。

 这里简单描述一下生成JDK动态代理实现的过程,感兴趣的同学可以移步https://www.cnblogs.com/zuidongfeng/p/8735241.html。

  step 1、为接口创建代理类的字节码文件$Proxy0,该类继承自java.lang.reflect.Proxy,实现了用户自定义的接口;

  step 2、通过Class.getConstructor()获得类$Proxy0的含参数InvocationHandler的构造方法,该构造方法继承至Proxy;

  step 3、调用Constructor.newInstance(InvocationHandler)生成$Proxy0类的实例对象,即最终的代理实现。

 其中step 1为关键步骤,又可细分为下面几小步:

  (1)加载及校验传入的对象,主要是校验是否为接口类型,以及是否重复;

  (2)为代理类生成类名,命名规则为com.sun.proxy.$Proxy{n};

  (3)调用ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags)生成代理类的字节码文件,同时根据sun.misc.ProxyGenerator.saveGeneratedFiles取值决定是否写入本地磁盘;

  (4)调用native方法将字节码加载到JVM中

2、示例

public class JDKProxyTest {

    public static void main(String[] args) {

        // 该参数可以让动态代理对象生成class文件,文件保存地址为工程根目录下(注意不是src)的/com/sun/proxy包下
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        //创建目标对象
        LoginService loginService = new LoginServiceImpl();

        //创建LoginService代理对象
        ProxyHandler proxyHandler = new ProxyHandler();
        proxyHandler.setTarget(loginService);

        LoginService loginServiceProxy = (LoginService) Proxy.newProxyInstance(LoginService.class.getClassLoader(),
                loginService.getClass().getInterfaces(), proxyHandler);
        loginServiceProxy.checkUser();
    }

    public static class ProxyHandler implements InvocationHandler {
        private Object target;

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

        @Override
        public Object invoke(Object proxy, Method method, Object[] param)
                throws Throwable {
            System.out.println("**********JDK动态代理方法执行前************");
            Object retObj = method.invoke(target, param);
            System.out.println("**********JDK动态代理方法执行后************");
            return retObj;
        }
    }
}
public final class $Proxy0 extends Proxy implements LoginService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final boolean checkUser() throws  {
        try {
            return (Boolean)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.apple.mercury.proxy.LoginService").getMethod("checkUser");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

3、优点

 动态代理类不需要显示的实现被代理类所实现的接口

 使用Java原生的反射API进行操作,在类的生成上比较高效

4、缺点

 只能对接口进行代理,不能对普通类进行代理

四、CGLIB动态代理

1、原理

 Cglib是一个强大的、高性能的代码生成包,它广泛被许多AOP框架使用,为他们提供方法的拦截。下图是Cglib与一些框架和语言的关系:
在这里插入图片描述
 最底层的Bytecode,字节码是Java为了保证“一次编译、到处运行”而产生的一种虚拟指令格式;

 位于字节码之上的是ASM,这是一种直接操作字节码的框架,CGLIB就是通过它生成的字节码;

 位于ASM之上的是CGLIB、Groovy、BeanShell,它们通过ASM框架生成字节码变相执行Java代码,这说明在JVM中执行程序并不一定非要写Java代码,只要你能生成Java字节码,JVM并不关心字节码的来源;

 位于CGLIB之上的就是Hibernate、Spring AOP这些框架了;

 最上层的是Applications,即具体的应用。

2、示例

public class CglibProxyTest {

    public static void main(String[] args) {
        // 设置生成的class文件保存路径
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");

        //创建目标对象
        LoginService loginService = new LoginServiceImpl();

        //创建代理对象
        CglibProxy proxy = new CglibProxy();
        LoginService loginServiceProxy = (LoginService) proxy.newProxy(loginService);

        loginServiceProxy.checkUser();
    }

    public static class CglibProxy implements MethodInterceptor {
        @Override
        public Object intercept(Object proxy, Method method, Object[] params,
                                MethodProxy methodProxy) throws Throwable {
            System.out.println("**********cglib动态代理方法执行前************");
            Object retObj = methodProxy.invokeSuper(proxy, params);
            System.out.println("**********cglib动态代理方法执行后************");
            return retObj;
        }

        //返回目标对象的代理对象
        private Object newProxy(Object target) {
            /** Enhancer 既能够代理普通的class,也能够代理接口
             *  Enhancer 创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)
             *  Enhancer 不能够拦截final方法
             */
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(target.getClass());
            enhancer.setCallback(this);
            enhancer.setClassLoader(target.getClass().getClassLoader());
            return enhancer.create();
        }
    }
}
public class LoginServiceImpl$$EnhancerByCGLIB$$234d923c extends LoginServiceImpl implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    ……

    final boolean CGLIB$checkUser$0() {
        return super.checkUser();
    }

    // 覆写了父类LoginServiceImpl的checkUser方法
    public final boolean checkUser() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            Object var1 = var10000.intercept(this, CGLIB$checkUser$0$Method, CGLIB$emptyArgs, CGLIB$checkUser$0$Proxy);
            return var1 == null ? false : (Boolean)var1;
        } else {
            return super.checkUser();
        }
    }

    ……
}

3、优点

 能对接口和类进行代理

 直接对字节码进行操作,执行效率比较高

4、缺点

 Cglib原理是针对目标类生成一个子类,覆盖其中的所有方法,所以目标类和方法不能声明为final类型
代理实现过程复杂,生成代理类效率比JDK低

 由于是对字节码进行操作,生成的类会在JVM的永久堆中,如果此类代理生成过多,容易导致OOM

参考:

 1、https://blog.csdn.net/justloveyou_/article/details/79407248

 2、https://www.cnblogs.com/zuidongfeng/p/8735241.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值