现在的位置: 首页 > 综合 > 正文

学习笔记09—代理和AOP

2013年02月14日 ⁄ 综合 ⁄ 共 11735字 ⁄ 字号 评论关闭

代理类是个很有意思的东西。

让人不得不感叹这种对现实世界的抽象思维方法。说到底,编程语言本质上是在对现实世界进行抽象。

顾名思义,或者物如其名,代理人或代理商在为供货商做代理的同时,总要附加一些自己的服务,

下面所提到的target就是为谁做代理的那个谁,而附加的服务就是下面提到的Advice.

Spring的精髓所在就在于此。其一是工厂模式,其二是代理和AOP,代理是AOP的核心。

代理类和APO

 

代理类

 

 

生活中的代理就是常说的代理商,

从厂商将商品卖给消费者,消费者不用很麻烦的到厂商在购买了。

 

要为已经存在的多个具有相同接口的目标类的各个方法增加一些系统功能,

如异常处理、日志、计算方法的运行时间、事物管理等等。

 

代理类的优点:

 

如果采用工厂模式和配置文件的方式进行管理,

则不需要修改客户端程序,在配置文件中配置是使用目标类还是代理类。

这样以后很容易切换,如果想要日志功能时,就配置代理类,否则配置目标类,

这样,增加系统功能很容易,以后运行一段时间后,更换系统也容易

 

AOP

 

AOP(Aspect OrientedProgram)即面向方面的编程。

 

系统中存在着交叉业务,

一个交叉业务就是要切入到系统中的一个方面

 

              安全   事务    日志

StudentService ------|----------|------------|-------------

CourseService-------|----------|------------|-------------

MiscService----------|----------|------------|-------------

 

 

安全、事务、日志等功能要贯穿于好多个模块中,所以他们就是交叉业务。

 

交叉业务的编程问题即面向方面的编程(AOP),

AOP的目标就是使交叉业务模块化,

可以采用将切面代理移动到原始方法的周围,

这与直接在方法中编写切面代理的过程效果是一样的

 

如图:

 

 ------------------------------------------------------切面

func1         func2            func3

{             {                {

....            ....              ......

}             }                }

------------------------------------------------------切面

 

因此使用代理技术正好可以解决这种问题,

代理是实现AOP功能的核心和关键技术

 

 

动态代理

要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,

这时就不能采用静态代理方式,需用动态代理技术。

 

动态代理类:

JVM可在运行时,动态生成类的字节码,

这种动态生成的类往往被用作代理类,即动态代理类。

不是代理,只是拿出来作为代理类

 

注意:

JVM生成的动态类必须实现一或多个接口,

所以JVM生成的动态代理类只能用作具有相同接口的目标类代理。

 

CGLIB库可以动态生成一个类的子类,

一个类的子类也可以作为该类的代理,

所以,如果要为一个没有实现接口的类生成动态代理,那么可以使用CGLIB库。

 

代理类各个方法通常除了调用目标相应方法和对外返回目标返回的结果外,

还可以再代理方法中的如下位置上加上系统功能代码:

 

在调用目标方法之前

在调用目标方法之后

在调用目标方法前后

在处理目标方法异常的catch块中。

 

以上对应后面Advice接口四个方法。

 

创建动态类并查看其方法列表信息:

package cn.itcast.day3;
 
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
 
public classProxyTest {
 
   /**
     * @param args
     */
   @SuppressWarnings("rawtypes")
   publicstaticvoidmain(String[] args) {
        // 生成一个实现Collection接口的代理类,classLoader参数一般要用第二个参数取得
        Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
        System.out.println("name : " +clazzProxy.getName());
        // 获得这个代理类的构造方法
        System.out.println("------constructors list ----------");
        Constructor[] constructors =clazzProxy.getConstructors();
        /*
         * 对获得的构造方法进行迭代,按照加了参数类型的特定格式输出构造方法
         * 如果不用特定方式区分的话,重载的多个构造方法名字都是一样的
         * Constructor[] constructors =String.class.getConstructors();
         *    for (Constructor con : constructors) {
         *         System.out.println(con.getName());
         *    }
         * 上面的代码会输出一串的java.lang.String
         */
        for (Constructor constructor : constructors) {
            // 取得构造方法名字
            String name =constructor.getName();
            // 用StringBuilder,单线程下效率高
            StringBuilder sBuilder = new StringBuilder(name);
            // 名字后面加小括号
            sBuilder.append('(');
            // 小括号里再添加参数类型名,先取得一数组
            Class[] params =constructor.getParameterTypes();
           
            for (Class param :params) {
                // 如果参数有多个要用逗号分隔,但最后一个逗号不需要
                sBuilder.append(param.getName()+ ',');
            }
            // 删除最后一个逗号,前提是数组不为空且有参数
            if (params.length != 0 && params != null) {
               sBuilder.deleteCharAt(sBuilder.length() - 1);
            }
           
            sBuilder.append(')');
            // 最后输出完整的构造方法名字和参数类型
            System.out.println(sBuilder.toString());
        }
 
 
 
        // 用同样的手段取得代理类的各个方法
        System.out.println("------methods list------");
        Method[] methods =clazzProxy.getMethods();
        /*
         * 对获得的方法进行迭代,按照加了参数类型的特定格式输出方法
         */
        for (Method method : methods) {
            // 取得方法名字
            String name = method.getName();
            // 用StringBuilder,单线程下效率高
            StringBuilder sBuilder = new StringBuilder(name);
            // 名字后面加小括号
           sBuilder.append('(');
            // 小括号里再添加参数类型名,先取得一数组
            Class[] params =method.getParameterTypes();
           
            for (Class param :params) {
                // 如果参数有多个要用逗号分隔,但最后一个逗号不需要
                sBuilder.append(param.getName()+ ',');
            }
            // 删除最后一个逗号,前提是数组不为空且有参数
            if (params.length != 0 && params != null) {
               sBuilder.deleteCharAt(sBuilder.length() - 1);
            }
           
            sBuilder.append(')');
           // 最后输出完整的方法名字和参数类型
            System.out.println(sBuilder.toString());
        }
   }
 
}

输出:

name : $Proxy0

------start constructors list----------

$Proxy0(java.lang.reflect.InvocationHandler)

------end constructors list----------

------start methodslist---------------

add(java.lang.Object)

remove(java.lang.Object)

equals(java.lang.Object)

toString()

hashCode()

clear()

iterator()

containsAll(java.util.Collection)

removeAll(java.util.Collection)

retainAll(java.util.Collection)

contains(java.lang.Object)

isEmpty()

size()

toArray([Ljava.lang.Object;)

toArray()

addAll(java.util.Collection)

getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)

newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)

getInvocationHandler(java.lang.Object)

isProxyClass(java.lang.Class)

wait()

wait(long,int)

wait(long)

getClass()

notify()

notifyAll()

------end methods list---------------

 

由动态类生成动态对象

在上面的主方法继续写:

 

        // 下面生成代理类的对象
        // 用clazzProxy.newInstance();是绝对不可以的。这个代理类没有无参构造方法
        Constructor constructor = clazzProxy
                .getConstructor(InvocationHandler.class);
        // 要用到InvocationHandler的实现类,所以要先实现一个
        class MyInvocationHandler1 implementsInvocationHandler {
 
            @Override
            public Object invoke(Object proxy, Methodmethod, Object[] args)
                    throws Throwable {
                // TODO Auto-generated method stub
                return null;
            }
 
        }
        Collection proxy1 = (Collection)constructor
                .newInstance(new MyInvocationHandler1());
        // null.这是因为proxy1调用toString方法的时候派发给了new 出的MyInvocationHandler对象
        System.out.println(proxy1);
        proxy1.clear();
        // System.out.println(proxy1.size()); //空指针异常,因为调用size会派发给new
 
        // 也可以直接创建InvocationHandler的匿名子类作为参数
       Collection proxy2 =(Collection) constructor
                .newInstance(new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Methodmethod,
                            Object[] args) throws Throwable {
                        // TODO Auto-generated method stub
                        return null;
                    }
                });
 
        /*
         * 还可以一步到位创建对象,用Proxy.newProxyInstance方法
         */
        Collection proxy3 = (Collection) Proxy.newProxyInstance(
        // 第一个参数
                Collection.class.getClassLoader(),
                // 第二个参数,不可以用可变参数,因为可变参数须位于最后
                new Class[] { Collection.class },
                // 第三个参数InvocationHandler型对象
                new InvocationHandler() {
                    // 目标类
                    ArrayList target = new ArrayList();
 
                    @Override
                    public Object invoke(Object proxy, Methodmethod,
                            Object[] args) throws Throwable {
                        // 记录所用时间
                        long startTime = System.currentTimeMillis();
                        Object retVal =method.invoke(target,args);
                        long endTime = System.currentTimeMillis();
                        System.out.println(method.getName()+ " using time : "
                                + (endTime -startTime));
                        return retVal;
                    }
                });
        // 每调用一次add就会执行InvocationHandler里面的invoke方法
        // proxy3、add、“aa”分别对应InvocationHandlerde invoke方法的三个参数
        // 对proxy3的操作都派发给了InvacationHandler对象执行
        proxy3.add("aa");
        proxy3.add("bb");
        proxy3.add("cc");
        System.out.println(proxy3.size());
        // 只有hashCode方法、equals方法和toString方法会派发,getClass有自己的实现
       System.out.println(proxy3.getClass().getName());

 

分析:构造方法只有一个,接收一个InvocationHandler参数。

构造方法接收参数总是要与其成员变量相关联。

代理类内部一定有一个成员InvocationHandler,通过构造方法设置。

{
   InvocationHandler handler;
   public $Proxy0(InvocationHandler handler){
        this.handler = handler;
    }
}

在动态类的方法中,实现接口的各个方法,如size方法,其内部是调用成员handler来执行invoke方法

int size(){
    returnhandler.invoke(this, this.getClass().getMethod("size"),null);
}

同理,在add方法中也是handler来执行invoke方法

invoke方法的三个参数分别对应proxy对象,add方法,方法参数。

总结:动态代理工作原理

先生成代理类,为谁做代理(目标类)则由外部传入,系统功能对象也由外部传入。

上面的代码中目标类是硬编码写入的ArrayList target= newArrayList();

系统功能是测试时间的语句

long startTime = System.currentTimeMillis();

Object retVal = method.invoke(target, args);

long endTime = System.currentTimeMillis();

 

代理商为哪一家厂商做代理不是固定的。

 

画圈的部分需要分离出来,传入一个对象,由对象执行相应的方法。

这就是AOP思想

除了传递目标对象之外,还要传递系统功能的对象

把其他的做成框架

 

 

下面是修改后的代码:

 

系统功能接口Advice:

package cn.itcast.day3;
 
import java.lang.reflect.Method;
 
public interfaceAdvice {
   voidbeforeMethod(Method method);
   voidafterMethod(Method method);
}

 

系统功能实现类MyAdvice:

 

package cn.itcast.day3;
 
import java.lang.reflect.Method;
 
public classMyAdvice implementsAdvice {
 
   // 因为在下面两个方法中都用到了startTime所以把start设置为成员变量
   longstartTime;
 
   @Override
   publicvoidbeforeMethod(Method method) {
        System.out.println("------before method------");
        startTime = System.currentTimeMillis();
   }
 
   @Override
   publicvoidafterMethod(Method method) {
        long endTime = System.currentTimeMillis();
        System.out.println(method.getName() + " using time : "
                + (endTime - startTime));
        System.out.println("------after method------");
   }
}

 

ProxyTest中的代码:

抽取成的方法

private staticObject getProxy(final Object target, final Advice advice) {
   
   Object proxy3 = Proxy.newProxyInstance(
            // 第一个参数
            target.getClass().getClassLoader(),
            // 第二个参数,数组形式
            target.getClass().getInterfaces(),
            // 第三个参数
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Methodmethod,
                        Object[] args) throws Throwable {
                    // 记录所用时间
                    /*long startTime = System.currentTimeMillis();
                    Object retVal = method.invoke(target,args);
                    long endTime =System.currentTimeMillis();
                   System.out.println(method.getName() + " using time : "
                            + (endTime -startTime));
                    return retVal;*/
                   
                   advice.beforeMethod(method);
                   
                    Object retVal =method.invoke(target, args);
                   
                    advice.afterMethod(method);
                   
                    return retVal;
                }
            });
 
   return proxy3;
}

调用方法:

        // 由外部传入目标类对象
        final ArrayList target = new ArrayList();
       
        Collection proxy3 = (Collection)getProxy(target,newMyAdvice());
       
        // 每调用一次add就会执行InvocationHandler里面的invoke方法
        // proxy3、add、“aa”分别对应InvocationHandlerde invoke方法的三个参数
        // 对proxy3的操作都派发给了InvacationHandler对象执行
       proxy3.add("aa");
        proxy3.add("bb");
        proxy3.add("cc");
       System.out.println("proxy3's size is : "+ proxy3.size());

 

输出结果:

------before method------

add using time : 0

------after method------

------before method------

add using time : 0

------after method------

------before method------

add using time : 0

------after method------

------before method------

size using time : 0

------after method------

proxy3's size is : 3

 

实现一个类似Spring的可配置的AOP框架

BeanFactory 用于根据类的名字返回其实例

 

package cn.itcast.day3.aopframework;
 
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
 
import cn.itcast.day3.Advice;
 
public classBeanFactory {
 
   Properties props= newProperties();
 
   // 构造方法的参数总是要与成员属性相关联
   publicBeanFactory(InputStream ips) {
        try {
            props.load(ips);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
   }
   /*
     * 对外提供一个方法,根据一个类的名字返回该类的实例
     */
   publicObject getBean(String name) {
        // 类的名字将由props根据键值对取得
        String className = props.getProperty(name);
        // 为返回值赋初始值
        Object bean = null;
        try {
            // 取得字节码
            Class clazz = Class.forName(className);
            // 生成对象
            bean = clazz.newInstance();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        /*
         * 判断对象类型,如果是ProxyFactoryBean则返回生成的代理类
         */
        if (bean instanceof ProxyFactoryBean) {
            Object proxy = null;
            try {
                ProxyFactoryBeanproxyFactoryBean = (ProxyFactoryBean) bean;
                // 通知
                Advice advice = (Advice) Class.forName(
                        props.getProperty(name + ".advice")).newInstance();
                // 目标类
                Object target = Class.forName(
                        props.getProperty(name + ".target")).newInstance();
                // 设置通知
               proxyFactoryBean.setAdvice(advice);
                // 设置目标类
                proxyFactoryBean.setTarget(target);
                // 生成代理类
                proxy =proxyFactoryBean.getProxy();
            } catch (Exception e) {
                e.printStackTrace();
            }
            // 返回代理类
            return proxy;
        }
        //默认直接返回类的实例
        return bean;
   }
}

 

能够生成代理类的类ProxyFactoryBean

 

package cn.itcast.day3.aopframework;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
import cn.itcast.day3.Advice;
 
public classProxyFactoryBean {
   /*
     * ProxyFactoryBean具有JavaBean的特征
     * 为它添加私有成员和相应的setter和getter
     */
   // 代理提供的功能
   privateAdvice advice;
   // 为谁代理
   privateObject target;
   
   publicAdvice getAdvice() {
        return advice;
   }
 
   publicvoidsetAdvice(Advice advice) {
        this.advice = advice;
   }
 
   publicObject getTarget() {
        return target;
   }
 
   publicvoidsetTarget(Object target) {
        this.target = target;
   }
   /*
     * 通过Proxy.newProxyInstance生成动态代理类
     */
   publicObject getProxy() {
        Object proxy3 = Proxy.newProxyInstance(
                // 第一个参数
                target.getClass().getClassLoader(),
                // 第二个参数,数组形式
                target.getClass().getInterfaces(),
                // 第三个参数
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Methodmethod,
                            Object[] args) throws Throwable {
 
                        advice.beforeMethod(method);
                       
                        Object retVal =method.invoke(target,args);
                       
                        advice.afterMethod(method);
                       
                        return retVal;
                    }
                });
 
        return proxy3;
   }
 
}

 

用到的Advice类

package cn.itcast.day3;
 
import java.lang.reflect.Method;
 
public classMyAdvice implementsAdvice {
 
   // 因为在下面两个方法中都用到了startTime所以把start设置为成员变量
   longstartTime;
 
   @Override
   publicvoidbeforeMethod(Method method) {
        System.out.println("------before method------");
        startTime = System.currentTimeMillis();
   }
 
   @Override
   publicvoidafterMethod(Method method) {
        long endTime = System.currentTimeMillis();
        System.out.println(method.getName() + " using time : "
                + (endTime - startTime));
        System.out.println("------after method------");
   }
}

 

测试类:

package cn.itcast.day3.aopframework;
 
import java.io.InputStream;
 
public classAopFrameworkTest {
   publicstaticvoidmain(String[] args) {
        InputStream ips = AopFrameworkTest.class.getResourceAsStream("resource/config.properties");
        Object bean = newBeanFactory(ips).getBean("xxx");
        System.out.println(bean.getClass().getName());
   }
}

 

用到的properties文件,放到测试类所在包的resource目录下:

xxx=java.util.ArrayList

#xxx=cn.itcast.day3.aopframework.ProxyFactoryBean

xxx.advice=cn.itcast.day3.MyAdvice

xxx.target=java.util.ArrayList

抱歉!评论已关闭.