首页 > 代码库 > java --- 设计模式 --- 动态代理

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

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 =                 "http://www.mamicode.com/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文件

打开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:1407701374031Tank 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:1407702024556Transaction start1.插入记录到user表2.记录日志到日志表Transaction committime:0

 

 

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

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===============提交事务释放资源

 

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

 

 

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

于切入点匹配的类才会由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