动态代理

来源:互联网 发布:怎么关闭手机数据 编辑:程序博客网 时间:2024/06/06 06:47

一、代理

为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如:异常处理、日志、计算方法的运行时间,事务管理等等。

采用工厂模式和配置文件的方法进行管理,则不需要修改客户端程序,在配置文件中配置是使用的目标类,还是代理类,这样以后容易切换。

代理架构图



AOP

简述:AOPAspect Oriented Program)即面向方面的编程。

系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:

                              安全       事务         日志
StudentService  ------|----------|------------|-------------
CourseService  ------|----------|------------|-------------
MiscService       ------|----------|------------|-------------


用具体的程序代码描述交叉业务:
method1         method2          method3
{                      {                       { 
------------------------------------------------------切面
....            ....              ......
------------------------------------------------------切面
}                       }                       }


交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
------------------------------------------------------切面
func1         func2            func3
{             {                { 
....            ....              ......
}             }                }
------------------------------------------------------切面


使用代理技术正好可以解决这种问题,代理是实现 AOP 功能的核心和关键技术。



二、动态代理技术

一、概述:

1、要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,这时就不能采用静态代理方式,需用动态代理技术。

2、动态代理类:JVM可在运行时,动态生成类的字节码,这种动态(不是代理,只是拿出来作为代理类)生成的类往往被用作代理类,即动态代理类。

注:JVM生成的动态类必须实现一或多个接口,所以JVM生成的动态代理类只能用作具有相同接口的目标类代理。

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

4、代理类各个方法通常除了调用目标相应方法和对外返回目标返回的结果外,还可以再代理方法中的如下位置上加上系统功能代码:

在调用目标方法之前;

在调用目标方法之后;

在调用目标方法前后;

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


二、分析JVM动态生成的类

1、创建动态类的实例对象:

用反射获得构造方法

编写一个最简单的InvocationHandler的类

调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去。

代码实例:

import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.ArrayList;import java.util.Collection;public class ProxyInstance{    public static void main(String[] args)throws Exception{    Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);System.out.println(clazzProxy.getName());        Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class);class MyInvocationHandler implements InvocationHandler{@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {// TODO Auto-generated method stubreturn null;}}Collection proxy = (Collection)constructor.newInstance(new MyInvocationHandler());System.out.println(proxy);proxy.clear();// proxy.size();接受代理对象中的返回值,因为为nullCollection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {// TODO Auto-generated method stubreturn null;}});Collection proxy3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() {ArrayList target = new ArrayList();@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {// TODO Auto-generated method stublong beginTime = System.currentTimeMillis();Object retVal = method.invoke(target, args);long endTime = System.currentTimeMillis();System.out.println(method.getName() + "running time of" + (endTime - beginTime));return retVal;}});proxy3.add("zxx");proxy3.add("lmh");proxy3.add("zxc");System.out.println(proxy3.size());}}

1、怎样将目标传进去:

直接在InvocationHandler实现类中创建目标类的实例对象,可看运行效果和加入日志代码,但是毫无意义。

InvocationHandler实现类注入目标的实例对象,不能采用匿名内部类的形式了。

让匿名内的InvocationHandler实现类访问外面的方法中的目标类实例对象的final类型的引用变量。

2、动态代理的工作原理:

Client(客户端)调用代理,代理的构造方法接受一个InvocationHandlerclient调用代理的各个方法,代理的各个方法请求转发给刚才通过构造方法传入的handler对象,又把各请求分发给目标的相应的方法。就是将handler封装起来,其中this引用了当前的方法(发来什么请求就接受哪个方法)

将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接受目标,同时返回代理对象,让调用者更方便,调用者甚至不用接触任何代理的API

3、把系统功能代理模块化,即切面代码也改为通过参数形式提供,怎么把要执行的系统功能代码以参数的形式提供:

把要执行的代码装到一个对象的某个方法中,然后把此对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外部提供的代码。

为方法增加一个Advice参数。

import java.lang.reflect.Method;public interface Advice {void beginMethod(Method method);void afterMethod(Method method);}

import java.lang.reflect.Method;public class MyAdvice implements Advice{long beginTime;@Overridepublic void beginMethod(Method method) {// TODO Auto-generated method stubSystem.err.println("you!");beginTime = System.currentTimeMillis();}@Overridepublic void afterMethod(Method method) {// TODO Auto-generated method stubSystem.out.println("come on");long endTime = System.currentTimeMillis();System.err.println(method.getName() + "run time of" + (endTime - beginTime));}}

public class ProxyTest {public static void main(String[] args) {final ArrayList target = new ArrayList();// 目标,要被方法里的内部类访问成员变量,要加finalCollection proxy3 = (Collection) getProxy(target, new MyAdvice());proxy3.add("zxx");proxy3.add("flx");proxy3.add("lalala");System.out.println(proxy3.size());}private static Object getProxy(final Object target, final Advice advice) {Object proxy3 = Proxy.newProxyInstance(target.getClass().getClassLoader(),// new Class[] { Collection.class },target.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method,Object[] args) throws Throwable {/* * long startTime = System.currentTimeMillis(); Object * retVal = method.invoke(target, args); long endTime = * System.currentTimeMillis(); * System.out.println(method.getName() + * " running time of " + (endTime - startTime)); */advice.beginMethod(method);Object retVal = method.invoke(target, args);advice.afterMethod(method);return retVal;}});return proxy3;}}

代理类的优点:

如果采用工厂模式和配置文件的方式进行管理,则不要修改客户端程序,在配置文件中配置时候是目标类还是代理类。这样会容易切换,如果想要日志功能时,配置代理类,否则配置目标类,这样增加系统功能很容易,以后运行一段时候后,想换掉系统功能也容易。

动态代理的工作原理图

1,获取代理的构造方法和方法

代码示例:

public class ProxyTest {public static void main(String[] args) throws Exception {// TODO Auto-generated method stub// 创建动态代理和查看方法列表Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);System.out.println(clazzProxy.getName());System.out.println("------begin constructors list------");/* * $Proxy0() $Proxy0(InvocationHandler,int); 获取代理类的构造方法 */Constructor[] constructors = clazzProxy.getConstructors();for (Constructor constructor : constructors) {String name = constructor.getName();StringBuilder sBuilder = new StringBuilder(name);sBuilder.append("(");Class[] clazzParames = constructor.getParameterTypes();for (Class clazzParame : clazzParames) {sBuilder.append(clazzParame.getName()).append(",");}if (sBuilder != null && sBuilder.length() != 0)sBuilder.deleteCharAt(sBuilder.length() - 1);sBuilder.append(")");System.out.println(sBuilder.toString());}System.out.println("------end constructors list------");Method[] methods = clazzProxy.getMethods();for(Method method : methods){String name = method.getName();StringBuilder sBuilder = new StringBuilder(name);sBuilder.append("(");Class[] clazzParames = method.getParameterTypes();for(Class clazzParame : clazzParames){sBuilder.append(clazzParame.getName()).append(",");}if(sBuilder !=null && sBuilder.length() != 0)sBuilder.append(sBuilder.length() -1 );sBuilder.append(")");System.out.println(sBuilder.toString());}}}


实现类似 spring 的可配置的 AOP 框架

需求:要求在不修改 Client 代码的情况下,可以修改我们创建的对象的类型,并且配置是否创建目标功能的代理类对象,那么我们可以选用   配置文件 + 工厂模式  的方法。我们的需求在配置文件中配置好以后,使用工厂类对象读取配置文件获得我们要创建的对象的类的名字的字符串,使用该字符串来在工厂中使用反射来创建我们我们需要的实例。
现在我们的配置选项可以设置为我们要创建的对象要么是一般目标类对象,要么是目标代理类对象,创建目标代理类对象的话,还需要指定 目标类 和 Advice 接口的实现类。

1,定义一个 BeanFactory 类实现 bean 工厂,负责创建目标类或代理类的实例对象,并通过配置文件实现切换,getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则返回该类的实例对象,否则返回该类实例对象的getProxy方法返回的对象。
public class BeanFactory {Properties propes = new Properties();public BeanFactory(InputStream ips){try {propes.load(ips);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}public Object getBean(String name){String className = propes.getProperty(name);Object bean = null;Class clazz;try {clazz = Class.forName(className);bean = clazz.newInstance();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}if(bean instanceof ProxyFactoryBean){Object proxy = null;ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;try{Advice advice = (Advice)Class.forName(propes.getProperty(name + ".advice")).newInstance();Object target = Class.forName(propes.getProperty(name + ".target")).newInstance();proxyFactoryBean.setAdvice(advice);proxyFactoryBean.setTarget(target);proxy = proxyFactoryBean.getProxy();}catch(Exception e){e.printStackTrace();}return proxy;}return bean;}}


2,BeanFactory的构造方法接受配置文件的对象,配置一个config.properties文件,用类切换是目标类还是代理类

#xxx=java.util.ArrayListxxx=cn.itcast.Test.ProxyFactoryBeanxxx.advice=cn.itcast.Test.MyAdvicexxx.target=java.util.ArrayList


3,定义一个 ProxyFactoryBean 类它是一个产生代理类对象的工厂,它本身就是 javaBean ,而且实现了 BeanFactory 接口。

public class ProxyFactoryBean {private Advice advice;private Object target;public Advice getAdvice() {return advice;}public void setAdvice(Advice advice) {this.advice = advice;}public Object getTarget() {return target;}public void setTarget(Object target) {this.target = target;}public Object getProxy() {Object proxy3 = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler(){@Overridepublic Object invoke(Object proxy, Method method,Object[] args) throws Throwable {advice.beforeAdvice(method);Object retVal = method.invoke(target,args);advice.afterAdvice(method);return retVal;}});return proxy3;}}


定义一个测试类

public class AopFrameWorkTest {public static void main(String[] args){InputStream ips = AopFrameWorkTest.class.getResourceAsStream("config.properties");Object bean = new BeanFactory(ips).getBean("xxx");System.out.println(bean.getClass().getName());((Collection)bean).clear();}}



4,BeanFactory和ProxyFactoryBean:

 BeanFactory是一个纯粹的bean工程,就是创建bean相应对象的工厂。

 ProxyFactoryBean是BeanFactory中的一个特殊的bean,是创建代理的工厂。


1 0
原创粉丝点击