Java代理模式及动态代理
一.Java代理模式
1. 定义
一个角色代表另一个角色来完成某些特定的功能,这个跟我们现实生活中商业代理一样,比如:生产商,中间商,客户这三者这间的关系客户买产品并不直接与生产商打交道,也不用知道产品是如何产生的,客户只与中间商打交道,而中间商就可以对产品进行一些包装,提供一些售后的服务。
2. 代理模式的作用
A. 为其他对象提供一种代理以控制对这个对象的访问
B. 在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理类对象可以在客户端和目标对象之间起到中介的作用。
C. 在程序中,腰围已经存在的多个具有相同接口的目标类的对象各个方法增加一些系统功能,例如:异常处理、日志、计算方法的运行时间、事物管理、等等。这时候就可以采用代理的方法来完成。
3. 代理模式一般涉及到的角色
A. 抽象角色:声明真实对象和代理对象的共同接口(抽象类和接口)
B. 代理角色:代理对象角色内部含有对真实(目标)对象的引用,从而可以操作真实(目标)对象,同时代理对象还提供与真实(目标)对象相同接
口,以便在热河时刻都能代替真实(目标)对象,同时,代理对象还可以在执行真实对象操作时候,还可以附件其他的操作,相当于对真实(目标)对象进行封装。
C. 真实(目标)角色:代理角色需要代理的目标对象,是我们最终用引用的对象。
D. 如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类还是代理类。这样以后很容易切换,如果想要日志功能时,就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想换掉系统功能也很容易。
二. AOP应用
1. 简述:AOP(AspectOriented Program)即面向方面的编程。
2. 示意图:
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
安全 事务 日志
StudentService ------|----------|-----------|-------------
CourseService ------|-----------|----------|-------------
MiscService -------|----------|------------|-------------
安全、事务、日志等功能要贯穿于好多个模块中,所以他们就是交叉业务。
3. 用具体的程序代码描述交叉业务
A. 交叉业务的代码实现
method1 method2 method3
{ { {
------------------------------------------------------切面
.... .... ......
------------------------------------------------------切面
} } }
B. 交叉业务的编程问题即为面向方面的编程(Aspect orientedprogram ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
------------------------------------------------------切面
func1 func2 func3
{ { {
.... .... ......
} } }
------------------------------------------------------切面
因此使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术,只要是用到面向方面的编程,就涉及到代理。
简单的代理实例代码:
public interface SubjectInterface { public abstract void add(int a , int b); } //目标角色 public class Target implements SubjectInterface { @Override public void add(int a, int b) { System.out.println( a+b); } } //代理角色 public class MyProxy implements SubjectInterface { SubjectInterface subject; public MyProxy(SubjectInterface subject) { this.subject =subject; } @Override public void add(int a, int b) { //附加的前置功能 System.out.println("正在计算"+a+"+"+b+"的值,请稍候..."); long startTime = System.currentTimeMillis(); //目标业务 System.out.println(a+b); //附加的后置功能 long endTime = System.currentTimeMillis(); System.out.println("为你计算结果共耗时:"+(endTime-startTime)); } } //客户端 public class Client { public static void main(String[] args) { Target target = new Target(); MyProxy proxy = new MyProxy(target); proxy.add(3,6); } }
执行结果:
正在计算3+6的值,请稍候...
9
为你计算结果共耗时:0
总结:
从上面的代码可以看得出客户实际需要调用的是Target类的add()方法,现在用MyProxy类对象来代理,同样能达到目的,同时还添加了其他人性化的功能。另外,如果要按照上述的方法使用代理模式,那么真实(目标)角色必须实现已经存在,并将其作为代理对象内部的属性,这就是代理模式中的静态代理。但是实际使用时候,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果实现并不知道真实角色是哪一个,该如何使用代理呢?这个角色可以通过动态代理类来解决。
三. 动态代理类
从上面的静态代理类中可以得知,要为每一个目标累代理业务,就必须实现存在并且知道要为哪一个目标对象去完成代理,每一个代理对象就需要对应一个目标对象,这样一来代理类和代理对象在不断增加,有要事先有目标类,事先知道要为哪个目标类做代理,程序缺乏扩展性,这时候就需要使用动态代理。
1. 简述:
所谓Dynamic Proxy是这样一种class,它是在程序运行时候生成的class,在生成它时你必须提供一组Interface给它,然后该class就宣称它实现了这些Interface,所以可以把该class的实例对象当作这些Interface中的任何一个来用,所以JVM生成的动态代理类只能用作具有实现这些接口的目标类做代理,这个Dynamic Proxy其实就是一个Proxy,它不会替你做任何实质行的工作,在生成它的实例时候,你必须提供一个handler,由它接管实际的工作(完成目标业务或目标功能方法)。
2. CGLIB库概念
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理。所以如果要为一个没有实现接口的类生成动态代理类,就那么可以使用CGLIB库。
3. 增加附加系统功能
代理类各个方法通常除了调用目标相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下位置上加上系统功能代码:
A. 在调用目标方法之前
B. 在调用目标方法之后
C. 在调用目标方法前后
D. 在处理目标方法异常的catch块中。
4. 动态代理涉及到的类介绍
Java动态代理类位于java.lang.reflect包下,一般主要涉及到如下几个类:
A. Interface InvocationHandler:该接口种仅定义了一个方法,public Object invoke(Object obj, Method method,Object[] args)
在实际使用中,第一个参数obj一般是指代理类,method是被代理的方法,args是被代理方法的参数列表。这个抽象方法在代理类中动态实现。
B. Proxy :该类即为动态代理类,动态产生的代理类继承这个类,同时也实现了传入接口的类,所以动态产生的代理类也是接口的实现类,该类定一个了属性h,即持有了一个InvocationHandler对象的引用,从而有一个Proxy.getInvocationHandler()的方法。Static Class getProxyClass (ClassLoader loader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组,Protected Proxy(InvocationHandler h):构造函数,估计用于给内部的h赋值。
5. 动态代理的创建步骤
A. 创建一个实现接口的InvocationHandler的类,它必须实现invoke方法。
B. 创建被代理的类以及接口。
C. 通过Proxy的静态方法创建一个代理对象。
分解创建方式(这里只写关键步骤,其他类和代码省略)
Subject sb = new RealSubject();//new一个目标对象实例对象 //创建一个InvocationHandler实例对象 InvocationHandler handler = new MyInvocationHandler(sb); //传入handler和sb,获得动态代理类的class字节码对象 Class proxyClass = Proxy.getProxyClass(Subject.class.getClassLoader(),Subject.class); //使用获得到代理类class字节码对象获取Proxy的构造方法对象。 //参数传递的必须是InvocationHandler.class,不能是InvocationHandler的子类的//class对象 Constructor con = proxyClass.getConstructor(InvocationHandler.class); //使用构造方法创建动态代理对象 Subject subProxy =(Subject) con.newInstance(handler);
整合步骤(这里只写关键步骤,其他类和代码省略):
Subject subject = new RealSubject(); InvocationHandler handler = new MyInvocationHandler(subject); Subject proxy =(Subject)Proxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(), handler);
练习:
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 MyDynamicProxyTest { public static void main(String[] args)throws Exception { Class classzz = Proxy.getProxyClass(MyDynamicProxyTest.class.getClassLoader(),Collection.class); Constructor[] constructors = classzz.getConstructors(); System.out.println("=======================List Constructor ========================="); for(Constructor constructor : constructors ) { StringBuilder sb = new StringBuilder(); sb.append( constructor.getName()); sb.append("("); Class[] params = constructor.getParameterTypes(); for(Class param : params) { sb.append(param.getName()+","); } if(params != null && params.length != 0 ) sb.deleteCharAt(sb.length()-1); sb.append(")"); System.out.println(sb.toString()); } System.out.println("==================List Methods ================"); Method[] methods = classzz.getMethods(); for(Method method : methods) { StringBuilder sb = new StringBuilder(); sb.append(method.getName()); sb.append("("); Class[] params = method.getParameterTypes(); for(Class param : params) { sb.append(param.getName()+","); } if(params.length != 0 && params != null) sb.deleteCharAt(sb.length()-1); sb.append(")"); System.out.println(sb.toString()); } System.out.println("==============begin create instance============ "); //方式一:采用获取Proxy类构造方法且以内部类的方式创建InvocationHandler的 //子类的方法创建代理对象 Constructor constructor = classzz.getConstructor(InvocationHandler.class); class MyInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null;//直接让它返回null } } Collection proxy1 =(Collection) constructor.newInstance(new MyInvocationHandler()); //不会报错,因为这个方法返回void,而invoke方法也返回null。 proxy1.clear(); //报告空指针异常,因为这个方法需要返回int类型的值,而invoke返回null。 //proxy1.size(); //方式二:采用匿名内部类的方法传递InvocationHandler对象 Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }); //方式三: 使用Proxy的静态方法newProxyInstance()一步到位new出Proxy对象 Collection proxy3 = (Collection)Proxy.newProxyInstance ( MyDynamicProxyTest.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() { ArrayList target = new ArrayList(); public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object obj = null; long sTime = System.currentTimeMillis(); obj = method.invoke(target,args); long eTime = System.currentTimeMillis(); System.out.println("方法执行总共话费时间:" + (eTime - sTime)); return obj; }; } ); proxy3.add("Ansen"); proxy3.add("Jack"); System.out.println(proxy3.size()); } }
注意:动态类的实例对象的getClass()方法返回的还是Object实例的原方法,只有调用代理对象从Object类继承的hashCode,equals,或toString这几个方法时,代理对象会将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求。所以对于getClass()方法,还是Object实例的原方法。即proxy3.getClass(),该是什么结果还是什么结果,并不会交给invoke方法处理。
6.分析动态代理类的原理和结构
怎样将目标传进去:
A.直接在InvocationHandler实现类中创建目标类的实例对象,可看运行效果和加入日志代码,但是没有实际意义。
B.为InvocationHandler实现类注入目标的实例对象,不能采用匿名内部类的形式了。
C.让匿名内部类的InvocationHandler实现类访问外面的方法中的目标类实例对象的final类型的引用变量。
动态代理的工作原理:
A.
Client(客户端)调用代理,代理的构造方法接收一个InvocationHandler,client调用代理的各个方法,代理的各个方法请求转发给刚才通过构造方法传入的handler对象,又把各请求分发给目标的相应的方法。
7. 附加功能模块化切面插入到目标方法前后
A. 定义个一个Advice约定接口,创建两个抽象方法,如doBefore()
或doAfter()。在创建这个接口的实现类,去实现这两个方法,并在InvocationHandler实现类内部创建这个实现类的对象,所以当invoke()方法调用目标目标方法的时候,可以在调用方法前后插入这两个方法。
import java.lang.reflect.Method; public interface Advice { public abstract void beforDo(Method m); public abstract void afterDo(Method m); } import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class MyAdvice implements Advice { long startTime = 0; @Override public void beforDo(Method m) { System.out.println(m.getName()+"方法开始执行!"); startTime = System.currentTimeMillis(); } @Override public void afterDo(Method m) { long endTime =System.currentTimeMillis(); System.out.println(); System.out.println(m.getName()+"方法执行结束! 执行用时为:"+(endTime-startTime)); } } import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class MyHandler implements InvocationHandler { Object target ;//定义一个目标对象引用 Advice advice ;//定义一个Advice对象应用 public MyHandler(Object target , Advice advice ) { this.target = target; this.advice = advice; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在执行目标方法前执行附加系统功能。 advice.beforDo(method); Object retval = method.invoke(target , args); //在执行目标方法后执行附加系统功能。 advice.afterDo(method); return retval; } //在InvocationHandler实现类内部创建获得proxy对象的方法。 public Object getDynamicProxy() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this ); } } import java.lang.reflect.InvocationHandler; import java.util.ArrayList; import java.util.List; public class MyDynamicTest { public static void main(String[] args) { List<String> list = new ArrayList<String>(); Advice advice = new MyAdvice(); MyHandler handler = new MyHandler(list, advice); List<String> listProxy = ( List)handler.getDynamicProxy(); listProxy.add("Ansen"); listProxy.add("Kaka1 SB"); listProxy.add("Rudy SB"); System.out.println( listProxy.size()); } }
四. 实现AOP功能的封装与配置
1. 工厂类BeanFactory
BeanFactory类负责创建目标类或者代理类的实例对象,并通过配置文件实现切换。
2. getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则返回该类示例对象的getProxy方法返回的对象。
3. BeanFactory的构造方法接收代表配置文件的输入流对象的配置文件格式如下:
xxx=java.util.ArrayList
#xxx=com.AOPFrame.ProxyFactoryBean
xxx.Advice=com.AOPFrame.MyAdvice
xxx.Target=java.util.ArrayList
4. ProxyFactoryBean充当封装生成动态代理的工厂,需为工厂提供的配置参数信息包括:
目标(target)
通知(advice)
5. BeanFactory和ProxyFactoryBean
A. BeanFactory是一个纯粹的bean工程,就是创建bean即相应的对象的工厂。
B. ProxyfactoryBean是BeanFactory中的一个特殊的Bean,是创建代理的工厂。
6. 实现类似spring的可配置的AOP框架的思路
A. 创建BeanFactory类
构造方法接受一个字节输入流获取配置文件信息,通过Properties对象加载InputStream流对象获得。创建getBean(String name)方法,接收Bean的名字,从上面加载后的对象获得。通过其字节码对象创建实例对象bean。
B. 判断bean是否是特殊的Bean即ProxyFactoryBean,如果是,就要创建代理类,并设置目标和通告,分别得到各自的实例对象,并返回代理类实例对象。如果不是在返回普通类的实例对象。
创建ProxyFactoryBean(接口),此处直接定义为类做测试,其中有一个getProxy方法,用于获得代理类对象。编写实现Advice接口的类和在配置文件中进行配置。
C. 创建配置文件
xxx=java.util.ArrayList
#xxx=com.AOPFrame.ProxyFactoryBean
xxx.Advice=com.AOPFrame.MyAdvice
xxx.Target=java.util.ArrayList
D. 定义一个测试类
AOPFrameTest,也称客户端,调用BeanFactory获取对象。
import java.lang.reflect.Method; public interface Advice { public void doBefore(); public void doAfter(Method method); } import java.lang.reflect.Method; public class MyAdvice implements Advice { long sTime = 0; @Override public void doBefore() { System.out.println("黑马程序员训练营开学啦!"); sTime = System.currentTimeMillis(); } public void doAfter(Method method) { long eTime = System.currentTimeMillis(); System.out.println(method.getName()+"运行花费时间:"+(eTime-sTime)); System.out.println("我从黑马程序员训练营毕业啦,找到不错的工作!"); } } import java.io.IOException; import java.io.InputStream; import java.util.Properties; //创建BeanFactory类,用于创建目标类或者代理类的实例对象。 public class BeanFactory { private Properties properties = new Properties(); public BeanFactory(InputStream in) { try { properties.load(in); } catch (IOException e) { e.printStackTrace(); } } public Object getBean(String name) { String className = properties.getProperty(name); Object objBean = null; try { Class classBean = Class.forName(className); objBean = classBean.newInstance(); } catch (Exception e) { e.printStackTrace(); } //如果创建的对象是ProxyFactoryBean类型,则通过getProxy方法获取代理类对象 if(objBean instanceof ProxyFactoryBean ) { Object proxy = null; ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)objBean; try { //从配置文件中获取代理类额外添加的代码封装成的对象 Advice advice = (Advice) Class.forName(properties.getProperty(name+ ".Advice")).newInstance(); //从配置文件中获取目标 Object target = Class.forName(properties.getProperty(name + ".Target")).newInstance(); proxyFactoryBean.setAdvice(advice); proxyFactoryBean.setTarget(target); //调用getProxy方法,获取代理对象 proxy = proxyFactoryBean.getProxy(); } catch (Exception e) { e.printStackTrace(); } return proxy; } return objBean; } } import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; //创建ProxyFactoryBean类,用于产生代理类实例对象 public class ProxyFactoryBean { private Advice advice =null; private Object target = null; public void setAdvice(Advice advice) { this.advice = advice; } public void setTarget(Object target) { this.target = target; } public Advice getAdvice() { return this.advice; } public Object getTarget() { return this.target; } public Object getProxy() { Object proxy = Proxy.newProxyInstance (ProxyFactoryBean.class.getClassLoader(), this.target.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy,Method method, Object[] args) throws Throwable { Object retVal = null; //使用约定的对象中的方法 advice.doBefore(); retVal = method.invoke(target,args); advice.doAfter(method); return retVal; }; } ); return proxy; } }
import java.io.InputStream; import java.util.ArrayList; public class AOPFrameTest { public static void main(String[] args) throws Exception { InputStream in = AOPFrameTest.class.getClassLoader().getResourceAsStream("MyConfig.properties"); ArrayList bean = (ArrayList)new BeanFactory(in).getBean("xxx"); System.out.println(bean.getClass().getName()); bean.add(3); System.out.println(bean.get(0)); } }