黑马程序员——Java高新_动态代理

时间:2021-06-25 00:46:00

 ----------------------Android培训Java培训、期待与您交流! ----------------------

1 代理类的作用?

现在程序中有这样的一些需求:要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?

编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。

个人理解:就像代理商从供货商拿货给用户一样,代理类(代理商)从目标(供货商)那里拿了方法(货),代理类可以对这个方法进行一些操作,给它加点“料”再返还出去。比如可以加进去一些异常处理、日志等功能再返回。

代理构架图

黑马程序员——Java高新_动态代理

个人理解:客户端调用程序Client调用代理类Proxy,这个代理类就会调用目标类中的doSomeThing方法,并且在这个方法的前后加了一点“料”(前置系统功能代码、后置系统功能代码)。Target目标类中因为实现了某个或多个接口才能使用doSomeThing方法,为了能够调用目标类中的doSomeThing等方法,代理类Proxy应当与目标类Target实现同样的接口才能调用这些方法。

-----------------------------分割线-----------------------------

2 提到代理类的应用,就要提到AOPAspect Oriented Programming),即面向切面编程。

这个东西有什么特点?

它是OOP(面向对象编程)的延续。主要的意图是:将日志记录,性能统计,安全控制,事务处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

个人理解:若多个代码块或者方法中都出现了某些同样的功能需要去实现(如日志记录、性能统计等),那么,同样的东西何必要重复的去编写呢?所以我们可以把这些功能抽取成一个方法划分并独立出来。这样就极大的避免了影响业务逻辑代码。

没有实现AOP之前是这样的:

黑马程序员——Java高新_动态代理

实现了AOP之后是这样的:

黑马程序员——Java高新_动态代理

这样,同样的功能不就变得模块化独立起来了吗?

AOP的原则:不把供货商暴露给客户。而动态代理就是实现AOP的重要手段和技术。AOP的原理就是动态代理机制。

-----------------------------分割线-----------------------------

3 那么动态代理是什么?

动态代理:JVM可以在运行期动态生成出类的字节码,这种动态生成的类如果被用作代理类的话,即称之为动态代理类。

个人理解:动态代理类就是代理类和反射技术的融合产物。

JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。

个人理解:目标类如果是像ListSet这些也实现了Collection接口的,那么代理类就也要实现Collection接口,不然,代理类怎么去调用目标类的方法啊?

代理类存在的用法和最大意义:

代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:

1.在调用目标方法之前

2.在调用目标方法之后

3.在调用目标方法前后

4.在处理目标方法异常的catch块中

 

动态代理机制有两个重要的类或接口:InvocationHandler接口Proxy类。

那么,如何才能创建这个酷炫的动态代理类?

通过查阅API发现,它的构造方法需要一个实现了InvocationHandler接口的对象,

每个动态代理类都应当实现这个接口。

列出三种构造方式:

//构造方式一:先用getProxyClass()获得字节码,使用constructor类获得代理类的构造方法,构造方法需要一个InvocationHandler
//然后传入到newInstance()方法中进行实例创建
Class clazzProxy1 =
Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
class MyInvocationHandler implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
}
}
Collection proxy =
(Collection)constructor.newInstance(new MyInvocationHandler());
System.out.println(proxy);
proxy.clear();
//构造方式二:将实现InvocationHandler以匿名对象的方式进行定义(熟练后的写法)
Collection proxy2 = (Collection) constructor.newInstance(new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
}
});
//构造方式三:newProxyInstance一步到位,需要3个参数:目标类的字节码
//需要实现的接口,还有一个InvocationHandler
final ArrayList target = new ArrayList();
Collection proxy3 = (Collection)getProxy(target,new MyAdvice());
proxy3.add("lx");
proxy3.add("js");
proxy3.add("fs");
其中最常用的是第三种构造方式,getProxy()方法的代码如下:

private static Object getProxy(final Object target, final Advice advice) {
Object proxy3 = Proxy.newProxyInstance
(target.getClass().getClassLoader(),
//代理类可能会实现多个接口!怎么办?用可变参数吗?不行!可变参数只能放在所有参数的最后面
//所以可以使用数组来实现 如:<span style="font-family: 宋体;">new Class[] {Collection.class},</span>
//不知道目标类是什么类型!怎么写才能更为适用?改为使用与目标类相同的接口
target.getClass().getInterfaces(),
new InvocationHandler(){
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
// TODO Auto-generated method stub
//自定义服务中写在调用目标方法之前的事件
advice.beforeMethod(method);
//对目标类调用方法
Object retVal = method.invoke(target, args);
//自定义服务中写在调用目标方法之后的事件
advice.afterMethod(method);
//这个retVal返回值(目标类要求返回的值)应当与代理类的返回值类型一致
return retVal;
}

});
return proxy3;
}
再给出增加的服务的Advice接口的代码,它定义了两个方法用于插在invoke()方法的前后:

import java.lang.reflect.*;
public interface Advice {
void beforeMethod(Method method);
void afterMethod(Method method);
}

总结一下需要注意的地方:

目标方法要求返回的值类型与代理类返回的值类型应当保持一致;

newProxyInstance()方法可以得到一个动态代理类,它需要接受3个参数

public static Object newProxyInstance(ClassLoader loader, 

Class<?>[] interfaces, 

InvocationHandler h)

Loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载

Interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了

h一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

InvocationHandler中需要实现的invoke()方法接受三个参数

invoke(Object proxy, Method method,Object[] args)

proxy: 指代我们所代理的那个真实对象

method: 指代的是我们所要调用真实对象的某个方法的Method对象

args: 指代的是调用真实对象某个方法时接受的参数

这个invoke方法要用到这些参数的原因是:代理类要去找目标类的方法并调用,然后返回。


问题来了:既然是这样,不是去找目标的吗,为什么下面这句代码打印出来的不是目标类的字节码名字?

System.out.println(proxy3.getClass().getName()); //打印出来的是代理类的字节码名字

答:因为目标委托给handler方法中,只有hasCode()equals()toString()这三个方法交给handler去实现,其他的方法自己去实现。所以这里调用了getClass()是打印的代理类的字节码名称。

-----------------------------分割线-----------------------------

前面提到AOP面向切面编程,那么动态代理是如何实现它的呢?

实现AOP功能的封装与配置

用到了两个东西:BeanFactoryProxyFactoryBean

BeanFactory Bean工厂

下面这句代码中getBean()方法的作用:通过配置文件信息判断使用目标类还是使用动态代理。

Object obj = BeanFactory.getBean(“xxx”);

BeanFactory的构造方法接受代表配置文件的输入流对象,配置文件的格式如下:

#xxx = java.util.ArryList

xxx = com.itheima.ProxyFactoryBean

xxx.target = java.util.ArrayList

xxx.advice = com.itheima.MyAdvice 

 

javaBean的特殊作用:ProxyFactoryBean。它充当封装生成动态代理的工厂,需要为工厂类提供某些配置参数信息,这些信息是:目标、通知。

javaBean一定会有一个不带参数的构造方法

ProxyFactoryBean应定义为一个接口,然后去实现它较好,视频中省略,直接自定义了一个ProxyFactoryBean类。



 ---------------------- Android培训 Java培训 、期待与您交流! ----------------------