代理的作用在不修改原有代码的情况下,去增强跟核心业务没有关系的公共功能的代码。
例如,加日志,增加权限控制,加事物管理。
AOP 底层原理 - 代理
首先什么是代理 ? ----代理就是由委托人与代理人签订代理协议,授权代理人在一定范围内代表其向第三者进行处理有关事务。
静态代理
定义一个游戏玩家的接口
public interface Player {
void play();
}
一个普通游戏玩家,实现了玩家的接口
public class NormalPlayler implements Player {
public String name;
public NormalPlayler(String name){
this.name = name;
}
public void play() {
login();
System.out.println(this.name + "游戏中...");
logout();
}
private void login(){
System.out.println(this.name + "登录游戏成功!");
}
private void logout(){
System.out.println(this.name + "退出游戏成功!");
}
}
普通玩家玩游戏:
public class ProxyTest {
public static void main(String[] args) {
Player player = new NormalPlayler("Bill");
player.play();
}
}
打印结果:
Bill登录游戏成功!
Bill游戏中...
Bill退出游戏成功!
Process finished with exit code 0
现在我想在不改变普通玩家类的前提下做一些功能的增强,在每次登录前判断下是否在8:00-10:00时间区间吗,只有这段时间才可以玩游戏
用静态代理的方式实现:
定义一个代理类,在每次你要玩游戏的时候就不能用普通玩家了,只能用代理。
public class PlayerProxy implements Player {
Player player ;
public PlayerProxy(Player player){
this.player = player;
}
public void play() {
System.out.println("现在处于8:00-10:00时间窗口中,可以玩游戏");
this.player.play();
System.out.println("游戏空闲中");
}
}
用代理玩家代替普通玩家:
public class ProxyTest {
public static void main(String[] args) {
Player player = new NormalPlayler("Bill");
Player player1 = new PlayerProxy(player);
player1.play();
}
}
执行结果:
现在处于8:00-10:00时间窗口中,可以玩游戏
Bill登录游戏成功!
Bill游戏中...
Bill退出游戏成功!
游戏空闲中
Process finished with exit code 0
上面例子用静态代理实现了我们想要等功能,既没有改变我原来的类,又增加了新功能,
但是我们总不能为每个需要代理的类都创建一个代理类吧,这样开发成本太高了。
所以就让动态代理排上用场了:
JDK 动态代理
public class ProxyTest {
public static void main(String[] args) {
Player player = new NormalPlayler("Bill");
Player proxPlay = (Player) Proxy.newProxyInstance(player.getClass().getClassLoader(), player.getClass().getInterfaces(),(proxy, method, arg)->{
System.out.println("现在处于8:00-10:00时间窗口中,可以玩游戏");
Object obj = method.invoke(player,arg);
System.out.println("游戏空闲中");
return obj;
});
proxPlay.play();
}
}
执行结果:
现在处于8:00-10:00时间窗口中,可以玩游戏
Bill登录游戏成功!
Bill游戏中...
Bill退出游戏成功!
游戏空闲中
Process finished with exit code 0
如上代码, 每次只要传入不同的player,或者各种实例都可以,Proxy利用反射机制实现对我们自己的方法的调用,我们可以在调用自己的方法前后都加需要的代码。
下面详细介绍下invoke方法的参数
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
ClassLoader loader -- 一般用我们自己接口的classLoader类加载器
Class<?>[] interfaces一般使用接口的getInterface获取接口数组
InvocationHandler h -- 最后一个参数,是一个接口,我们需要实现InvocationHandler接口的invoke方法
public Object invoke(Object proxy, Method method, Object[] args)
method.invoke-- 这个方法就是调用我们自己需要代理的方法。第一个参数是我们的实例,第二个参数是传进来的参数
args -- 被代理方法的参数列表
CGLIB 动态代理:
用cglib之前需要加入cglib包:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
public class ProxyTest {
public static void main(String[] args) {
NormalPlayler player = new NormalPlayler("Bill");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(NormalPlayler.class);
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
System.out.println("现在处于8:00-10:00时间窗口中,可以玩游戏");
Object obj = method.invoke(player,objects);
System.out.println("游戏空闲中");
return obj;
});
NormalPlayler cgPlayler = (NormalPlayler) enhancer.create();
cgPlayler.play();
}
}
执行结果:
现在处于8:00-10:00时间窗口中,可以玩游戏
Bill登录游戏成功!
Bill游戏中...
Bill退出游戏成功!
游戏空闲中
Process finished with exit code 0
使用cglib 的时候可以看出, 我们没有实现任何接口也没有继承任何类也可以实现代理。
这个接口只有一个intercept()方法,这个方法有4个参数:
1)obj表示增强的对象,即实现这个接口类的一个对象;
2)method表示要被拦截的方法;
3)args表示要被拦截方法的参数;
4)proxy表示要触发父类的方法对象;
JDK 动态代理也有局限性:
1. 代理的类不能加到spirng ioc里面去
2. 每个被代理的对象都必须实现接口。
CGLIB 动态代理的局限性,没法把代理类加到spring ioc中去。
所以用Spring的AOP 比较好。