zoukankan      html  css  js  c++  java
  • 动态代理

    1.class Tank实现IMoveable接口,实现了move()方法 

    package com.sj.tank;
    
    public interface IMoveable {
        void move();
    }
    package com.sj.tank;
    
    import java.util.Random;
    
    public class Tank implements IMoveable {
    
        public void move() {
            System.out.println("Tank Moving...");
            try {
                Thread.sleep(new Random().nextInt(10000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    2. 假如不知道Tank类的源码,不能在其中的move()方法中直接添加代码的情况下,怎么知道move方法的执行时间?

    1)TankInherit继承Tank,重写其中的move()方法,在调用super.move()方法的前后添加逻辑,来记录时间:

    public class TankInherit extends Tank {
        @Override
        public void move() {
            long start = System.currentTimeMillis();
            super.move();
            long end = System.currentTimeMillis();
            System.out.println("time: " + (end - start));
        }
    }

    2)聚合,实现接口的方式,TankImplementingInterface也实现IMoveable接口,保存Tank类,在调用Tank.move方法的前后添加逻辑,来记录时间:

    public class TankImplementingInterface implements IMoveable {
        private Tank tank;
    
        public TankImplementingInterface(Tank tank) {
            this.tank = tank;
        }
    
        public void move() {
            long start = System.currentTimeMillis();
            System.out.println("starttime: " + start);
            tank.move();
            long end = System.currentTimeMillis();
            System.out.println("time: " + (end - start));
        }
    }

    继承、实现接口(聚合)两种方式都实现了对class Tank中move()方法的代理,哪种方法好呢?——聚合的方式好些,继承的方式不灵活

    为什么?

    比如还有个记录Tank move方法的日志代理:TankLogProxy:

    public class TankLogProxy implements IMoveable {
        private IMoveable iMoveable;
    
        public TankLogProxy(IMoveable iMoveable) {
            this.iMoveable = iMoveable;
        }
    
        public void move() {
            System.out.println("Tank Start...");
            iMoveable.move();
            System.out.println("Tank Stop.");
        }
    }

    现在考虑功能的叠加:先记录日志,再记录时间:

    如果用继承方式,TankInherit的代码就要改动了;或者弄个Tank4 extends TankInherit;要实现的代理功能更多,类就会无限制的增加下去;而且各个功能顺序可能不一样,比如先记录时间,再记录日志。

    实现接口(聚合、实现同一接口)的方式更容易实现:

    TankTimeProxy和TankLogProxy都实现了IMoveable接口,两个类可以互相代理:

    public class TankTimeProxy implements IMoveable {
        private IMoveable iMoveable;
    
        public TankTimeProxy(IMoveable iMoveable) {
            this.iMoveable = iMoveable;
        }
    
        public void move() {
            long start = System.currentTimeMillis();
            System.out.println("starttime: " + start);
            iMoveable.move();
            long end = System.currentTimeMillis();
            System.out.println("time: " + (end - start));
        }
    }

    测试代码:

    public class Client {
        public static void main(String[] args) {
            IMoveable tank = new Tank();
            //日志代理,记录tank.move()执行时间
            IMoveable timeProxy = new TankTimeProxy(tank);
            timeProxy.move();
            System.out.println("---------------------------------");
    
            //在时间外面加一层日志
            IMoveable logProxyTimeProxy = new TankLogProxy(timeProxy);
            logProxyTimeProxy.move();
        }
    }

    Tank、TankTimeProxy、TankLogProxy都实现了IMoveable接口,TankTimeProxy和TankLogProxy可以互相代理。

    timeProxy 和 logProxyTimeProxy 都是 IMoveable 类型的对象,logProxyTimeProxy 是 timeProxy 的代理对象。

    -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    以上的部分是静态代理,不够强大。

    比如 IMoveable中还有个方法:void stop();class Tank势必会实现 stop()方法;TankTimeProxy也要记录stop()方法的执行时间,

    那么记录时间的代码:

    long start = System.currentTimeMillis();

    long end = System.currentTimeMillis();

    也要在stop方法中重写一遍。

    就像这样:

    public interface Moveable {
        void move();
        void stop();
    }
    public class Tank implements Moveable {
        public void move() {
            System.out.println("Tank Moving...");
            try {
                Thread.sleep(new Random().nextInt(10000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public void stop() {
            System.out.println("Tank Stopping...");
        }
    }
    public class TankTimeProxy implements Moveable {
        private Moveable moveable;
    
        public TankTimeProxy(Moveable moveable) {
            this.moveable = moveable;
        }
    
        public void move() {
            long start = System.currentTimeMillis();
            System.out.println("starttime: " + start);
            moveable.move();
            long end = System.currentTimeMillis();
            System.out.println("time: " + (end - start));
        }
    
        public void stop() {
            long start = System.currentTimeMillis();
            System.out.println("starttime: " + start);
            moveable.stop();
            long end = System.currentTimeMillis();
            System.out.println("time: " + (end - start));
        }
    }

    会发现TankTimeProxy中move()和stop()中两段代码几乎一样,于是将它们封装起来:

    比如beforeMethod(), afterMethods(), 貌似这些代码可以重用了。

    现在考虑这个问题:

    TankTimeProxy不叫TankTimeProxy了,而是叫TimeProxy,可以把任何一个对象当做被代理对象,可以计算这个对象里面的任意一个方法的运行时间,怎么做?

    原始的这种方法肯定不行了,因为假如一个系统里面有100个类,要知道这100个类里面的方法运行了多重时间,那么就要为这100个类写代理对象,显然太不行了。

    思路:

    写个通用的时间代理类,可以对任意的对象进行代理,对象里面的任意方法都可以重写,前面加上start,后面加上end来计算时间:

    1)我们假设被代理的对象都实现了某个接口,真正代理的时候是根据这个接口来生成代理对象的,

    怎么样生成这个代理对象呢?

    2)假设com.sj.proxy.Proxy1能产生一个代理类——Proxy1.java:

    public class Proxy1 {
        //用来产生新的代理类
        public static Object newProxyInstance() {
            String rt = "
    ";
            String src = "package com.sj.proxy;" + rt +
                    "public class TankTimeProxy implements Moveable {" + rt +
                    "    private Moveable moveable;" + rt +
    
                    "    public TankTimeProxy(Moveable moveable) {" + rt +
                    "        this.moveable = moveable;" + rt +
                    "    }" + rt +
    
                    "    public void move() {" + rt +
                    "        long start = System.currentTimeMillis();" + rt +
                    "        System.out.println("starttime: " + start);" + rt +
                    "        moveable.move();" + rt +
                    "        long end = System.currentTimeMillis();" + rt +
                    "        System.out.println("time: " + (end - start));" + rt +
                    "    }" + rt +
                    "}";
    
            return null;
        }
    }

    String src声明了一段源码,假设这段源码能够编译,产生一个新的类,再把这个类load到内存,用这个类产生一个对象;这个对象就是实现了TimeProxy逻辑的对象。

    问题就变成了,如何把这段代码动态的编译。原来的TankTimeProxy类就不需要了,这个类的名字不重要,调用Proxy.newProxyInstance()方法就能返回一个具体的代理对象。

    如何动态地编译这段String src的源码呢?有这些:JDK6 Compiler API,CGLib,ASM。JavaCompiler:Java编译器。CGLib:Code Generation Library是Code生产类库,它可以在运行期扩展Java类与实现Java接口。ASM:ASM是一个Java字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM可以直接产生二进制class文件,也可以在类被加载入Java虚拟机之前动态改变类行为。ASM从类文件中读取信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

    3)现在解决编译这段String src源代码的问题。

    下面测试程序是来编译string src这样源码的。 

    com.sj.compiler.test.Test1

    问题1、Usage of API documented as @since 1.6+

    File ->Project Structure->Project Settings -> Modules -> 你的Module名字 -> Sources -> Language Level->选个默认的就行。

    问题2、Error:java: Compilation failed: internal java compiler error

    修改完pom.xml,需要Reimport才生效。

    问题3、Exception in thread "main" java.lang.ClassNotFoundException: com.sj.proxy.TankTimeProxy 

    在 urlClassLoader.loadClass 打个断点,debug一次就可以了。

    什么原因导致的,目前不知道。如果哪个大神知道,请留言告诉我。感谢!

    package com.sj.compiler.test;
    
    import com.sj.proxy.Moveable;
    import com.sj.proxy.Tank;
    
    import javax.tools.JavaCompiler;
    import javax.tools.JavaFileObject;
    import javax.tools.StandardJavaFileManager;
    import javax.tools.ToolProvider;
    import java.io.File;
    import java.io.FileWriter;
    import java.lang.reflect.Constructor;
    import java.net.URL;
    import java.net.URLClassLoader;
    
    public class Test1 {
        public static void main(String[] args) throws Exception {
            String rt = "
    ";
            String src = "package com.sj.proxy;" + rt +
                    "public class TankTimeProxy implements Moveable {" + rt +
                    "    private Moveable moveable;" + rt +
    
                    "    public TankTimeProxy(Moveable moveable) {" + rt +
                    "        this.moveable = moveable;" + rt +
                    "    }" + rt +
    
                    "    public void move() {" + rt +
                    "        long start = System.currentTimeMillis();" + rt +
                    "        System.out.println("starttime: " + start);" + rt +
                    "        moveable.move();" + rt +
                    "        long end = System.currentTimeMillis();" + rt +
                    "        System.out.println("time: " + (end - start));" + rt +
                    "    }" + rt +
                    "}";
    
            String fileUrl = System.getProperty("user.dir") + "/src/main/java/com/sj/proxy";
            //user.dir:当前项目的根路径
            //先把源代码写到下面
            String fileName = fileUrl + "/TankTimeProxy.java";
            File file = new File(fileName);
            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write(src);
            fileWriter.flush();
            fileWriter.close();
    
            //在程序中编译这段代码
            //JavaCompiler java编译器
            //ToolProvider.getSystemJavaCompiler() 拿到系统当前默认的java编译器,其实就是javac
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager javaFileManager = compiler.getStandardFileManager(null, null, null);                                //通过javaFileManager管理要编译的文件
            Iterable<? extends JavaFileObject> compilationUnits = javaFileManager.getJavaFileObjects(fileName);                         //拿到编译的内容
            JavaCompiler.CompilationTask compilationTask = compiler.getTask(null, javaFileManager, null, null, null, compilationUnits); //编译的任务
            compilationTask.getClass();                                                                                                 //编译
            javaFileManager.close();
    
            //生成了这个TankTimeProxy.class之后,将期load到内存,并且生成一个对象
            URL[] urls = new URL[]{new URL("file:/" + fileUrl)};
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            Class clazz = urlClassLoader.loadClass("com.sj.proxy.TankTimeProxy");
            //clazz.newInstance();//调用的是类的参数为空的构造方法,但是类中没有此构造方法。
            //站在java虚拟机的角度,每一个类、每一个类里面的每一个方法都是一个对象。
            Constructor constructor = clazz.getConstructor(Moveable.class);
            Moveable m = (Moveable) constructor.newInstance(new Tank());
            m.move();
        }
    }

    打印结果如下:

    starttime: 1514873531442
    Tank Moving...
    time: 3308

    Proxy中就可以通过Proxy.newProxyInstance()来返回一个代理对象了。

    4)现在确实能产生一个动态的代理了,但是现在产生的代理只能代理实现了Moveable接口的这样的一种代理;如果是实现了别的接口的就不行了,现在考虑能生产实现任意接口的代理类:

    要做的事情:

    1、在Proxy.newProxyInstance()方法中添加参数,Proxy.newProxyInstance(Class infce),将要实现的接口类型传进去,src源码中动态implements这个接口;

    2、要知道infce中有多少个方法,src中就要动态实现这些方法,在这些方法中添加逻辑;

    下面是代码实现:(没有模拟方法的参数和返回值)

    a、测试程序,利用反射,获取接口中的方法:

    public class Test2 {
        public static void main(String[] args) {
            //通过反射可以获取接口中有多少个方法
            Method[] methods = com.sj.proxy.Moveable.class.getMethods();
            for (Method m : methods) {
                System.out.println(m);//public abstract void com.sj.proxy.Moveable.move()
            }
        }
    }

    b、代码实现,代理实现任意接口的类:

    Proxy.java: newProxyInstance(Class infce):

    package com.sj.proxy;
    
    import javax.tools.JavaCompiler;
    import javax.tools.JavaFileObject;
    import javax.tools.StandardJavaFileManager;
    import javax.tools.ToolProvider;
    import java.io.File;
    import java.io.FileWriter;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    import java.net.URL;
    import java.net.URLClassLoader;
    
    public class Proxy2 {
    
        //用来产生新的代理类的对象
        public static Object newProxyInstance(Class infce) throws Exception {
            String rt = "
    ";
            String methodStr = "";
    
            Method[] methods = infce.getMethods();
            for (Method m : methods) {
                methodStr +=
                        "    public void " + m.getName() + "() {" + rt +
                                "        long start = System.currentTimeMillis();" + rt +
                                "        System.out.println("starttime: " + start);" + rt +
                                "        t." + m.getName() + "();" + rt +
                                "        long end = System.currentTimeMillis();" + rt +
                                "        System.out.println("time: " + (end - start));" + rt +
                                "    }" + rt;
            }
    
            String src = "package com.sj.proxy;" + rt +
                    "public class TankTimeProxy implements " + infce.getName() + " {" + rt +
                    "    private Moveable t;" + rt +
    
                    "    public TankTimeProxy(Moveable t) {" + rt +
                    "        this.t = t;" + rt +
                    "    }" + rt +
                    methodStr +
                    "}";
    
    //        String fileUrl = System.getProperty("user.dir") + "/src/main/java/com/sj/proxy";
            //user.dir:当前项目的根路径
            //先把源代码写到下面
    //        String fileName = fileUrl + "/TankTimeProxy.java";
            String fileName = "d:/src/com/sj/proxy/TankTimeProxy.java";
            File file = new File(fileName);
            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write(src);
            fileWriter.flush();
            fileWriter.close();
    
            //在程序中编译这段代码
            //JavaCompiler java编译器
            //ToolProvider.getSystemJavaCompiler() 拿到系统当前默认的java编译器,其实就是javac
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager javaFileManager = compiler.getStandardFileManager(null, null, null);                                //通过javaFileManager管理要编译的文件
            Iterable<? extends JavaFileObject> compilationUnits = javaFileManager.getJavaFileObjects(fileName);                         //拿到编译的内容
            JavaCompiler.CompilationTask compilationTask = compiler.getTask(null, javaFileManager, null, null, null, compilationUnits); //编译的任务
            compilationTask.getClass();                                                                                                 //编译
            javaFileManager.close();
    
            //生成了这个TankTimeProxy.class之后,将期load到内存,并且生成一个对象
    //        URL[] urls = new URL[]{new URL("file:/" + fileUrl)};
            URL[] urls = new URL[] {new URL("file:/" + "d:/src/")};
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            Class clazz = urlClassLoader.loadClass("com.sj.proxy.TankTimeProxy");
            //clazz.newInstance();//调用的是类的参数为空的构造方法,但是类中没有此构造方法。
            //站在java虚拟机的角度,每一个类、每一个类里面的每一个方法都是一个对象。
            Constructor constructor = clazz.getConstructor(infce);
            Object m = constructor.newInstance(new Tank());
    
            return m;
        }
    }

    //上面的代码中修改了生成源码文件、编译class文件的存放位置;

    // 利用反射,动态生成实现接口方法的字符串;为了简单起见,没有模拟方法的参数和返回值;

    c、测试代码

    package com.sj.test;

    import com.sj.proxy.Moveable;
    import com.sj.proxy.Proxy2;

    public class ProxyTest {
    public static void main(String[] args) throws Exception {
    Moveable m = (Moveable) Proxy2.newProxyInstance(Moveable.class);
    m.move();
    }
    }

    问题1、系统找不到指定的路径。

    自己手动创建文件目录

    打印结果如下:

    starttime: 1515052644972
    Tank Moving...
    time: 3944

    5)我们现在的Proxy能够对实现任意接口的类进行处理了,但是上面的Proxy中的生成逻辑的代码是固定的,只能是记录时间的代码,如果要生成一个记录日志的代理、权限的代理,就不行了;

    现在怎么让这段内容,也能让用户灵活的指定呢?

    long start = System.currentTimeMillis();

    long end = System.currentTimeMillis();

    ……

    也能让用户灵活地指定呢?

    最好是这段代码能够调用别人指定的处理方式。现在需要一个能够调用别人指定的处理方法的东西。

    调用别人的处理方法的东西:InvocationHandler.java

    InvocationHandler定义了一个接口,这个接口可以对方法进行处理,处理的方式由子类去实现。

    package com.sj.proxy;
    
    import java.lang.reflect.Method;
    
    public interface InvocationHandler {
        void invoke(Object o, Method m);    //假设返回值void
    }
    TimeHandler1.java
    package com.sj.proxy;
    
    import java.lang.reflect.Method;
    
    public class TimeHandler1 {
        /**
         * 对方法进行自定义处理,在调用给定方法的前后加上记录时间的逻辑
         * 方法的调用:对一个方法的调用,必须知道这个方法的当前对象是谁,也就是,this是谁
         *
         * @param object 对哪个对象调用这个方法,即对object这个对象调用method方法
         * @param method 想调用的那个方法,在调用这个方法的之前或之后做一些事情
         */
        public void invoke(Object object, Method method) {
            long start = System.currentTimeMillis();
            System.out.println("starttime: " + start);
    
            try {
                method.invoke(object, new Object[]{}); //假设方法中没有参数
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            long end = System.currentTimeMillis();
            System.out.println("time: " + (end - start));
        }
    }

    现在考虑Proxy中动态的代码该怎么生成了:

    Proxy.java:

    package com.sj.proxy;
    
    import javax.tools.JavaCompiler;
    import javax.tools.JavaFileObject;
    import javax.tools.StandardJavaFileManager;
    import javax.tools.ToolProvider;
    import java.io.File;
    import java.io.FileWriter;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    import java.net.URL;
    import java.net.URLClassLoader;
    
    public class Proxy {
    
        /**
         * 用来产生新的代理类
         *
         * @param infce   产生哪个接口的动态代理
         * @param handler 要实现什么样的代理,对接口中的方法前后进行什么样的处理,记录时间or...
         * @return
         * @throws Exception
         */
        public static Object newProxyInstance(Class infce, InvocationHandler handler) throws Exception {
            String rt = "
    ";
            String methodStr = "";
    
            Method[] methods = infce.getMethods();
            for (Method m : methods) {
                methodStr +=
                        "    public void " + m.getName() + "() {" + rt +
                                "        try {" + rt +
                                "            Method md = " + infce.getName() + ".class.getMethod("" + m.getName() + "");" + rt +
                                "            h.invoke(this, md);" + rt +
                                "        } catch (Exception e) {" + rt +
                                "            e.printStackTrace();" + rt +
                                "        }" + rt +
                                "    }" + rt;
            }
    
            String src = "package com.sj.proxy;" + rt +
                    "import java.lang.reflect.Method;" + rt +
                    "public class TankTimeProxy implements " + infce.getName() + " {" + rt +
                    "    com.sj.proxy.InvocationHandler h;" + rt +
                    "    public TankTimeProxy(InvocationHandler h) {" + rt +
                    "        this.h = h;" + rt +
                    "    }" + rt +
                    methodStr +
                    "}";
    
            String fileUrl = System.getProperty("user.dir") + "/src/main/java/com/sj/proxy";
            String fileName = fileUrl + "/TankTimeProxy.java";
            File file = new File(fileName);
            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write(src);
            fileWriter.flush();
            fileWriter.close();
    
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager javaFileManager = compiler.getStandardFileManager(null, null, null);
            Iterable<? extends JavaFileObject> compilationUnits = javaFileManager.getJavaFileObjects(fileName);
            JavaCompiler.CompilationTask compilationTask = compiler.getTask(null, javaFileManager, null, null, null, compilationUnits);
            compilationTask.call();
            javaFileManager.close();
    
            URL[] urls = new URL[]{new URL("file:/" + fileUrl)};
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            Class clazz = urlClassLoader.loadClass("com.sj.proxy.TankTimeProxy");
    
            Constructor constructor = clazz.getConstructor(InvocationHandler.class);
            Object m = constructor.newInstance(handler);
    
            return m;
        }
    }

    TimeHandler.java:

    package com.sj.proxy;
    
    import java.lang.reflect.Method;
    
    public class TimeHandler implements InvocationHandler {
        private Object target;//被代理对象
    
        public TimeHandler(Object target) {
            this.target = target;
        }
    
        /**
         * @param object o这里没用到。
         *               Proxy中的h.invoke(this, md),其实this没用到,this的指向是代理对象。
         */
        public void invoke(Object object, Method method) {
            long start = System.currentTimeMillis();
            System.out.println("starttime: " + start);
            System.out.println(object.getClass().getName());//com.sj.proxy.TankTimeProxy
    
            try {
                method.invoke(target); //对被代理对象调用method方法
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            long end = System.currentTimeMillis();
            System.out.println("time: " + (end - start));
        }
    }

    Client.java测试代码:

    package com.sj.test;
    
    import com.sj.proxy.*;
    
    public class Client {
        public static void main(String[] args) throws Exception {
            Tank t = new Tank();//被代理对象
            InvocationHandler h = new TimeHandler(t);//代理逻辑,这里是记录时间的逻辑
    
            //Proxy.newProxyInstance(Moveable.class, h);//返回一个代理对象,我们不知道名字
            Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class, h);
            m.move();
        }
    }

    问题1、NoSuchMethodException

    在Proxy 的 Class clazz = urlClassLoader.loadClass("com.sj.proxy.TankTimeProxy"); 打个断点,debug一次就可以了。

    打印结果如下:

    starttime: 1515136915379
    com.sj.proxy.TankTimeProxy
    Tank Moving...
    time: 7152

    看下生成的代理类——TankTimeProxy.java

    package com.sj.proxy;
    import java.lang.reflect.Method;
    public class TankTimeProxy implements com.sj.proxy.Moveable {
        com.sj.proxy.InvocationHandler h;
        public TankTimeProxy(InvocationHandler h) {
            this.h = h;
        }
        public void move() {
            try {
                Method md = com.sj.proxy.Moveable.class.getMethod("move");
                h.invoke(this, md);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    5、JDK中的动态代理,和上面模拟的代码几乎一样:

    Proxy:

    返回一个指定接口的代理类的实例。该代理类实例将方法调用指派给指定的调用处理程序。

    ClassLoader:当你产生这个动态代理的时候,用哪个classLoader把它load进来;上面模拟是用的URLClassLoader;真正调的时候,可以用当前这个类的ClassLoader;如果用传过来的ClassLoader,得保证生成代理的类生成在特定的目录里面,才load进来。

    java.lang.reflect.InvocationHandler:

    在代理实例上处理方法调用,并返回结果。

    proxy、method和上面代码模拟的一样。args是method的参数。

    6、使用JDK里面的Proxy、InvocationHandler写的动态代理的例子

    需求:在userDao的save、delete方法的前后加上日志记录

    UserDao.java

    package com.sj.aop;
    
    public interface UserDao {
        int save(String user);
        boolean delete(String user);
    }

    UserDaoImpl.java

    package com.sj.aop;
    
    public class UserDaoImpl implements UserDao {
        public int save(String user) {
            System.out.println("add User: " + user);
            return 1;
        }
    
        public boolean delete(String user) {
            System.out.println("delete User: " + user);
            return true;
        }
    }

    UserDaoLogHandler.java,在方法的前后加上记录日志的逻辑

    package com.sj.aop;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class UserDaoLogHandler implements InvocationHandler {
    
        //target是被代理对象
        private Object target;
    
        public UserDaoLogHandler(Object target) {
            this.target = target;
        }
    
        /**
         * 调用被代理对象实现接口UserDao的每个方法(ave,delete)都会调用invoke方法;
         * 1.先加自己的业务逻辑
         * 2.再调用被代理对象的对应方法;
         * 3.再加自己的业务逻辑也行。
         */
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println(method.getName() + "----log start...");
    
            /**
             * obj      是target调用method的返回值。也就是public int save(String)方法的返回值int
             * method   是要调用的被代理对象的方法
             * args     是method方法的参数
             */
            Object obj = method.invoke(target, args);
            System.out.println(method.getName() + "----log end.");
            return obj;//执行target.save,obj是1;执行target.delete,obj是true。
        }
    }

    UserService.java 测试代码:

    package com.sj.aop;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    public class UserService {
        /**
         * 1.构建被代理对象 userDao,
         * 2.把被代理对象交给handler,
         * 3.使用newProxyInstance产生代理对象,其中的参数:
         * 1)calssLoader:必须和被代理对象使用同一个calssLoader,
         * 2)被代理对象使用的接口,jdk生成的代理对象会使用同样的接口,
         * 3)代理对象调用方法的时候是由handler来处理
         */
        public static void main(String[] args) throws Exception {
            UserDao userDaoImpl = new UserDaoImpl();
            InvocationHandler invocationHandler = new UserDaoLogHandler(userDaoImpl);
            UserDao proxy = (UserDao) Proxy.newProxyInstance(userDaoImpl.getClass().getClassLoader(),
                    userDaoImpl.getClass().getInterfaces(), invocationHandler);
    
            proxy.save("小明");
            proxy.delete("小明");
        }
        /**
         * JDK中要给一个类实现动态代理:这个类必须实现一个接口,没有实现接口的类,JDK是产生不了动态代理的。
         */
    }

    打印结果如下:

    save----log start...
    add User: 小明
    save----log end.
    delete----log start...
    delete User: 小明
    delete----log end.

    被代理对象实现接口的每个方法,前后都加上了逻辑。

    源码:https://gitee.com/SevenDayBabyface/Proxy.git

    参考 马士兵设计模式-动态代理

  • 相关阅读:
    Thinkphp 获取当前url
    Navicat Premium 11 For Mac 注册机
    android 客户端支付宝 php服务器端编写
    tp框架集成支付宝,中转页变成gbk编码
    sql,插入最大值加1
    获取php的配置
    百度授权回调问题
    模拟新浪微博textarea,刷新页面输入信息保留
    同一客户端使用多份SSH Key
    SSH 自动化安装部署遇到的问题
  • 原文地址:https://www.cnblogs.com/xsj891107/p/8119053.html
Copyright © 2011-2022 走看看