动态代理

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

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

原文地址:https://www.cnblogs.com/xsj891107/p/8119053.html