Java高新技术之代理

来源:互联网 发布:猎头书籍推荐 知乎 编辑:程序博客网 时间:2024/05/16 23:55

概念

        代理模式即Proxy Pattern,23种java常用设计模式之一。代理模式的定义:对其他对象提供一种代理以控制对这个对象的访问。比如要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等可以用代理来实现

        代理的实现思路:编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。如下图
Aop(面向切面编程)
安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务,系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
                              安全       事务         日志
StudentService  ------|----------|------------|-------------
CourseService   ------|----------|------------|-------------
MiscService       ------|----------|------------|-------------
用具体的程序代码描述交叉业务:
method1         method2          method3
{                      {                       { 
------------------------------------------------------切面
....            ....              ......
------------------------------------------------------切面
}                       }                       }
交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
------------------------------------------------------切面
func1         func2            func3
{             {                { 
....            ....              ......
}             }                }
------------------------------------------------------切面
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。

动态代理技术

要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中

分析JVM动态生成的类
1、创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。
//通过Proxy.getProxyClass方法传入接口的类加载器和接口,返回该接口的代理类的 Class对象,。Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);//得到代理类的名称System.out.println(clazzProxy1.getName());
2、编码列出动态类中的所有构造方法和参数类型
// 返回代理类所有构造方法Constructor[] constructors = clazzProxy1.getConstructors();//以一定格式遍历构造方法for (Constructor constructor : constructors) {String name = constructor.getName();//获取方法中所有参数类型Class[] clazzParams = constructor.getParameterTypes();StringBuilder sb = new StringBuilder(name);sb.append('(');//遍历所有参数类型for (Class clazzParam : clazzParams) {sb.append(clazzParam.getName()).append(',');}if (clazzParams != null && clazzParams.length != 0) {sb.delete(sb.length() - 1, sb.length());}sb.append(')');System.out.println(sb.toString());}//构造方法打印结果是com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)//说明代理类只有一个有参的构造方法
3、编码列出动态类中的所有方法和参数类型
Method[] methods = clazzProxy1.getMethods();for (Method method : methods) {String name = method.getName();//获取方法中所有参数类型Class[] clazzParams = method.getParameterTypes();StringBuilder sb = new StringBuilder(name);sb.append('(');//遍历所有参数类型for (Class clazzParam : clazzParams) {sb.append(clazzParam.getName()).append(',');}if (clazzParams != null && clazzParams.length != 0) {sb.delete(sb.length() - 1, sb.length());}sb.append(')');System.out.println(sb.toString());}//发现遍历的结果是Constructor接口中的方法
4、创建动态类的实例对象
方法一、先的到构造方法,通过构造方法实例化对象
// 通过反射获取指定参数类型的构造方法,传入参数类型InvocationHandler.classConstructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);class MyInvocationHandler implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return null;}}//实例化参数的时候传入的是参数类型的实例对象,InvocationHandler是接口,所以要写一个类实现它Collection proxy1 = (Collection) constructor.newInstance(new MyInvocationHandler());System.out.println(proxy1);//返回nullSystem.out.println(proxy1.toString());// 也返回null说明toString方法返回null//上面两步可以使用匿名内部类实例化Collection proxy2 = (Collection) constructor.newInstance(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return null;}});
方法二、直接通过代理的newProxyInstance方法实例化
//通过Proxy.newProxyInstance方法直接实例化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 {return method.invoke(target, args);}});proxy3.add("zhangsan1");proxy3.add("zhangsan2");proxy3.add("zhangsan3");System.out.println("size="+proxy3.size());
总结思考:让jvm创建动态类及其实例对象,需要给它提供哪些信息?
三个方面:
生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
产生的类字节码必须有个一个关联的类加载器对象;
生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。

分析动态生成的类的内部代码
首先动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。代码如下
$Proxy0 implements Collection{InvocationHandler handler;public $Proxy0(InvocationHandler handler){this.handler = handler;}}
构造方法接受一个InvocationHandler对象,接受对象了之后就是要把生成的代理类中的所有方法通过该对象的invoke方法来实现,代码如下
$Proxy0 implements Collection{InvocationHandler handler;public $Proxy0(InvocationHandler handler){this.handler = handler;}//生成的Collection接口中的方法的运行原理int size(){return handler.invoke(this,this.getClass().getMethod("size"),null);}void clear(){handler.invoke(this,this.getClass().getMethod("clear"),null);}boolean add(Object obj){handler.invoke(this,this.getClass().getMethod("add"),obj);}}
让动态生成的类成为目标类的代理

动态代理的工作原理图

怎样将目标类传进去?
直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但没有实际意义。
为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了。
让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量。

示例代码
private static Object getProxy(final Object target, final Advice advice) {Object proxy3 = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//之前硬编码/*long beginTime=System.currentTimeMillis();Object retVal = method.invoke(target, args);long enfTime=System.currentTimeMillis();System.out.println(method.getName()+"执行时间"+(enfTime-beginTime));*///将消息功能抽取出来advice.beforMethod(method);Object retVal = method.invoke(target, args);advice.afterMethod(method);return retVal;}});return proxy3;}
final ArrayList list = new ArrayList();Collection proxy4 = (Collection) getProxy(list, new MyAdvice());proxy4.add("zhangsan1");proxy4.add("zhangsan2");proxy4.add("zhangsan3");System.out.println("size=" + proxy3.size());
接口Advice
public interface Advice {public void beforMethod(Method method);public void afterMethod(Method method);}
接口实现类MyAdvice
public class MyAdvice implements Advice {private long begTime;@Overridepublic void beforMethod(Method method) {System.out.println("方法开始执行");begTime = System.currentTimeMillis();}@Overridepublic void afterMethod(Method method) {long endTime = System.currentTimeMillis();System.out.println(method.getName() + "执行时间:" + (endTime - begTime));System.out.println("方法执行结束");}}

实现AOP功能的封装与配置

工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回的对象。
BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:
#xxx=java.util.ArrayList
xxx=cn.itcast.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.itcast.MyAdvice

代码编写步骤

BeanFactory工厂类,如果是对象代理就返回代理,不是代理,直接返回该对象
public class BeanFactory {Properties props = new Properties();public BeanFactory(InputStream ips) {try {props.load(ips);} catch (IOException e) {e.printStackTrace();}}@SuppressWarnings("rawtypes")public Object getBean(String name) {String className = props.getProperty(name);Object bean = null;try {Class clazz = Class.forName(className);bean = clazz.newInstance();} catch (Exception e) {e.printStackTrace();}//如果bean是代理对象,就返回代理if (bean instanceof ProxyFactoryBean) {ProxyFactoryBean proxyFactoryBean=(ProxyFactoryBean)bean;Advice advice=null;Object target=null;try {advice = (Advice)Class.forName(props.getProperty(name+".advice")).newInstance();target = Class.forName(props.getProperty(name+".advice")).newInstance();} catch (Exception e) {e.printStackTrace();}proxyFactoryBean.setAdvice(advice);proxyFactoryBean.setTarget(target);Object proxy = proxyFactoryBean.getProxy();return proxy;}//如果bean不是代理类直接返回beanreturn bean;}}
代理工厂类ProxyFactoryBean
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 proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {advice.beforMethod(method);Object retVal = method.invoke(target, args);advice.afterMethod(method);return retVal;}});return proxy;}}
测试类 AopFrameworkTest
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());}}
配置文件config.properties
xxx=java.util.ArrayListxxx=darkhorse.enhance.day02.proxy.aopframework.ProxyFactoryBeanxxx.advice=darkhorse.enhance.day02.proxy.MyAdvicexxx.target=java.util.ArrayList
































原创粉丝点击