-------
1、Proxy简介
API中的代理分为 java.lang.feflect包和 java.net包中的代理,前者提供创建动态代理类和实例的静态方法,是所有动态代理类的超类。后者表示代理设置,通常为类型(http、socks)和套接字地址。Proxy 是不可变对象,该代理从 JDK1.5版本开始。现在介绍的代理为前者,动态代理。
程序中的代理:为了给已存在的多个具有相同接口的目标的各个方法增加一些系统功能,如:异常处理、日志、事务管理等。通常会编写一个与目标类具有相同接口的代理类。代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。这样也类似于在目标方法中添加系统功能。
代理架构图:
在某些情况下,如采用工厂模式和配置文件的方式进行管理时,则不需要修改客户端程序,在配置文件中配置是否使用代理类即可,这样以后使用更容易切换,增加了系统功能的灵活性。若不想使用系统功能,通过配置文件配置目标类,就能去掉代理类功能。
2、交叉业务
AOP(Aspect Oriented Program):面向方面的编程
OOP(Object Oriented Program):面向对象的编程
交叉业务:系统中多个模块均会实现如安全、事务管理、日志等功能,这些功能贯穿到多个模块中,它们就是交叉业务。
交叉业务的编程即是面向方面的编程,AOP的目标就是要使交叉业务模块化,将切面代码(如日志、事务管理、安全等代码块)移动到原是方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,而且不会对原目标程序进行改动。如下图:
上面两张实现交叉业务的方式,后者就是通过代理来完成AOP技术的,代理是AOP技术的核心和关键。
3、动态代理
(1) JVM 可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类(但该类不是代理类,只是当作),即动态代理类。
(2) JVM 生成的动态代理类必须实现一个或多个接口,所以 JVM 生成的动态代理类只能用作具有相同接口的目标类的代理。
(3) CGLB 库可以动态的生成一个类的子类,一个类的子类也可以用作该类的代理类。所以,要为一个没有实现接口的类生成动态代理类可以使用 CGLB 库。CGLB 库不是JVM 自带的,是开源的。
(4) 系统功能可以加在代理类代理方法中的四个位置上:调用目标方法之前、之后、前后或处理目标方法异常的catch 块中。
(5) 通过JVM 生成的动态代理类,需要提供那些信息?
① 生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知。
② 生成的类的字节码必须有一个关联的类加载器对象。
③ 生成的类中的方法的代码是怎样的,也得由开发者提供。把我们的代码写在一个约定好的接口(该接口非目标类实现的接口)对象的方法中,把对象传给它,它调用我们的方法,即相当于插入了我们的代码。提供执行代码的接口就是 InvocationHandler(调用处理程序,只有一个invoke 方法需要被复写),它是在创建动态类的实例对象的构造方法(newInstance 或 newProxyInstance)时传递进去的。
(6) 示例
1> 获取代理类的 Class 对象。
Class<?> clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
2> 通过动态代理类中的接口类获取接口类的构造方法以及其参数列表。
Constructor<?>[] cons = clazzProxy.getConstructors();
for (Constructor<?> con : cons) {
String consName = con.getName();
StringBuilder sb = new StringBuilder(consName);
sb.append('(');
Class<?>[] clazzparams = con.getParameterTypes();
for (Class<?> clazzparam : clazzparams) {
sb.append(clazzparam.getName()).append(',');
}
if (clazzparams != null && clazzparams.length != 0)
sb.deleteCharAt(sb.length()-1);
sb.append(')');
System.out.println(sb.toString());
}
3> 通过动态代理类中的接口类获取接口类的一般方法以及其参数列表,同上,只是将类型改为Method。
4> 创建指定代理类对象。
private static Object getProxy(final Object target, final Advice advice)throws Exception{
Object retProxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
// invoke中的参数proxy代理类是通过target的类加载器和继承接口获得的。
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
advice.beforeTime();
// 调用目标类方法。
Object retVal = method.invoke(target, args);
advice.afterTime(method);
return retVal;
}
});
return retProxy;
}
上述代码中代理类的创建是通过匿名内部类的形式来完成的,这样可以更直观简单,前面讲解匿名内部类的时候也提到过,匿名内部类的应用非常之广。getProxy 中的两个参数分别是:前者是目标对象,是任意的;后者是我们约定好的接口类的多态体现形式,该接口中有两个需要被代理类复写的方法,代码为:
public interface Advice {
void beforeTime();
void afterTime(Method method);
}
InvocationHandler 接口中定义的 invoke 方法接收的三个参数所指的意义:Client 程序调用 objProxy.add("abc") (通过代理实现向集合中添加元素,即在原 add 方法前后添加了一些系统功能,扩展了源程序)方法时,涉及到的三个元素分别对应invoke方法的三个参数:objProxy 代理对象,add 方法,"abc" 参数。
class Proxy${
void add(Object obj){
return handler.invoke(Object proxy, Method method, Object...args);
}
}
注:用 (Constructor)cons.newInstance( ) 方法直接一步就可以创建出代理对象。或者用Proxy.newProxyInstance(ClassLoader loader, Class<?>[ ] interfaces, InvocationHandler h) 来创建一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序,相当于先获取代理类的字节码,通过字节获取其构造方法类,并且指定其构造类的调用处理程序,然后在通过构造类创建代理类的实例对象:
Proxy.getProxyClass(loader, interfaces).
getConstructor(new Class[] { InvocationHandler.class }).
newInstance(new Object[] { handler });
4、实现AOP功能的封装与配置
JavaSpring 中代理配置的精髓:
(1) 工厂类 BeanFactory 负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其 getBean() 方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是 ProxyFactoryBean ,则直接返回该类的实例对象(目标类对象),否则返回该类实例对象的 getProxy 方法返回的对象(代理类对象)。
工厂类 BeanFactory 的 getBean 方法是实例化代理类的具体过程,接收一个参数:String key 。方法中会根据具体的参数值来确定返回 Object 对象是代理类还是目标类。(cn.itcast.day3.aopframework.BeanFactory.java)
(2) BeanFactory 的构造方法接收配置文件输入流对象,配置文件格式如下:
#xxx=java.util.ArrayList
xxx=cn.itcast.day3.aopframework.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.itcast.day3.aopframework.MyAdvice
初始化实现代码:
Properties props = new Properties();
public BeanFactory(InputStream in) {
try {
props.load(in);
} catch (IOException e) {
throw new RuntimeException("配置文件不存在!");
}
}
(3) ProxyFactoryBean 类充当封装成生成动态代理类的工厂,其 getProxy 方法需要接收的配置参数为:target 、advice 。
(4) 编写客户应用:① 实现 Advice 接口的子类在配置文件中进行配置。② 调用 BeanFactory 对象。
InputStream in = AopFrameworkTest.class.getResourceAsStream("config.properties");
Object bean = new BeanFactory(in).getBean("xxx");
((Collection<?>) bean).clear();
总结:代理处理方式跟模版设计模式整好相反,模版设计模式是已知的处理信息在调用未知的内容,所以会把未知的内容封装成方法(抽象方法或一般方法),让使用者去复写。而代理是未知的内容在调用已知的信息(目标类方法),开发者只需要去实现需要添加的未知内容(系统功能)即可。