1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Java代理簡述

        共 32839字,需瀏覽 66分鐘

         ·

        2021-05-02 07:00

        點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”

        優(yōu)質(zhì)文章,第一時間送達(dá)

          作者 |  碼猿手

        來源 |  urlify.cn/qQBvMf

         1.什么是代理?

          對類或?qū)ο螅繕?biāo)對象)進(jìn)行增強(qiáng)功能,最終形成一個新的代理對象,(Spring Framework中)當(dāng)應(yīng)用調(diào)用該對象(目標(biāo)對象)的方法時,實(shí)際調(diào)用的是代理對象增強(qiáng)后的方法,比如對功能方法login實(shí)現(xiàn)日志記錄,可以通過代理實(shí)現(xiàn);

          PS:

        目標(biāo)對象--被增強(qiáng)的對象;

        代理對象--增強(qiáng)后的對象;

          2.為什么需要代理?

          一些類里面的方法有相同的代碼或類中有相同的功能,可以將這些相同抽取出來形成一個公共的方法或功能,但Java有兩個重要的原則:

        單一職責(zé)(對類來說的,即一個類應(yīng)該只負(fù)責(zé)一項(xiàng)職責(zé))和開閉原則(開放擴(kuò)展,修改關(guān)閉),如果每個類的每個功能都調(diào)用了公共功能,就破壞了單一職責(zé),如下圖;

        如果這個類是別人已經(jīng)寫好的,你動了這個代碼,同時也破壞了開閉原則(同時改動代碼很麻煩,里面可能涉及其他很多的調(diào)用,可能帶出無數(shù)的bug,改代碼比新開發(fā)功能還難/(ㄒoㄒ)/~~);

          由于有上面的問題存在,使用代理來實(shí)現(xiàn)是最好的解決辦法;

          3.Java實(shí)現(xiàn)代理有哪些?

         ?。?)靜態(tài)代理:通過對目標(biāo)方法進(jìn)行繼承或聚合(接口)實(shí)現(xiàn);

        (會產(chǎn)生類爆炸,因此在不確定的情況下,盡量不要使用靜態(tài)代理,避免產(chǎn)生類爆炸)   1)繼承:代理對象繼承目標(biāo)對象,重寫需要增強(qiáng)的方法;

        //業(yè)務(wù)類(目標(biāo)對象)
        public class UserServiceImpl {
            public void query(){
                System.out.println("業(yè)務(wù)操作查詢數(shù)據(jù)庫....");
            }
        }
        //日志功能類
        public class Log {
            public static void info(){
                System.out.println("日志功能");
            }
        }
        //繼承實(shí)現(xiàn)代理(代理對象)
        public class UserServiceLogImpl extends UserServiceImpl {
            public void query(){
                Log.info();
                super.query();
            }
        }

            從上面代碼可以看出,每種增強(qiáng)方法會產(chǎn)生一個代理類,如果現(xiàn)在增強(qiáng)方法有日志和權(quán)限,單個方法增強(qiáng)那需要兩個代理類(日志代理類和權(quán)限代理類),如果代理類要同時擁有日志和權(quán)限功能,那又會產(chǎn)生一個代理類,同時由于順序的不同,可能會產(chǎn)生多個類,比如先日志后權(quán)限是一個代理類,先權(quán)限后日志又是另外一個代理類。

            由此可以看出代理使用繼承的缺陷:

        產(chǎn)生的代理類過多(產(chǎn)生類爆炸),非常復(fù)雜,維護(hù)難;

            2)聚合:

        代理對象和目標(biāo)對象都實(shí)現(xiàn)同一接口,使用裝飾者模式,提供一個代理類構(gòu)造方法(代理對象當(dāng)中要包含目標(biāo)對象),參數(shù)是接口,重寫目標(biāo)方法;

        //接口
        public interface UserDao {
            void query();
        }
        //接口實(shí)現(xiàn)類:目標(biāo)對象
        public class UserDaoImpl implements  UserDao {
            @Override
            public void query() {
                System.out.println("query......");
            }
        }
        //日志功能類
        public class Log {
            public static void info(){
                System.out.println("日志功能");
            }
        }
        //聚合實(shí)現(xiàn)代理:同樣實(shí)現(xiàn)接口,使用裝飾者模式;(代理對象)
        public class UserDaoLogProxy implements UserDao {
            UserDao userDao;
            public UserDaoLogProxy(UserDao userDao){//代理對象包含目標(biāo)對象
                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();
        }


            聚合由于利用了面向接口編程的特性,產(chǎn)生的代理類相對繼承要少一點(diǎn)(雖然也是會產(chǎn)生類爆炸,假設(shè)有多個Dao,每個Dao產(chǎn)生一個代理類,所以還是會產(chǎn)生類爆炸),如下案例:

        //時間記錄功能
        public class Timer {
            public static void timer(){
                System.out.println("時間記錄功能");
            }
        }
        //代理類:時間功能+業(yè)務(wù)
        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:

            裝飾和代理的區(qū)別:

        代理不需要指定目標(biāo)對象,可以對任何對象進(jìn)行代理;

        裝飾需要指定目標(biāo)對象,所以需要構(gòu)造方法參數(shù)或set方法指定目標(biāo)對象;

            幾個io的Buffer流(BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter)就是使用了裝飾模式的靜態(tài)代理;

        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)動態(tài)代理:Java有JDK動態(tài)代理和CGLIB代理;

        (Spring Framework通過Spring AOP實(shí)現(xiàn)代理,底層還是使用JDK代理和CGLIB代理來實(shí)現(xiàn))    

        模擬動態(tài)代理:不需要手動創(chuàng)建類文件(因?yàn)橐坏┦謩觿?chuàng)建類文件,會產(chǎn)生類爆炸),通過接口反射生成一個類文件,然后調(diào)用第三方的編譯技術(shù),動態(tài)編譯這個產(chǎn)生的類文件成class文件,然后利用URLclassLoader把這個動態(tài)編譯的類加載到j(luò)vm中,然后通過反射把這個類實(shí)例化。

        (通過字符串產(chǎn)生一個對象實(shí)現(xiàn)代理);

            PS:

        Java文件 -> class文件 -> byte字節(jié)(JVM)-> class對象(類對象)-> new(對象);

            所以步驟如下:

              1)代碼實(shí)現(xiàn)一個內(nèi)容(完整的Java文件內(nèi)容,包含包名、變量、構(gòu)造方法、增強(qiáng)后的方法等),使其通過IO產(chǎn)生一個Java文件;

              2)通過第三方編譯技術(shù)產(chǎn)生一個class文件;

              3)加載class文件利用反射實(shí)例化一個代理對象出來;

        public class ProxyUtil {
            /**
             *  content --->string
             *     |
             *     |生成
             *     v
             *  .java   <-----通過io產(chǎn)生
             *  .class  <-----Java文件編程產(chǎn)生
             *
             *  .new    <-----class文件反射產(chǎn)生實(shí)例對象
             * @return
             */
            public static Object newInstance(Object target){
                Object proxy=null;
                //根據(jù)對象獲取接口
                Class targetInf =target.getClass().getInterfaces()[0];
                //獲取接口的所有方法
                //getMethods(),該方法是獲取本類以及父類或者父接口中所有的公共方法(public修飾符修飾的)
                //getDeclaredMethods(),該方法是獲取本類中的所有方法,包括私有的(private、protected、默認(rèn)以及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;
                //構(gòu)造方法
                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 參數(shù)類型
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    String argsContent = "";
                    String paramsContent="";
                    int flag = 0;
                    for(Class args :parameterTypes){
                        //String,參數(shù)類型
                        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
                            //增強(qiáng)方法先寫死
                            +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();
                    }
                    //創(chuàng)建
                    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);
                    //構(gòu)造方法創(chuàng)建實(shí)例
                    proxy = constructor.newInstance(target);
                    //clazz.newInstance();//根據(jù)默認(rèn)構(gòu)造方法創(chuàng)建對象
                    //Class.forName()
                }catch (Exception e){
                    e.printStackTrace();
                }
                return proxy;
            }
        }

          自定義代理的缺點(diǎn):

        生成Java文件;

        動態(tài)編譯文件;

        需要一個URLClassLoader;

        涉及到了IO操作,軟件的最終性能體現(xiàn)到了IO操作,即IO操作影響到軟件的性能;

        案例:

          1)接口:

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

         2)實(shí)現(xiàn)類:

        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("張三");
        }
        --------結(jié)果--------
        log
        query
        log
        張三

        package com;
        import com.hrh.dynamicProxy.dao.UserDao;
        public class $Proxy implements UserDao{//自定義動態(tài)代理生成的文件
            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動態(tài)代理:

        基于反射實(shí)現(xiàn),通過接口反射得到字節(jié)碼,然后將字節(jié)碼轉(zhuǎn)成class,通過一個native(JVM實(shí)現(xiàn))方法來執(zhí)行;

          案例實(shí)現(xiàn):

        實(shí)現(xiàn) InvocationHandler 接口重寫 invoke 方法,其中包含一個對象變量和提供一個包含對象的構(gòu)造方法;

        public class MyInvocationHandler implements InvocationHandler {
            Object target;//目標(biāo)對象
            public MyInvocationHandler(Object target){
                this.target=target;
            }
            /**
             * @param proxy 代理對象
             * @param method 目標(biāo)對象的目標(biāo)方法
             * @param args    目標(biāo)方法的參數(shù)
             */
            @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) {
                //參數(shù): 當(dāng)前類的classLoader(保證MyInvocationHandlerTest當(dāng)前類可用)
                //         接口數(shù)組:通過接口反射得到接口里面的方法,對接口里面的所有方法都進(jìn)行代理
                //         實(shí)現(xiàn)的InvocationHandler:參數(shù)是目標(biāo)對象
                UserDao jdkproxy = (UserDao) Proxy.newProxyInstance(MyInvocationHandlerTest.class.getClassLoader(),
                        new Class[]{UserDao.class},new MyInvocationHandler(new UserService()));
                jdkproxy.query("query");
                //-----結(jié)果------
                //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反編譯后的內(nèi)容:

        下面的內(nèi)容驗(yàn)證了JDK動態(tài)代理為什么基于聚合(接口)來的,而不能基于繼承來的?

        因?yàn)镴DK動態(tài)代理底層已經(jīng)繼承了Proxy,而Java是單繼承,不支持多繼承,所以以接口來實(shí)現(xiàn);

        //
        // 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(一個操作字節(jié)碼的框架)實(shí)現(xiàn)代理操作;

        CGLIB基于繼承來的(前文有CGLIB代理類class的反編譯可以看出);

        public class UserService {
            public void query(){
                System.out.println("query");
            }

            public static void main(String[] args) {
                // 通過CGLIB動態(tài)代理獲取代理對象的過程
                Enhancer enhancer = new Enhancer();
                // 設(shè)置enhancer對象的父類
                enhancer.setSuperclass(UserService.class);
                // 設(shè)置enhancer的回調(diào)對象
                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;
                    }
                });
                //創(chuàng)建代理對象
                UserService bean = (UserService) enhancer.create();
                bean.query();
            }
        }//-----------結(jié)果------before method run...queryafter method run...

          PS:如果只是對項(xiàng)目中一個類進(jìn)行代理,可以使用靜態(tài)代理,如果是多個則使用動態(tài)代理;





        粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

        ??????

        ??長按上方微信二維碼 2 秒


        感謝點(diǎn)贊支持下哈 

        瀏覽 41
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            被摁着cao到哭尿到里面视频 | 九色porny丨入口在线 | 一级伦奷片高潮无码看了5 | 中国处女操逼视频 | 蜜臀久久久久久 | 91嫩草精品少妇91嫩草影视剧 | 我才15就同学破了处好爽 | 骚逼自拍| 免费观看国产黄片 | 成人偷拍自拍视频 |