java --- 设计模式 --- 动态代理

时间:2022-02-18 16:14:42

Java设计模式——动态代理

java提供了动态代理的对象,本文主要探究它的实现,

动态代理是AOP(面向切面编程, Aspect Oriented Programming)的基础实现方式,

动态代理使代码的重复更少,更便与维护

本文参考了满一行老师和马士兵老师的视频,在此表示Thanks。

假设小张通过QQ和一个网名为如花的妹纸聊天,

而在QQ的另一端,这个网名为如花的妹纸的name是小丽

但是,正在聊天的时候小丽生病了不想打字,小丽就找她的男朋友帮忙回应

小丽的男朋友在如花的QQ账号上与小张聊天,小张并不知道和他聊天的如花是谁

小张发来消息了,"HI, 你好,",小丽的男朋友说,“小张发来消息了‘HI, 你好’,我该怎么回?”

小丽说,那就回一个“你也好, 哈哈”吧, 小张的男朋友按照小丽说的做了。

这就是代理模式的雏形,小丽实现了如花这个接口,小张也实现了如花这个接口

小张调用的是如花这个接口,但是他并不知道,和他聊天的是小丽,还是小丽的男朋友

小丽的男朋友因为在信息的必经道上,所以可以对信息进行扭曲以及篡改,对不良的信息过滤

哈哈,这就是面向切面编程的一个例子了。

故事就讲到这里,下面来解释一下静态代理

Tank类实现一个Movable接口,怎样知道它的运行时间呢方法move()的运行时间呢

我们可以在方法前面和后面都记录时间, long start = System.currentTimeMillis();

/Proxy/src/yuki/design/proxy/package1/Movable.java

package yuki.design.proxy.package1;

public interface Movable {

    void move();

}

/Proxy/src/yuki/design/proxy/package1/Tank.java

package yuki.design.proxy.package1;

import java.util.Random;

public class Tank implements Movable {

    @Override
    public void move() {
//      long start = System.currentTimeMillis();

        System.out.println("Tank is moving...");
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

//      long end = System.currentTimeMillis();
//      System.out.println("time:" + (start-end));
    }

}

通过注释掉的内容可以计算出夹在中间代码的运行时间

这种方法需要修改源码,但是很多方法都是不知道源码的class文件

这个方式拿到的也不是方法本身运行的时间,因为方法已经被篡改了

还有一种方法是要在main方法中把方法执行的前后加上时间的记录

这是不行的,因为jdk为方法的运行准备也需要时间,这显然是不精确的

哦,我们可以继承Tank这个类,重写它的方法就可以了

用继承把原来的方法前后加一些逻辑,原来的方法就用super调用

/Proxy/src/yuki/design/proxy/package1/Tank2.java

package yuki.design.proxy.package1;

public class Tank2 extends Tank {

    @Override
    public void move() {
        long start = System.currentTimeMillis();

        super.move();

        long end = System.currentTimeMillis();
        System.out.println("time:" + (start-end));
    }

}

一个类里面有另一个类的对象,这种对象间的关系叫做聚合,

它也可以实现上面的继承所提到的效果,只是把父类改成代理类的一个成员变量而已

Tank3是用聚合的方式实现的

/Proxy/src/yuki/design/proxy/package1/Tank3.java

package yuki.design.proxy.package1;

public class Tank3 implements Movable {

    public Tank3(Tank t) {
        this.t = t;
    }

    Tank t;

    @Override
    public void move() {
        long start = System.currentTimeMillis();

        t.move();

        long end = System.currentTimeMillis();
        System.out.println("time:" + (start-end));
    }

}

继承明显是不灵活的,也不符合日常的逻辑

一般来说,如果可能这个类有多个代理类呢,比如说,添加一个记录日志的类

功能上的叠加,先记录日志,后记录时间;或者是相反的顺序

如果用继承的方式,会导致类越来越多

如果用聚合的方式,可以代理Movable接口,这里的关键是实现同一接口

/Proxy/src/yuki/design/proxy/package1/TankTimeProxy.java

package yuki.design.proxy.package1;

public class TankTimeProxy implements Movable {

    public TankTimeProxy(/*Tank*/Movable t) {
        this.t = t;
    }

    /*Tank*/Movable t;

    @Override
    public void move() {
        long start = System.currentTimeMillis();

        t.move();

        long end = System.currentTimeMillis();
        System.out.println("time:" + (end-start));
    }

}

/Proxy/src/yuki/design/proxy/package1/TankLogProxy.java

package yuki.design.proxy.package1;

public class TankLogProxy implements Movable{

    public TankLogProxy(/*Tank*/ Movable t) {
        this.t = t;
    }

    /*Tank*/Movable t;

    @Override
    public void move() {
        System.out.println("Tank start......");

        t.move();

        System.out.println("Tank stop......");
    }

}

注意,这里代理类的对象并不是真实的对象,而是接口,通过这个接口

这两个代理类和被代理的对象就可以*组合

严格的说,在被代理对象和最终的代理对象之间可以随机插入代理类

每一个代理类就可以看作是一个切片

/Proxy/src/yuki/design/proxy/package1/Client.java

package yuki.design.proxy.package1;

public class Client {

    public static void main(String[] args) {
        Tank t = new Tank();

        TankTimeProxy timeProxy = new TankTimeProxy(t);
        TankLogProxy logProxy = new TankLogProxy(timeProxy);
        Movable m = logProxy;
        m.move();
        /*
        Movable logProxy = new TankLogProxy(t);
        Movable timeLogProxy = new TankTimeProxy(logProxy);
        timeLogProxy.move();
        */
    }

}

spring这个轻量级容器,提供了继承和聚合实现代理的方式,

但是,强烈建议我们使用聚合的哪一种

可以说,AOP是动态代理的一个应用

还要说明的是,当一个接口有多个方法时,相同的代码必须重复

就是封装成对象也至少要插入一条插入调用方法的语句

/Proxy/src/yuki/design/proxy/package2/Movable.java

package yuki.design.proxy.package2;

public interface Movable {

    void move();
    void stop();

}

/Proxy/src/yuki/design/proxy/package2/Tank.java

package yuki.design.proxy.package2;

import java.util.Random;

public class Tank implements Movable {

    @Override
    public void move() {
        System.out.println("Tank is moving...");
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void stop() {
        System.out.println("Tank is stopping...");
    }

}

/Proxy/src/yuki/design/proxy/package2/TankTimeProxy.java

package yuki.design.proxy.package2;

public class TankTimeProxy implements Movable {

    public TankTimeProxy(Movable t) {
        this.t = t;
    }

    Movable t;

    @Override
    public void move() {
        long start = System.currentTimeMillis();

        t.move();

        long end = System.currentTimeMillis();
        System.out.println("time:" + (end-start));
    }

    @Override
    public void stop() {
        long start = System.currentTimeMillis();

        t.stop();

        long end = System.currentTimeMillis();
        System.out.println("time:" + (end-start));
    }

}

如果Movable接口里还有其它的方法,计算时间的代码在代理类中还得重写
可以把相同的代码放在方法中,before,after
怎样让计算时间的代理变得更加通用,
不只是计算坦克的时间,还可以计算汽车的
怎样写一个通用的时间代理

我们应该怎样生成一个代理呢

动态代理的意思是,不再看到代理类的名字,需要的是代理对象直接产生

加入能把源码编译完产生新的类,再把这个类放入内存,产生新的对象
怎样对这段代码动态的编译,动态编译完成就能产生动态代理了
现在看不到这个动态代理类的名字
这里可以自定义proxy的实现,而具体的类就不用写了

/Proxy/src/yuki/design/proxy/package3/Movable.java

/Proxy/src/yuki/design/proxy/package3/Tank.java

package yuki.design.proxy.package3;

import java.util.Random;

public class Tank implements Movable {

    @Override
    public void move() {
//        long start = System.currentTimeMillis();

        System.out.println("Tank is moving...");
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

//        long end = System.currentTimeMillis();
//        System.out.println("time:" + (start-end));
    }

}
package yuki.design.proxy.package3;

import java.util.Random;

public class Tank implements Movable {

    @Override
    public void move() {
//        long start = System.currentTimeMillis();

        System.out.println("Tank is moving...");
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

//        long end = System.currentTimeMillis();
//        System.out.println("time:" + (start-end));
    }

}

在JDK6里,提供了编译java代码的API
获取项目的根路径System.getProperty("user.dir");
通过FileWriter的write方法把字符串写到文件中

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler是空值,把/Java/Installed JREs由jre改为jdk
这里的jre还是从jdk下的jre下拿的
/JavaBuildPath/Libraries选择Workspace default JRE(jdk1.8.0)

在Eclipse中,必须先编译好,有了文件后才能加载类
使用反射加载对象,运行方法

/Proxy/src/yuki/design/proxy/Proxy.java

package yuki.design.proxy;

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;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class Proxy {

    public static Object newProxyInstance(Class<?> interfaces, InvocationHandler h) throws Exception {

        String rt = "\r\n";
        String methodStr = "";

        Method[] methods = interfaces.getMethods();
        /*for(Method m : methods){
            methodStr +=
                    "    @Override" + rt +
                    "    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+
                    "    }";
        }*/
        for(Method m : methods){
            methodStr +=
                    "    @Override" + rt +
                    "    public void "+ m.getName() +"() {" + rt +
                    "        try{" + rt +
                    "            Method md = "+ interfaces.getName() +".class.getMethod(\""+ m.getName() +"\");" + rt +
                    "            h.invoke(this, md);"+rt+
                    "        }catch(Exception e){ e.printStackTrace(); }" + rt +
                    "    }";
        }

        /*String src =
                "package yuki.design.proxy.package3;"+rt+
                "public class TankTimeProxy implements "+ interfaces.getName() +" {"+rt+
                "    public TankTimeProxy("+ interfaces.getName() +" t) {"+rt+
                "        this.t = t;"+rt+
                "    }"+rt+
                "    "+ interfaces.getName() +" t;"+rt+
                    methodStr +rt+
                "}";*/
        String src =
                "package yuki.design.proxy.package3;"+rt+
                "import yuki.design.proxy.InvocationHandler;"+rt+
                "import java.lang.reflect.Method;"+rt+
                "public class TankTimeProxy implements "+ interfaces.getName() +" {"+rt+
                "    public TankTimeProxy(InvocationHandler h) {"+rt+
                "        this.h = h;"+rt+
                "    }"+rt+
                "    InvocationHandler h;"+rt+
                methodStr +rt+
                "}";

        String fileName = System.getProperty("user.dir") +
                                        "/temp/yuki/design/proxy/package3/TankTimeProxy.java";
        File f = new File(fileName);
        if(!f.exists()){ f.mkdirs(); f.delete();}
        FileWriter fw = new FileWriter(f);
        fw.write(src);
        fw.flush();
        fw.close();

        /*
         * compile
         */
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        // System.out.println(compiler); //com.sun.tools.javac.api.JavacTool@a570f
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable<? extends JavaFileObject> units =  fileManager.getJavaFileObjects(fileName);
        CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units);
        task.call();
        fileManager.close();

        /*
         * load into memory and create an instance
         */
        URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/temp/")};
        //System.out.println(urls[0]); //file:/D:/Workspaces/Eclipse/Proxy/bin
        URLClassLoader ul = new URLClassLoader(urls);
        Class<?> c = ul.loadClass("yuki.design.proxy.package3.TankTimeProxy");
        //System.out.println(c); //class yuki.design.proxy.package3.TankTimeProxy
        ul.close();

        Constructor<?> constructor = c.getConstructor(InvocationHandler.class);
        Object m = constructor.newInstance(h);

        return m;
    }

}

现在的代理只能实现Movable接口,现在要求可以实现任意接口的
传入Class<?>类型的参数就可以了
站在ClassLoader的角度Method也是一系列的对象
假设有很多方法就遍历所有的方法
往里传入任意接口,就可以产生实现了这个接口的任意对象

更换为其它的目录后,一次运行就可以成功
原先在src目录下,会有两份class文件,
一份是eclipse编译的,一份是ClassLoader加载的

/Proxy/src/yuki/design/proxy/InvocationHandler.java

package yuki.design.proxy;

import java.lang.reflect.Method;

public interface InvocationHandler {

    void invoke(Object o, Method m);

}

这里,实现了InvocationHandler的invoke(Obejct, Method)方法

/Proxy/src/yuki/design/proxy/TimeHandler.java

package yuki.design.proxy;

import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler {

    private Object target;

    public TimeHandler(Object target) {
        this.target = target;
    }

    @Override
    public void invoke(Object o, Method m) {
        long start = System.currentTimeMillis();
        System.out.println("starttime:" + start);
        try {
            m.invoke(target);
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("time:" + (end-start));
    }

}

我们来看一看它是如何被调用的吧

/Proxy/src/yuki/design/proxy/Client.java

package yuki.design.proxy;

import yuki.design.proxy.package3.Movable;
import yuki.design.proxy.package3.Tank;

public class Client {

    public static void main(String[] args) throws Exception {
        Tank t = new Tank();
        InvocationHandler h = new TimeHandler(t);
        Movable m = (Movable) Proxy.newProxyInstance(Movable.class, h);
//        Movable m = (Movable) Proxy.newProxyInstance(Comparable.class);
        m.move();

    }
}

看一看生成的代理java文件和class文件

java --- 设计模式 --- 动态代理

打开TankTimeProxy.java

package yuki.design.proxy.package3;
import yuki.design.proxy.InvocationHandler;
import java.lang.reflect.Method;
public class TankTimeProxy implements yuki.design.proxy.package3.Movable {
    public TankTimeProxy(InvocationHandler h) {
        this.h = h;
    }
    InvocationHandler h;
    @Override
    public void move() {
        try{
            Method md = yuki.design.proxy.package3.Movable.class.getMethod("move");
            h.invoke(this, md);
        }catch(Exception e){ e.printStackTrace(); }
    }
}

它被翻译成了class文件,加载进内存,生成对象

if(!f.exists()){ f.mkdirs(); f.delete();}

这一句的意思是,如果文件不存在就建立这些文件,但是在文件的树梢

f.mkdirs()之后,有一个TimeTankProxy.java的文件夹,

导致在该目录下不能新建TimeTankProxy.java的文件,所以要f.delete();

运行看一下结果吧,控制台打印输出如下语句

starttime:1407701374031
Tank is moving...
time:5934

补充一些小细节,

/Proxy/src/yuki/design/proxy/complier/Test1.java

package yuki.design.proxy.complier;

import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import yuki.design.proxy.package3.Movable;
import yuki.design.proxy.package3.Tank;

public class Test1 {

    public static void main(String[] args) throws Exception {
        String rt = "\r\n";
        String src =
                "package yuki.design.proxy.package3;"+rt+
                "public class TankTimeProxy implements Movable {"+rt+
                "    public TankTimeProxy(Movable t) {"+rt+
                "        this.t = t;"+rt+
                "    }"+rt+
                "    Movable t;"+rt+
                "    @Override"+rt+
                "    public void move() {"+rt+
                "        long start = System.currentTimeMillis();"+rt+
                "        System.out.println(\"starttime:\" + start);"+rt+
                "        t.move();"+rt+
                "        long end = System.currentTimeMillis();"+rt+
                "        System.out.println(\"time:\" + (end-start));"+rt+
                "    }"+rt+
                "}";

        String fileName = System.getProperty("user.dir") +
                                        "/src/yuki/design/proxy/package3/TankTimeProxy.java";
        File f = new File(fileName);
        FileWriter fw = new FileWriter(f);
        fw.write(src);
        fw.flush();
        fw.close();

        /*
         * compile
         */
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        // System.out.println(compiler); //com.sun.tools.javac.api.JavacTool@a570f
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable<? extends JavaFileObject> units =  fileManager.getJavaFileObjects(fileName);
        CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units);
        task.call();
        fileManager.close();

        /*
         * load into memory and create an instance
         */
        URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/src")};
        //System.out.println(urls[0]); //file:/D:/Workspaces/Eclipse/Proxy/bin
        URLClassLoader ul = new URLClassLoader(urls);
        Class<?> c = ul.loadClass("yuki.design.proxy.package3.TankTimeProxy");
        //System.out.println(c); //class yuki.design.proxy.package3.TankTimeProxy
        ul.close();

        Constructor<?> constructor = c.getConstructor(Movable.class);
        Movable m = (Movable) constructor.newInstance(new Tank());
        m.move();

    }

}

/Proxy/src/yuki/design/proxy/complier/Test2.java

package yuki.design.proxy.complier;

import java.lang.reflect.Method;

import yuki.design.proxy.package3.Movable;

public class Test2 {

    public static void main(String[] args) {
        Method[] methods = Movable.class.getMethods();
        for(Method m : methods){
            System.out.println(m.getName());
        }
    }

}

现在,我们一起来连贯一下代理的主要细节:

一个代理类TimeHandler实现一个接口InvocationHandler
接口的方法是invoke(Object, Method)
这个类需要传入一个被代理对象Tank,
并提供一个它的每个方法需要添加的代码,在invoke中调用Tank的方法
它会被Proxy的生成器吸收,遍历这个类的所有方法
最后会在一个临时的目录生成代码并编译,在生成的类中,
它吸收了InvocationHandler,
并通过invoke的方法参数调用被代理类的每个方法
生成Class文件,加载进内存,获得了这个代理类的对象
这时,调用这个代理类的每个方法都会执行在TimeHandler中被填入的内容

使用代理类实现不修改原来的代码,在代码前后添加逻辑
面向切面编程,这个逻辑是可插拔的,可以把逻辑卸载配置文件中
配置是可以叠加的,

既然动态代理的类已写好,我们让它来实现一些其它的代理逻辑

/Proxy/src/yuki/design/proxy/test/UserMgr.java

package yuki.design.proxy.test;

public interface UserMgr {

    void addUser();

}

/Proxy/src/yuki/design/proxy/test/UserMgrImpl.java

package yuki.design.proxy.test;

public class UserMgrImpl implements UserMgr {

    @Override
    public void addUser() {
        System.out.println("1.插入记录到user表");
        System.out.println("2.记录日志到日志表");
    }

}

/Proxy/src/yuki/design/proxy/test/TransactionHandler.java

package yuki.design.proxy.test;

import java.lang.reflect.Method;

import yuki.design.proxy.InvocationHandler;

public class TransactionHandler implements InvocationHandler {

    private Object target;
    public TransactionHandler(Object target) {
        this.target = target;
    }

    @Override
    public void invoke(Object o, Method m) {
        System.out.println("Transaction start");
        try {
            m.invoke(target);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Transaction commit");
    }

}

/Proxy/src/yuki/design/proxy/test/Client.java

package yuki.design.proxy.test;

import java.lang.reflect.Method;

import yuki.design.proxy.InvocationHandler;

public class TransactionHandler implements InvocationHandler {

    private Object target;
    public TransactionHandler(Object target) {
        this.target = target;
    }

    @Override
    public void invoke(Object o, Method m) {
        System.out.println("Transaction start");
        try {
            m.invoke(target);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Transaction commit");
    }

}

运行一下,就会看到结果,编译的文件以及控制台输出的结果

package yuki.design.proxy.package3;
import yuki.design.proxy.InvocationHandler;
import java.lang.reflect.Method;
public class TankTimeProxy implements yuki.design.proxy.test.UserMgr {
    public TankTimeProxy(InvocationHandler h) {
        this.h = h;
    }
    InvocationHandler h;
    @Override
    public void addUser() {
        try{
            Method md = yuki.design.proxy.test.UserMgr.class.getMethod("addUser");
            h.invoke(this, md);
        }catch(Exception e){ e.printStackTrace(); }
    }
}
starttime:
Transaction start
.插入记录到user表
.记录日志到日志表
Transaction commit
time:

在最后用到了两个代理类的嵌套,它也可以用配置文件变成灵活的,即配置是可叠加的

jdk的动态代理有Proxy类的
newProxyInstance(ClassLoader, Class<?>[], InvocationHandler)
接口InvocationHandler有方法
invoke(Object, Method, Object[])

简单的小思考:

什么叫动态代理?
动态代理是怎么产生的?
动态代理有什么用?

怎样使用JavaAPI生成动态代理呢

/Proxy/src/yuki/design/proxy/java/EmpService.java

package yuki.design.proxy.java;

public interface EmpService {

    void save();

    void update();

    void delete(int id);
}

/Proxy/src/yuki/design/proxy/java/EmpServiceImpl.java

package yuki.design.proxy.java;

public class EmpServiceImpl implements EmpService {

    public void save() {
        System.out.println("EmpService save");
    }

    @Override
    public void update() {
        System.out.println("EmpService update");
    }

    @Override
    public void delete(int id) {
        System.out.println("EmpService delete, id="+ id);
    }

}

/Proxy/src/yuki/design/proxy/java/EmpServiceProxy.java

package yuki.design.proxy.java;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class EmpServiceProxy {

    public static void main(String[] args) {
        //被代理的目标对象
        final EmpService target = new EmpServiceImpl();

        /**
         * 用来产生代理的类
         * 第一个参数:classLoader, 用来加载*.class文件,类加载器
         *                                 需要拿到当前线程的类加载器
         * 第二个参数:代理类应当实现的接口,可以实现多个接口
         * 第三个参数:一个接口
         */
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class<?>[] interfaces = new Class[]{EmpService.class};
        InvocationHandler h = new InvocationHandler() {
            /**
             * Object proxy 代理对象自己
             * Method method 代理对象正在被调用的那个方法,是在接口里定义的
             * Object[] args 方法的参数数组
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                System.out.println("方法名:" + method.getName());
                try {
                    System.out.println("开始事物");
                    // 调用真正业务逻辑
                    System.out.println("===============");
                    method.invoke(target, args);
                    System.out.println("===============");
                    System.out.println("提交事务");
                } catch (Exception e) {
                    System.out.println("回滚事务");
                } finally {
                    System.out.println("释放资源");
                }
                return null;
            }
        };
        EmpService service = (EmpService) Proxy.newProxyInstance(loader, interfaces, h);
//        service.save();
//        service.update();
        service.delete(111);
    }
}

运行,会在控制台打印出如下语句

方法名:delete
开始事物
===============
EmpService delete, id=111
===============
提交事务
释放资源

整个演示项目的包路径如下:

java --- 设计模式 --- 动态代理

对于面向切面变成来说,有切入点和切面两个术语

于切入点匹配的类才会由spring生成动态代理,在spring中,我们需要写切入点表达式

切面是对应的附加操作,可以把这部分操作写在通知类中

<bean class="通知类"></bean>

<aop:config>

  <aop:pointcut id="切入点id" expression="切入点表达式"></aop:pointcut>

  <aop:aspect ref="通知类id">

    <aop:通知类型 method="通知类中的方法" pointcut-ref="切入点id"></<aop:通知类型>

  </aop:aspect>

</aop:config>

Object 代理对象 = context.getBean("目标对象id");

将主业务逻辑的附加操作抽取出去,根据功能不同,抽取为不同通知类的思想,称为面向切面编程

更多好文请查看:http://www.cnblogs.com/kodoyang/

孔东阳

2014/8/11