1.概述
1.1.面向切面(AOP)思想
系统中存在交叉业务,一个交叉业务就是要切入到系统中的某一个方面,比如多个类的共方法例如:需要达到这样的效果:运行某个方法,在可以得到方法应有返回值的同时,可以知道该方法运行的时间.
交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化.
可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术.
1.2.代理的概念
要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,
譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。
2.java动态代理
2.1.概述
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
所以:功能代码可以是:在调用目标方法之前,在调用目标方法之后,在调用目标方法前后,在处理目标方法异常的catch块中.
2.2.Proxy类
static InvocationHandler getInvocationHandler(Object proxy)
//返回指定代理实例的调用处理程序。
//(其实就是对被代理类所做的切面化操作.)
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
//返回代理类的 java.lang.Class 对象,并向其提供类加载器和参数列表接口数组。
//(通过这个Class对象的方法,可以获得一个代理类的实例)
static boolean isProxyClass(Class<?> cl)
//当且仅当指定的类通过 getProxyClass 方法或 newProxyInstance 方法动态生成为代理类时,返回 true。
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序
2.3.字节尝试写一个代理类的代码:
/*
* 需求:
* 写一个ArrayList类的代理,实现和ArrayList中完全相同的功能,并可以计算每个方法运行的时间。
* 思路:
* 代理运用的是一种面向切面编程的思想.代理类在目标类的方法前,后,中的异常处理中嵌入其他功能方法等.
* 这里想要创建一个ArrayList代理,可以直接使用java提供的动态代理来实现.
* 步骤:
* 1.建立Advice接口,定义代理添加的功能接口.
* 2.建立接口实现类,实现计算每个方法运行时间的方法.
* 3.通过Proxy类的静态方法建立获得ArrayList的一个基本动态类(具有和ArrayList相同的方法)
* */
public class Test3 {
public static void main(String[] args){
}
}
interface Advice{
public abstract void before();
public abstract void after();
}
class MyAdvice implements Advice{
long start = 0;
@Override
public void before() {
start = System.currentTimeMillis();
}
@Override
public void after() {
long end = System.currentTimeMillis();
System.out.println("该方法运行时间为:"+(end-start));
System.out.print("方法返回:");
}
}
//按照Proxy提供的静态方法直接构建一个代理类
class ArrayListProxy{
public static void main(String[] args){
final ArrayList<?> target = new ArrayList<>();
final MyAdvice temp = new MyAdvice();
Collection collProxy = (Collection) Proxy.newProxyInstance(ArrayListProxy.class.getClassLoader(), ArrayList.class.getInterfaces(), new InvocationHandler() {
// List <?> listProxy = (List<?>) Proxy.newProxyInstance(ArrayListProxy.class.getClassLoader(), new Class[]{List.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
temp.before();
Object obj = method.invoke(target, args);
temp.after();
return obj;
}
});
((ArrayList) collProxy).get(0);
System.out.println(collProxy.size());
collProxy.add(null);
collProxy.add("abc");
}
}
//按照逐步的方法尝试获得一个代理类
class ArrayListProxy_1{
public static void main(String[] args) throws Exception{
//获得代理类Class文件
Class clazz = Proxy.getProxyClass(ArrayListProxy_1.class.getClassLoader(), Collection.class);
// Class clazz = Proxy.getProxyClass(ArrayListProxy.class.getClassLoader(), ArrayList.class);
//Exception in thread "main" java.lang.IllegalArgumentException: java.util.ArrayList is not an interface
// System.out.println(clazz);
//通过Class文件获得实例对象
// Collection alProxy = (Collection) clazz.newInstance();
// System.out.println(alProxy);
//Exception in thread "main" java.lang.InstantiationException: com.sun.proxy.$Proxy0
//无法被实例化,没有对应构造方法.
//找构造
Constructor[] cons = clazz.getConstructors();
for( Constructor temp : cons){
System.out.println(temp);
}
//public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
//用对应的构造,建立实例对象
//这里
Collection alProxy =(Collection)cons[0].newInstance(new InvocationHandler(){
// Collection<?> alProxy =(Collection<?>)cons[0].newInstance(new InvocationHandler(){
// ArrayList<?> alProxy =(ArrayList)cons[0].newInstance(new InvocationHandler(){
ArrayList<?> target = new ArrayList<>();
@Override
public Object invoke(Object alProxy, Method method, Object[] args)
throws Throwable {
MyAdvice temp = new MyAdvice();
temp.before();
System.out.println(temp.start);
Object tempObj = method.invoke(target,args);
temp.after();
return tempObj;
}});
alProxy.add("abc");
System.out.println(alProxy);
// alProxy.get(0); // 用不了子类的功能,只能用那个接口Collection中的方法.
}
}
3.总结
在自己一步一步用不同方法写一个代理类出来的过程中,发现很多看上去明白了,但实际上只是连想都没开始想的问题.
1.参数列表中的接口Class参数
首先就是构造一个代理类实例对象需要的那个Interface接口参数.老师,用的为Collection.class,结果就让我固定了思维,在需要做一个ArrayList代理是,毫不犹豫的将ArrayList实现的这个父类接口放了进去.
所以,就出现了我代码里测试的那样,这样得到的代理,在调用ArrayList的特有方法的时候,jvm编译时都无法通过,说不识别的方法名.还在考虑难道一个具体实现了某个接口的子类,拥有了父类没有的特有功能,那在代理中就无法使用,那样这代理还有什么意义?
最终在Class类中,找到了答案.Class类中提供了方法:
getInterfaces
public Class<?>[] getInterfaces()确定此对象所表示的类或接口实现的接口。
通过Arraylist.class.getInterfaces(),得到的Class<?>[]作为参数构造出来的代理就解决了我刚开始遇到的问题.
2.在代理类中,对目标功能的封装
在写代理类时,初期就是直接在InvocationHandler的方法中直接将操作代码写进去.后来,才发现可以将功能抽取,到时候在类中接收一个抽取出的接口的实例对象,调用需要增加的功能即可.大大增加可扩展性.
3.装饰设计模式and代理模式
还有一点,就是冥冥中觉得,装饰设计模式跟代理(面向切面)的思想应该有很大的隐情,有一腿.前者,只不过是把需要功能进行增强的类的实例当作了一个构造的实例参数传入了包装类中.代理则干脆直接伪装成一个目标类,用户以为自己操作的是目标类,实际上是一个披了目标外表的代理类...
他们两者到底有什么关系?等想的有些头绪再跟新吧...