一、生活中的代理
想买优衣库的衣服,可以在区域代理商店里买到,也可以到旗舰店、总部买到。哪儿都能买到。但是如果到当地的代理商那儿买,更方便,省时省钱。这就是代理商的作用:它能实现总部能买到衣服的功能。同时又有自己的优势之处。
二、代理
1.概念
程序中如果需要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,如日志、事务管理、异常处理、计算方法的时间等,可以编写一个与目标类实现相同接口的代理类。代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能代码。
如果采用工厂模式和配置文件的方式进行管理,就不需要修改客户端程序。在配置文件中配置,明确使用目标类,还是代理类,方便的实现切换功能。通过切换,可以在不同时间增加或者去掉系统功能。
2.代理架构图
三、动态代理技术
1.动态代理产生的必要性:
要为系统中的实现各种接口的类增加代理功能,很可能需要太多的代理类,若全部采用静态代理方式,那么工作量就很大。
2.动态代理类的概念
JVM在运行期间可以动态生成类字节码,这种动态生成的类往往被用作代理类,即动态代理类。
3.哪些动态类可以作为代理类?
1)JVM生成的动态类必须实现一个或者多个接口,所以JVM生成的动态类只能作为实现相同接口的目标类的代理类。
2)如果动态类没有实现接口,那么可以使用CGLB库。因为CGLB库可以动态生成一个类的子类,子类继承了父类的所
有方法,所以动态生成的子类也可以作为该父类的代理类。
4.系统功能代码可以加在代理类中的哪些位置?
代理类的各个方法中通常除了要调用目标类的相应方法和对外返回目标返回的结果外,还可以再代理类中的如下四个位置加上系统功能代码:
1) 在调用目标方法之前。
2) 在调用目标方法之后。
3) 在调用目标方法前后。
4) 在处理目标方法异常的catch块中。
系统功能代码出现位置示意图
5.分析JVM动态生成的类。
创建一个实现了Collection接口的动态代理类,并打印出其构造方法列表和一般方法列表。
步骤:
1)利用反射获取动态代理类的字节码文件clazzProxy。
调用方法:Proxy.getProxyClass(类加载器.class,实现的接口.class文件);
2)根据字节码文件获取动态代理类的构造函数。
Constructor[]constructor = clazzProxy.getConstructors();
3)利用循环将构造函数名称以及参数添加到StringBuilder sb中,打印构造函数列表。
4)根据字节码文件获取动态代理类的方法数组。
5)同样用循环打印方法成列表。(ProxyDemo1.java)
6.让JVM创建动态类及其实例对象,需要提供哪些信息?
三个方面:
1) 生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知。
2) 产生的类字节码必须有一个关联的类加载器对象。
3)生成的类中的方法的代码是怎样的,由我们提供,把我们的代码写在一个约定好了接口对象的方法中,
把对象传给他,它调用我们的方法,就相当于插入了我的代码,提供执行代码的对象就是
InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的,在上面的
InvocationHandler对象invoke()方法中加一点代码,就可以看到这些代码被调用运行了。
其实,可以用Proxy.newInstance()方法直接一步就创建出代理对象。
7.分析动态生成的类的内部代码
1)动态代理类中的构造方法接收一个InvocationHandler对象,构造方法内部代码会声明一个InvocationHandler类型的成员变量,接收传进来的对象并记录它。以便在类中使用。
即,这个构造方法代码如下:
InvocationHandlerhandler;
public $Proxy0(InvocationHandler handler){
this.handler = handler;
}
2)实现Collection接口的动态类中的各个方法的代码解析:
①InvocationHandler接口中的invoke()方法接收了三个参数:
Objectproxy:调用invoke()的代理类对象。
Methodmethod:运行的proxy对象方法。
Object[]args: proxy对象运行的方法传入的参数,空参时,该参数为null
Client客户端调用objProxy.add(“aaa”)时,add方法底层会调用invoke(objProxy,add,”aaa”);
代码如下:
<span style="font-size:18px;">class $proxy0{
add(Object obj){
return handler.invoke(Object proxy,Method method,Object[] args);
}
}
</span>
②生成的实现Collection接口的动态类中各个方法的运行原理:
<span style="font-size:18px;">int size(){
return handler.invoke(this,this.getClass().getMethod(“size”),null);
}
void clear(){
return handler.invoke(this,this.getClass().getMethod(“clear”),null);
}
</span>
3)代理类有返回值时,代理方法通过invoke方法,会调用目标类,目标类方法运行后会返回值,该返回值就是invoke方
法返回的值。Invoke方法继续将该值返回给代理类的方法。也就是说,代理类返回的值其实就等于目标类方法返回
的值。
4)调用目标类方法之前,可以在invoke方法中修改参数,同样的,运行完目标类方法后,也可以改变返回的值。
5) 没有重写invoke方法之前,有返回值的方法会报告异常。
调用size方法时,方法内部会调用invoke()方法,invoke返回null;size要的是整数,null不能转换为整数,所以会抛
出异常。
那为什么代理类调用getClass()时没报错呢?
因为Object只将hashCode(),equals(),toString()这三个方法给InvocationHandler。
其他方法,包括getClass()方法,Object保留自己的处理方式,所以,代理调用getClass()方法时,内部没有调用invoke()方法, 所以,不会报错。
8.动态代理的工作原理总结
1.客户端调用代理
2.代理的构造方法接收一个InvocationHandler对象
3.客户端调用代理的各个方法
4.方法运行时将调用请求转发给Handler对象中的invoke方法,invoke方法根据代
理运行的方法调用目标类的相应的方法。
5.方法中的目标类和系统功能代码可以抽取出来,系统功能代码封装在通告(Advice)中,目标类则封装在
target类中。将这两个对象以参数的形式传递给invoke方法,调用对象中的方法即可。
四、AOP-面向方面的编程
1.概述
AOP,Aspect Oriented Program的缩写,面向方面的编程。面向方面的编程,也称为交叉业务的编程问题。
所谓交叉业务,举例来说,就是安全、事务、日志等功能要贯穿到多个模块中。一个交叉业务要切入到系统中的一个方面。
安全 事务 日志
StudentService ------|----------|------------|-------------
CourseService ------|----------|------------|-------------
MiscService -------|----------|------------|-------------
用具体的程序代码描述交叉业务:
1)交叉业务的代码实现
method1 method2 method3
{ { {
------------------------------------------------------切面
.... .... ......
------------------------------------------------------切面
} } }
2)交叉业务的编程问题即为面向方面的编程(Aspect orientedprogram ,简称AOP),AOP的目标就是要使交叉业
务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样
的,如下所示:
------------------------------------------------------切面
func1 func2 func3
{ { {
.... .... ......
} } }
------------------------------------------------------切面
AOP的目标就是使交叉业务模块化。可以将切面代码移动到原始方法的周围。这与直接在方法内部编写切面代码的效果是一样的。用代理技术正好可以解决这种问题。代理技术是AOP功能的核心和关键技术。
2.实现AOP功能的封装与配置
整体思路:
1) 创建工厂类BeanFactory
① 工厂类BeanFactory负责创建目标类或者代理类的实例对象,并通过配置文件config.properties实现切换。该类中创
建getBean(String name)方法,根据参数字符串返回一个相应的对象bean。
②如果参数字符串在配置文件对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象(配置为目标类)。如
果是ProxyFactoryBean类的对象,将bean强转为(ProxyFactoryBean)bean,并调用新对象的getProxy方法,返回一
个代理类。
BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:
#xxx=java.util.ArrayList //”#”表示注释当前行
xxx=cn.itcast.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.itcast.MyAdvice
2) 封装创建代理类方法的ProxyFactoryBean类
① ProxyFactoryBean类中封装了动态代理类的方法,该类需要为工厂类BeanFactory提供如下信息:
目标(target)
通告(advice)
② 根据工厂类中读取的配置文件信息,获取目标和通告后,工厂类会调用
ProxyFactoryBean类中的getProxy()方法创建代理类。
3)编写实现Advice接口的类和在配置文件中进行配置。
4)定义一个测试类:AopFrameworkTest,也称客户端,调用BeanFactory获取对象。
代码实现:
工厂类BeanFactory
<span style="font-size:18px;">import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import p8.proxy.Advice;
//工厂类
public class BeanFactory {
// 创建Properties对象,用于加载配置文件
Properties props = new Properties();
// 构造方法,接收配置文件输入流对象
public BeanFactory(InputStream ips) {
// 加载接收的配置文件
try {
props.load(ips);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 判断bean是否是ProxyFactoryBean的实例对象。
* 是,就创建代理类
* 不是,则返回普通类对象bean
* @param name
* @return
*/
//创建目标类或代理类的实例对象
public Object getBean(String name) {
// 根据Bean的名字,从配置文件中获取类名
String className = props.getProperty(name);
Object bean = null;
try {
// 根据类名,利用反射获取类文件
Class clazz = Class.forName(className);
// 创建Bean对象。JavaBean类必须要有一个不带参数的构造方法
bean = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
// 判断bean是否是ProxyFactoryBean的实例对象。是,就创建代理类
if (bean instanceof ProxyFactoryBean) {
Object proxy = null;
try {
//根据配置文件获取系统功能和目标类
Advice advice = (Advice) Class.forName(props.getProperty(name+".advice")).newInstance();
Object target = Class.forName(props.getProperty(name+".target")).newInstance();
ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean) bean;
//设置advice 和target的值
proxyFactoryBean.setAdvice(advice);
proxyFactoryBean.setTarget(target);
//获得代理类
proxy = proxyFactoryBean.getProxy();
} catch (Exception e) {
e.printStackTrace();
}
return proxy;
}
return bean;
}
}
</span>
ProxyFactoryBean类
<span style="font-size:18px;">import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import p8.proxy.Advice;
public class ProxyFactoryBean {
private Object target;
private Advice advice;
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public Advice getAdvice() {
return advice;
}
public void setAdvice(Advice advice) {
this.advice = advice;
}
//根据目标类和系统功能代码获取一个代理类
public Object getProxy() {
Object proxy3 = (Object) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
// long beginTime = System.currentTimeMillis();// 增加系统功能
advice.beforeMethod(method);
// 调用目标类方法
Object retVal = method.invoke(target, args);
// long endTime = System.currentTimeMillis();
advice.afterMethod(method);
return retVal;
}
});
return proxy3;
}
}
</span>
MyAdvice类
import java.lang.reflect.Method;
public class MyAdvice implements Advice {
long beginTime;
long endTime;
public void beforeMethod(Method method) {//将系统功能代码封装在此类的方法
System.out.println("开始啦!");
beginTime = System.currentTimeMillis();// 增加系统功能
}
public void afterMethod(Method method) {
endTime = System.currentTimeMillis();
System.out.println("结束啦!");
System.out.println(method.getName()
+ " running time of " + (endTime - beginTime));
}
}
AopFrameworkTest测试类
import java.io.InputStream;
import java.util.Collection;
public class AopFrameworkTest {
/**
* @param args
*/
public static void main(String[] args) throws Exception {
InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
Object bean = new BeanFactory(ips).getBean("xxx");
System.out.println(bean.getClass().getName());
((Collection)bean).clear();
}
}