目录
一、代理模式是什么?Java的静态、动态代理模式
1、静态代理
2、动态代理
2.1 基于接口的动态代理(JDK官方)
2.2 基于子类的动态代理(第三方CGLib)
二、Spring AOP 怎么应用的动态代理
参考资料:
你真的完全了解Java动态代理吗?看这篇就够了
Java的三种代理模式
理解java的三种代理模式
Proxy是一种设计模式,通过 代理对象 来访问 被代理对象 ,可以在被代理对象的基础之上,增强额外的功能,而不需要修改别人已经写好的代码。
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。
代码实例:
被代理对象与代理对象一起实现相同的接口如下:
/**
* 接口
*/
public interface IUserDao {
void save();
}
目标对象(被代理对象):
/**
* 接口实现
* 目标对象
*/
public class UserDao implements IUserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
}
代理对象:
/**
* 代理对象,静态代理
*/
public class UserDaoProxy implements IUserDao{
//接收保存目标对象
private IUserDao target;
public UserDaoProxy(IUserDao target){
this.target=target;
}
public void save() {
System.out.println("开始事务...");
target.save(); //代理对象的方法中包含了被代理对象的方法,执行被代理对象的方法
System.out.println("提交事务...");
}
}
测试:
/**
* 测试类
*/
public class App {
public static void main(String[] args) {
//目标对象
UserDao target = new UserDao();
//代理对象,把目标对象传给代理对象,建立代理关系
UserDaoProxy proxy = new UserDaoProxy(target);
proxy.save();//执行的是代理对象的方法
}
}
总结:代理对象特意包含了目标对象的方法,正是这种特征使得看起来像是“扩展”了目标对象的方法。这里的包含是关键。
静态代理的缺点:
1、需增加的代理类太多
2、被实现的接口如果增加了方法,被代理对象与代理对象都需要修改
在Spring项目中用的注解,例如依赖注入的@Bean、@Autowired,事务注解@Transactional等都有用到,换言之就是Srping的AOP(切面编程)。可以非常灵活地在某个类,某个方法,某个代码点上切入我们想要的内容,就是动态代理其中的内容。
动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)。代理对象不需要实现接口,需要指定接口类型。但是目标对象一定要实现接口,否则不能用动态代理
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader, Class>[] interfaces,InvocationHandler h )
三个参数的意义是:
ClassLoader loader:指定当前目标对象使用的类加载器,也就是代理对象和被代理对象使用相同的类加载器
Class>[] interfaces:目标对象实现的接口的类型。也就是代理对象和被代理对象具有相同的行为。实现相同的接口。
InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。
实例代码
目标对象必须实现的接口:
public interface IActor {
/**
* 基本演出
* @param time 表演时长
*/
public void basicAct(float time);
/**
* 危险演出
* @param time 表演时长
*/
public void dangerousAct(float time);
}
目标对象的类,实现了接口:
public class Actor implements IActor {
/**
* 基本演出
*
* @param time 表演时长
*/
@Override
public void basicAct(float time) {
System.out.println("基本表演,时长为:" + time);
}
/**
* 危险演出
*
* @param time 表演时长
*/
@Override
public void dangerousAct(float time) {
System.out.println("危险表演,时长为:" + time);
}
}
测试类,在本类中实现了代理对象
public class ClientTest {
public static void main(String[] args){
// 生成被代理的对象,此处和静态代理的目的不一样,所以不是生成IActor的实例
final Actor actor = new Actor(); // 内部类调用外部成员变量,必须为final
// 获取代理对象
IActor proxy = (IActor)Proxy.newProxyInstance(actor.getClass().getClassLoader(),
actor.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 执行被代理对象的任何方法,都会经过该方法。
* 此方法有拦截的功能。
* @param proxy 代理对象的引用。不一定每次都用得到
* @param method 当前执行的方法对象
* @param args 执行方法所需的参数
* @return 前执行方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName(); // 代理对象执行了接口声明的任意方法都会调用本invoke方法,此处获得是调用了哪个方法名
Float time = (Float)args[0];
Object returnValue = null; // Method method的返回值
if("basicAct".equals(name)) {
returnValue = method.invoke(actor, time);
}
return returnValue;
}
});
// 开始测试
proxy.basicAct(100);
}
}
上面的测试类中,明明代理对象已经是IActor类型的,为什么还要使用第二个getInterfaces()参数呢?如果看下面这个例子就能明白了:
/**
* 创建动态代理对象
* 动态代理不需要实现接口,但是需要指定接口类型
*/
public class ProxyFactory{
//维护一个目标对象
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
//给目标对象生成代理对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(), // 此处可不知道代理对象是什么接口
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务2");
//执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务2");
return returnValue;
}
}
);
}
}
有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理
Cglib子类代理实现方法:
1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入spring-core-3.2.5.jar
即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
实例代码
还是之前的目标对象,但是没有实现接口
public class Actor {
/**
* 基本演出
*
* @param time 表演时长
*/
@Override
public void basicAct(float time) {
System.out.println("基本表演,时长为:" + time);
}
/**
* 危险演出
*
* @param time 表演时长
*/
@Override
public void dangerousAct(float time) {
System.out.println("危险表演,时长为:" + time);
}
}
测试类,包含了生成的代理对象:
public class Client {
/**
* 基于子类的动态代理
* 要求:
* 被代理对象不能是最终类
* 用到的类:
* Enhancer
* 用到的方法:
* create(Class, Callback):创建参数Class的子类对象
* 方法的参数:
* Class:被代理对象的字节码
* Callback:如何代理
*
* @param args
*/
public static void main(String[] args) {
final Actor actor = new Actor();
Actor cglibActor = (Actor) Enhancer.create(actor.getClass(),
new MethodInterceptor() {
/**
* 执行被代理对象的任何方法,都会经过该方法。在此方法内部就可以对被代理对象的任何
方法进行增强。
在调用目标方法时,CGLib会回调MethodInterceptor接口方法拦截,来实现你自
己的代理逻辑,类似于JDK中的InvocationHandler接口
*
* 参数:
* 前三个和基于接口的动态代理是一样的。
* proxy为:由CGLib动态生成的代理类实例
* MethodProxy:当前执行方法的代理对象,也就是生成的代理类对方法的代理引用。
* 更通俗的说,MethodProxy是cglib生成用来代替Method对象的一个对象,使用
* MethodProxy比调用JDK自身的Method直接执行方法效率会有提升。
* 返回值:
* 当前执行方法的返回值
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
String name = method.getName();
Float time = (Float) args[0];
Object rtValue = null;
if ("basicAct".equals(name)) {
//基本演出
if (time > 2000) {
rtValue = method.invoke(actor, time / 2);
}
}
return rtValue;
}
});
cglibActor.basicAct(10000);
}
}
在Spring的AOP编程中:
如果加入容器的目标对象有实现接口,用JDK代理
如果目标对象没有实现接口,用Cglib代理