当前位置:首页 > CN2资讯 > 正文内容

Java代理简述java静态代理和动态代理

2天前CN2资讯

代理简述

  1.什么是代理?

  对类或对象(目标对象)进行增强功能,最终形成一个新的代理对象,(Spring Framework中)当应用调用该对象(目标对象)的方法时,实际调用的是代理对象增强后的方法,比如对功能方法login实现日志记录,可以通过代理实现;

  PS:目标对象--被增强的对象;代理对象--增强后的对象;

  2.为什么需要代理?

  一些类里面的方法有相同的代码或类中有相同的功能,可以将这些相同抽取出来形成一个公共的方法或功能,但Java有两个重要的原则:单一职责(对类来说的,即一个类应该只负责一项职责)和开闭原则(开放扩展,修改关闭),如果每个类的每个功能都调用了公共功能,就破坏了单一职责,如下图;如果这个类是别人已经写好的,你动了这个代码,同时也破坏了开闭原则(同时改动代码很麻烦,里面可能涉及其他很多的调用,可能带出无数的bug,改代码比新开发功能还难/(ㄒoㄒ)/~~);

  由于有上面的问题存在,使用代理来实现是最好的解决办法;

  3.Java实现代理有哪些?

  (1)静态代理:通过对目标方法进行继承或聚合(接口)实现;(会产生类爆炸,因此在不确定的情况下,尽量不要使用静态代理,避免产生类爆炸)

    1)继承:代理对象继承目标对象,重写需要增强的方法;

//业务类(目标对象)public class UserServiceImpl {    public void query(){         System.out.println("业务操作查询数据库....");     } }//日志功能类public class Log {    public static void info(){         System.out.println("日志功能");     } }//继承实现代理(代理对象)public class UserServiceLogImpl extends UserServiceImpl {    public void query(){         Log.info();        super.query();     } }

    从上面代码可以看出,每种增强方法会产生一个代理类,如果现在增强方法有日志和权限,单个方法增强那需要两个代理类(日志代理类和权限代理类),如果代理类要同时拥有日志和权限功能,那又会产生一个代理类,同时由于顺序的不同,可能会产生多个类,比如先日志后权限是一个代理类,先权限后日志又是另外一个代理类。

    由此可以看出代理使用继承的缺陷:产生的代理类过多(产生类爆炸),非常复杂,维护难;

    2)聚合:代理对象和目标对象都实现同一接口,使用装饰者模式,提供一个代理类构造方法(代理对象当中要包含目标对象),参数是接口,重写目标方法;

//接口public interface UserDao {    void query(); }//接口实现类:目标对象public class UserDaoImpl implements  UserDao {     @Override    public void query() {         System.out.println("query......");     } }//日志功能类public class Log {    public static void info(){         System.out.println("日志功能");     } }//聚合实现代理:同样实现接口,使用装饰者模式;(代理对象)public class UserDaoLogProxy implements UserDao {     UserDao userDao;    public UserDaoLogProxy(UserDao userDao){//代理对象包含目标对象        this.userDao = userDao;     }     @Override    public void query() {         Log.info();         userDao.query();     } }//测试public static void main(String[] args) {     UserDaoImpl target = new UserDaoImpl();     UserDaoLogProxy proxy = new UserDaoLogProxy(target);     proxy.query(); }

    聚合由于利用了面向接口编程的特性,产生的代理类相对继承要少一点(虽然也是会产生类爆炸,假设有多个Dao,每个Dao产生一个代理类,所以还是会产生类爆炸),如下案例:

//时间记录功能public class Timer {    public static void timer(){         System.out.println("时间记录功能");     } }//代理类:时间功能+业务public class UserDaoTimerProxy implements UserDao {     UserDao userDao;    public UserDaoTimerProxy(UserDao userDao){        this.userDao = userDao;     }     @Override    public void query() {         Timer.timer();         userDao.query();     } }//测试public static void main(String[] args) {    //timer+query     UserDao target = new UserDaoTimerProxy(new UserDaoImpl());    //log+timer+query     UserDao proxy = new UserDaoLogProxy(target);     proxy.query(); }public static void main(String[] args) {    //log+query     UserDao target = new UserDaoLogProxy(new UserDaoImpl());    //timer+log+query     UserDao proxy = new UserDaoTimerProxy(target);     proxy.query(); }

  PS:

    装饰和代理的区别:代理不需要指定目标对象,可以对任何对象进行代理;装饰需要指定目标对象,所以需要构造方法参数或set方法指定目标对象;

    几个io的Buffer流(BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter)就是使用了装饰模式的静态代理;

public class BufferedReader extends Reader {    private Reader in;     ........    public BufferedReader(Reader in, int sz) {        super(in);        if (sz <= 0)            throw new IllegalArgumentException("Buffer size <= 0");        this.in = in;         cb = new char[sz];         nextChar = nChars = 0;     }    public BufferedReader(Reader in) {        this(in, defaultCharBufferSize);     } }

  (2)动态代理:Java有JDK动态代理和CGLIB代理;(Spring Framework通过Spring AOP实现代理,底层还是使用JDK代理和CGLIB代理来实现)

    模拟动态代理:不需要手动创建类文件(因为一旦手动创建类文件,会产生类爆炸),通过接口反射生成一个类文件,然后调用第三方的编译技术,动态编译这个产生的类文件成class文件,然后利用URLclassLoader把这个动态编译的类加载到jvm中,然后通过反射把这个类实例化。(通过字符串产生一个对象实现代理);

    PS:Java文件 -> class文件 -> byte字节(JVM)-> class对象(类对象)-> new(对象);

    所以步骤如下:

      1)代码实现一个内容(完整的Java文件内容,包含包名、变量、构造方法、增强后的方法等),使其通过IO产生一个Java文件;       2)通过第三方编译技术产生一个class文件;       3)加载class文件利用反射实例化一个代理对象出来;public class ProxyUtil {    /**      *  content --->string      *     |      *     |生成      *     v      *  .java   <-----通过io产生      *  .class  <-----Java文件编程产生      *      *  .new    <-----class文件反射产生实例对象      * @return      */     public static Object newInstance(Object target){         Object proxy=null;        //根据对象获取接口         Class targetInf =target.getClass().getInterfaces()[0];        //获取接口的所有方法        //getMethods(),该方法是获取本类以及父类或者父接口中所有的公共方法(public修饰符修饰的)        //getDeclaredMethods(),该方法是获取本类中的所有方法,包括私有的(private、protected、默认以及public)的方法         Method[] declaredMethods = targetInf.getDeclaredMethods();         String line="\n";         String tab ="\t";        //接口名称         String targetInfName = targetInf.getSimpleName();         String content ="";        //包位置         String packageContent = "package com;"+line;        //接口         String importContent = "import "+targetInf.getName()+";"+line;         String clazzFirstLineContent = "public class $Proxy implements "+targetInfName+"{"+line;        //属性         String filedContent  =tab+"private "+targetInfName+" target;"+line;        //构造方法         String constructorContent =tab+"public $Proxy ("+targetInfName+" target){" +line                +tab+tab+"this.target =target;"                 +line+tab+"}"+line;        //方法         String methodContent = "";        for(Method method:declaredMethods){            //返回值             String returnTypeName = method.getReturnType().getSimpleName();            //方法名             String methodName = method.getName();            // Sting.class String.class 参数类型             Class[] parameterTypes = method.getParameterTypes();             String argsContent = "";             String paramsContent="";            int flag = 0;            for(Class args :parameterTypes){                //String,参数类型                 String simpleName = args.getSimpleName();                //String p0,Sting p1,                 argsContent+=simpleName+" p"+flag+",";                 paramsContent+="p"+flag+",";                 flag++;             }            if (argsContent.length()>0){                 argsContent=argsContent.substring(0,argsContent.lastIndexOf(",")-1);                 paramsContent=paramsContent.substring(0,paramsContent.lastIndexOf(",")-1);             }             methodContent+=tab+"public "+returnTypeName+" "+methodName+"("+argsContent+") {"+line                    //增强方法先写死                     +tab+tab+"System.out.println(\"log\");"+line;            if(returnTypeName.equals("void")){                 methodContent+=tab+tab+"target."+methodName+"("+paramsContent+");"+line                        +tab+"}"+line;             }else{                 methodContent+=tab+tab+"return target."+methodName+"("+paramsContent+");"+line                        +tab+"}"+line;             }         }         content+=packageContent+importContent+clazzFirstLineContent+filedContent                +constructorContent+methodContent+"}";        //生成Java文件         File file = new File("D:\\com\\$Proxy.java");        try{            if(!file.exists()){                 file.createNewFile();             }            //创建             FileWriter wr = new FileWriter(file);             wr.write(content);             wr.flush();             wr.close();            //编译Java文件             JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();             StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);             Iterable units = fileMgr.getJavaFileObjects(file);             JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);             t.call();             fileMgr.close();            //new --> 反射             URL[] urls = new URL[]{new URL("file:D:\\\\")};            //加载外部文件             URLClassLoader classLoader = new URLClassLoader(urls);             Class proxyClass = classLoader.loadClass("com.$Proxy");             Constructor constructor = proxyClass.getConstructor(targetInf);            //构造方法创建实例             proxy = constructor.newInstance(target);            //clazz.newInstance();//根据默认构造方法创建对象            //Class.forName()         }catch (Exception e){             e.printStackTrace();         }        return proxy;     } }

  自定义代理的缺点:生成Java文件;动态编译文件;需要一个URLClassLoader;涉及到了IO操作,软件的最终性能体现到了IO操作,即IO操作影响到软件的性能;

案例:

  1)接口:

public interface UserDao {     void query();     void query(String name);      String getName(String id); }

  2)实现类:

public class UserService implements UserDao {     @Override    public void query() {         System.out.println("query");     }     @Override    public void query(String name) {         System.out.println(name);     }     @Override    public String getName(String id) {         System.out.println("id:"+id);        return "李四";     } }

  3)测试:

public static void main(String[] args) {     UserDao dao = (UserDao) ProxyUtil.newInstance(new UserService());     dao.query();     dao.query("张三"); }--------结果--------log query log 张三

          

package com;import com.hrh.dynamicProxy.dao.UserDao;public class $Proxy implements UserDao{//自定义动态代理生成的文件    private UserDao target;    public $Proxy (UserDao target){        this.target =target;     }    public String getName(String p) {         System.out.println("log");        return target.getName(p);     }    public void query(String p) {         System.out.println("log");         target.query(p);     }    public void query() {         System.out.println("log");         target.query();     } }

  JDK动态代理:基于反射实现,通过接口反射得到字节码,然后将字节码转成class,通过一个native(JVM实现)方法来执行;

  案例实现:实现 InvocationHandler 接口重写 invoke 方法,其中包含一个对象变量和提供一个包含对象的构造方法;

public class MyInvocationHandler implements InvocationHandler {     Object target;//目标对象    public MyInvocationHandler(Object target){        this.target=target;     }    /**      * @param proxy 代理对象      * @param method 目标对象的目标方法      * @param args    目标方法的参数     */     @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {         System.out.println("log");        return method.invoke(target,args);     } }public class MyInvocationHandlerTest {    public static void main(String[] args) {        //参数: 当前类的classLoader(保证MyInvocationHandlerTest当前类可用)        //         接口数组:通过接口反射得到接口里面的方法,对接口里面的所有方法都进行代理        //         实现的InvocationHandler:参数是目标对象         UserDao jdkproxy = (UserDao) Proxy.newProxyInstance(MyInvocationHandlerTest.class.getClassLoader(),                new Class[]{UserDao.class},new MyInvocationHandler(new UserService()));         jdkproxy.query("query");        //-----结果------        //log        //query    } }

  下面查看底层JDK生成的代理类class:

        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy18", new Class[]{UserDao.class});        try {             FileOutputStream fileOutputStream = new FileOutputStream("xxx本地路径\\$Proxy18.class");             fileOutputStream.write(bytes);             fileOutputStream.flush();         } catch (FileNotFoundException e) {             e.printStackTrace();         } catch (IOException e) {             e.printStackTrace();         }

  或在代码的最前面添加下面代码:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "xxx本地路径");

  代理类class反编译后的内容:下面的内容验证了JDK动态代理为什么基于聚合(接口)来的,而不能基于继承来的?因为JDK动态代理底层已经继承了Proxy,而Java是单继承,不支持多继承,所以以接口来实现;

//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//import com.hrh.dao.UserDao;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy18 extends Proxy implements UserDao {    private static Method m1;    private static Method m4;    private static Method m5;    private static Method m2;    private static Method m0;    private static Method m3;    public $Proxy18(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 void query(String var1) throws  {        try {            super.h.invoke(this, m4, new Object[]{var1});         } catch (RuntimeException | Error var3) {            throw var3;         } catch (Throwable var4) {            throw new UndeclaredThrowableException(var4);         }     }    public final void query() throws  {        try {            super.h.invoke(this, m5, (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);         }     }    public final String getName(String var1) throws  {        try {            return (String)super.h.invoke(this, m3, new Object[]{var1});         } catch (RuntimeException | Error var3) {            throw var3;         } catch (Throwable var4) {            throw new UndeclaredThrowableException(var4);         }     }    static {        try {             m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));             m4 = Class.forName("com.hrh.dao.UserDao").getMethod("query", Class.forName("java.lang.String"));             m5 = Class.forName("com.hrh.dao.UserDao").getMethod("query");             m2 = Class.forName("java.lang.Object").getMethod("toString");             m0 = Class.forName("java.lang.Object").getMethod("hashCode");             m3 = Class.forName("com.hrh.dao.UserDao").getMethod("getName", Class.forName("java.lang.String"));         } catch (NoSuchMethodException var2) {            throw new NoSuchMethodError(var2.getMessage());         } catch (ClassNotFoundException var3) {            throw new NoClassDefFoundError(var3.getMessage());         }     } }

  CGLIB代理:借助asm(一个操作字节码的框架)实现代理操作;CGLIB基于继承来的(前文有CGLIB代理类class的反编译可以看出);

public class UserService {    public void query(){         System.out.println("query");     }    public static void main(String[] args) {        // 通过CGLIB动态代理获取代理对象的过程         Enhancer enhancer = new Enhancer();        // 设置enhancer对象的父类         enhancer.setSuperclass(UserService.class);        // 设置enhancer的回调对象         enhancer.setCallback(new MethodInterceptor() {             @Override            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {                 System.out.println("before method run...");                 Object result = proxy.invokeSuper(obj, args);                 System.out.println("after method run...");                return result;             }         });        //创建代理对象         UserService bean = (UserService) enhancer.create();         bean.query();     } } //-----------结果------ before method run... query after method run...

  PS:如果只是对项目中一个类进行代理,可以使用静态代理,如果是多个则使用动态代理;

  关于代理的其他相关知识介绍可参考前文:Spring(11) - Introductions进行类扩展方法、Spring笔记(3) - SpringAOP基础详解和源码探究

    你可能想看:

    扫描二维码推送至手机访问。

    版权声明:本文由皇冠云发布,如需转载请注明出处。

    本文链接:https://www.idchg.com/info/24314.html

    分享给朋友:

    “Java代理简述java静态代理和动态代理” 的相关文章

    Siteground怎么样?深入分析其安全性、正常运行时间与客户支持

    Siteground的安全性实践 谈到Siteground的安全性实践,我总是很欣赏他们的努力。作为一个成立于2004年的托管服务商,Siteground在安全方面采取了多重措施。我注意到,首先,他们为所有用户提供免费的Let’s Encrypt SSL证书。SSL证书能够加密网站与访客之间的数据,...

    腾讯云国际站:助力企业全球化发展的云计算服务平台

    腾讯云国际站是腾讯云为全球用户打造的云计算服务平台,其目的是为企业和开发者提供强大的技术支持。这一平台的核心特点在于其全球化的服务网络与数据中心布局,让每位用户都能感受到来自不同地区的高效服务。 全球服务与数据中心特点 我对腾讯云国际站的全球服务网络感到非常惊艳。它在全球开通了21个地理区域,涵盖了...

    iHerb优惠码使用指南:最大化你的购物折扣

    在现代生活中,网络购物已经成为很多人日常消费的一部分,尤其是像iHerb这样的电商平台,提供了丰富的天然保健品和生活必需品。对于我们消费者来说,iHerb优惠码就是一个能够让购物更加实惠的绝佳工具。 iHerb优惠码是一种特殊的代码,用户在结账时输入这些代码,就可以享受相应的折扣。无论是新用户还是老...

    WordPress reCAPTCHA插件:提升网站安全与用户体验的最佳解决方案

    reCAPTCHA插件概述 在今天的网络环境中,安全性愈发重要,尤其是对于使用WordPress的网站。WordPress reCAPTCHA插件成为了一种流行的解决方案,它借助Google强大的reCAPTCHA服务,帮助我们有效地区分真实用户和可能扰乱网站的机器程序。在我接触这个插件之后,发现它...

    搬瓦工VPS:初学者的最佳选择与使用指南

    大家对VPS可能不太熟悉,搬瓦工VPS在整个市场中已经站稳了脚跟。作为加拿大IT7公司旗下的一款主机服务,搬瓦工以其性价比高的OpenVZ VPS起步,而现在主要以KVM架构为主流,逐渐发展成为国内用户的热门选择。这种转型让我觉得很有意思,因为它不仅仅是一种产品的升级,更是一种服务的提升。 搬瓦工提...

    解决Windows无法使用复制粘贴功能的实用方法

    在计算机使用中,Windows的复制粘贴功能是我们高效工作与学习的得力助手。从文字处理到文件管理,无论是在文档编辑中提取关键信息,还是将图片或文件快速转移到其他地方,复制粘贴都简化了许多操作。它轻松地帮助我们完成任务,节省了宝贵的时间。 我经常在工作中使用复制粘贴,特别是在撰写报告或为项目汇总资料时...