程序中代理的概念与作用
要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等。
代理架构图:
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类,还是使用代理类,
这样以后很容易切换,例如:想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易。
1.分析代理类的作用与原理及AOP概念
代理是实现AOP功能的核心和关键技术。
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面。
安全,事务,日志等功能要贯穿到好多个模块中,所以,他们就是交叉业务
用具体的程序代码描述交叉业务:
交叉业务的编程问题即为面向方面的变成(Aspect oriented program,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围。这与直接在方法中编写切面代码的运行效果是一样的。因为不可能去修改用户的代码。如下所示:
---------------------------------------------------- 切面
func1 func2 func3
{ { {
} } }
---------------------------------------------------- 切面
2.动态代理技术
> JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
JVM动态生成的类不是代理,而是可以作为其他类的代理。
> JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理.
*如果目标类没有实现接口,要想生成代理使用 CGLIB
> CGLIB 库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的的类生成动态代理类,那么可以使用CGLIB库。
> 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
> 1.在调用目标方法之前
> 2.在调用目标方法之后
> 3.在调用目标方法前后
> 4.在处理目标方法异常的catch块中
void sayHello(){ .............. try{ target.sayHello(); }catch(Exception e){ ............... } ................. }
创建动态类及查看其方法列表信息
分析JVM动态生成的类
创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数
static <?> | getProxyClass( loader, <?>... interfaces) Returns the java.lang.Class object for a proxy class given a class loader and an array of interfaces. |
编码列出动态类中的所有构造方法和参数签名
编码列出动态类中的所有方法和签名
创建动态类的实例对象
> 用反射获得构造方法
> 编写一个最简单的InvocationHandler类
> 调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去
> 打印创建的对象和调用对象的没有返回值得方法和getClass方法,调用其他有返回值的方法会抛异常。
public class ProxyTest { public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { //这里指定的classLoader 为任意一个ClassLoader,通常用与接口相同的 Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); System.out.println("-------------" + clazzProxy1.getName()); //-------------com.sun.proxy.$Proxy0 System.out.println("-------------Begin constructor list-----------"); Constructor[] constructors = clazzProxy1.getConstructors(); //返回所有的构造方法 for(Constructor constructor : constructors){ String name = constructor.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append('('); Class[] clazzs = constructor.getParameterTypes(); //返回所有的参数列表类型 for(Class clazz: clazzs){ sBuilder.append(clazz.getName()); sBuilder.append(","); } if(clazzs != null && clazzs.length != 0){ sBuilder.deleteCharAt(sBuilder.length() - 1); //去掉最后一个 ',' } sBuilder.append(')'); System.out.println(sBuilder.toString()); } System.out.println("-------------Begin method list-----------"); Method[] methods = clazzProxy1.getMethods(); //返回所有的方法 for(Method method : methods){ String name = method.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append('('); Class[] clazzs = method.getParameterTypes(); //返回所有的参数列表类型 for(Class clazz: clazzs){ sBuilder.append(clazz.getName()); sBuilder.append(","); } if(clazzs != null && clazzs.length != 0){ sBuilder.deleteCharAt(sBuilder.length() - 1); //去掉最后一个 } sBuilder.append(')'); System.out.println(sBuilder.toString()); } System.out.println("--------------begin create instance----------------"); //以下为创建代理对象 Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class); class MyInvocationHandler implements InvocationHandler{ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } } Collection proxy1 = (Collection) constructor.newInstance(new MyInvocationHandler()); System.out.println(proxy1); //null 这里为null是因为toString()返回为null System.out.println(proxy1.toString());//null 这里为null 没有报错是因为调用了handler的toString(),handler的返回为null proxy1.clear(); //调用没有返回值的就不会报错,无返回值为void invoke返回的是null 正确// proxy1.size(); //如果collection为null, 调用有返回值的方法时会抛出异常 是由于调用handler的invoke方法,返回值为null, //以上为一种方式,还可稍微简化下 Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } }); }}
> InvocationHandler 对象的内部功能
System.out.println("--------------begin create instance----------------"); //以下为创建代理对象 Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class); class MyInvocationHandler implements InvocationHandler{ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } } Collection proxy1 = (Collection) constructor.newInstance(new MyInvocationHandler()); System.out.println(proxy1); //null 这里为null是因为toString()返回为null System.out.println(proxy1.toString());//null 这里为null 没有报错是因为调用了handler的toString(),handler的返回为null proxy1.clear(); //调用没有返回值的就不会报错,无返回值为void invoke返回的是null 正确// proxy1.size(); //如果collection为null, 调用有返回值的方法时会抛出异常 是由于调用handler的invoke方法,返回值为null, //以上为一种方式,还可稍微简化下 Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } }); //可以使用Proxy.newProxyInstance(ClassLoader loader, Class [] interfaces, InvocationHandler h) 更为简单 Collection proxy3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() { ArrayList target = new ArrayList(); //为代理类挂上目标类,代理类需为前边接口的实现类 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /** * 如果目标类出现在这,则proxy每次调用方法时都会调用invoke() 每次都会是一个全新的ArrayList对象 * 即后边的size() 总是0 */// ArrayList target = new ArrayList (); //为代理类挂上目标类,代理类需为前边接口的实现类 long startTime = System.currentTimeMillis(); Object obj = method.invoke(target, args); long endTime = System.currentTimeMillis(); System.out.println(method.getName() + " running time " + (endTime-startTime)); return obj; } }); proxy3.add("zxx"); proxy3.add("lhm"); proxy3.add("bxd"); System.out.println(proxy3.size()); /** * 在执行proxy3.getClass().getName() * 返回的是com.sun.proxy.$Proxy0,调用的是代理的不是目标的getClass() * 是由于 getClass() 是继承的Object的方法,其中Object中只有 hashCode,equals,toString这三个方法会委托给handler去调用 * 其余的Object中的其他方法 proxy自己有实现,不交给handler * java API: * If a proxy interface contains a method with the same name and parameter signature * as the hashCode, equals, or toString methods of java.lang.Object, when such a method is invoked on a proxy instance, * the Method object passed to the invocation handler will have java.lang.Object as its declaring class. * In other words, the public, non-final methods of java.lang.Object logically precede all of the proxy interfaces for the determination of which Method object to pass to the invocation handler. */ System.out.println(proxy3.getClass().getName());//com.sun.proxy.$Proxy0
总结分许动态代理类的设计原理与结构
> InvocationHandler 对象的运行原理
Client程序调用objProxy.add("abc")方法时,涉及三个要素:objProxy对象、add方法、"abc"参数
Class Proxy${
add(Object object){
return handler.invoke(Object proxy, Method method, Object[] args);
}
}
动态代理的工作原理图
这里的log() 相当于一个系统功能,这个不应该一直是一个功能,而应该配置成为动态变化的,即可动态设置的系统功能。
> 面向切面编程:
把切面的代码,以对象的形式进行封装,以对象的形式传递给你,你只要执行这个对象,就等于执行了切面的代码
> 编写可生成代理和插入通告的通用方法
Collection proxy3 = (Collection)getProxy(new ArrayList<>(),new MyAdvice()); //每调用一次,都会调用 一次InvocationHandler 的 invoke方法 proxy3.add("zxx"); proxy3.add("lhm"); proxy3.add("bxd"); System.out.println(proxy3.size());
getProxy方法
private static Object getProxy(final Object target, final MyAdvice advice) { Object proxy3 = Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.beforeMethod(method); Object object = method.invoke(target, args); advice.afterMethod(method); return object; } }); return proxy3; }
MyAdvice类:
public class MyAdvice implements Advice { long startTime = 0; @Override public void beforeMethod(Method method) { startTime = System.currentTimeMillis(); } @Override public void afterMethod(Method method) { long endTime = System.currentTimeMillis(); System.out.println(method.getName() + " running time of " + (endTime - startTime)); }}
目的是把 代理 和系统功能都转义成了外边的 对象
完成Spring 只完成一件事:写MyAdvice, target是在配置文件中配置的
实现类似Spring的可配置的AOP框架
第一个类 FactoryBean bean工厂用于产生bean
public class BeanFactory { Properties properties = new Properties(); public BeanFactory(InputStream is) { try { properties.load(is); } catch (IOException e) { e.printStackTrace(); } } public Object getBean(String name) throws Exception { //根据传入的名字判断为javaBean还是代理 String className = properties.getProperty(name); Object bean = null; Class clazz = Class.forName(className); bean = clazz.newInstance(); if (bean instanceof ProxyFactoryBean) { //如果为一个代理对象,产生响应的代理bean ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean) bean; 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); Object proxy = proxyFactoryBean.getProxy(); return proxy; } return bean; //直接返回普通javaBean }}
ProxyFactoryBean.java 产生代理的类 要求有两个参数 目标类target 以及Advice
public class ProxyFactoryBean { private Object target; private Advice advice; /** * @return the target */ public Object getTarget() { return target; } /** * @param target * the target to set */ public void setTarget(Object target) { this.target = target; } /** * @return the advice */ public Advice getAdvice() { return advice; } /** * @param advice * the advice to set */ public void setAdvice(Advice advice) { this.advice = advice; } public Object getProxy() { Object proxy3 = Proxy.newProxyInstance(target.getClass() .getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /** * 如果目标类出现在这,则proxy每次调用方法时都会调用invoke() * 每次都会是一个全新的ArrayList对象 即后边的size() 总是0 */ // ArrayList target = new ArrayList(); // //为代理类挂上目标类,代理类需为前边接口的实现类 advice.beforeMethod(method); Object obj = method.invoke(target, args); advice.afterMethod(method); return obj; } }); return proxy3; }}
存放配置的配置文件 config.properties
xxx=java.util.ArrayList#xxx=com.java.javaenhance1.aopFrameWork.ProxyFactoryBeanxxx.advice=com.java.javaenhance1.MyAdivcexxx.target=java.util.ArrayList
测试类:
public class AopFrameWorkTest { public static void main(String[] args) throws Exception { InputStream is = AopFrameWorkTest.class.getResourceAsStream("config.properties"); Object bean = new BeanFactory(is).getBean("xxx"); System.out.println(bean.getClass().getName()); }}
以上的例子实现了Spring的两大核心 IOC(Bean工厂) 及AOP(动态代理)