一、何为代理(Proxy)
代理,即:你不用去做,别人代替你去处理。
代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象。
如:在公司里要上外网,要在浏览器里设置一个 HTTP 代理;
王宝强事业忙没空照顾妻子,被宋JJ代替照顾了(O(∩_∩)O哈哈~)。
Spring的AOP底层原理就是通过JDK原生动态代理实现的。
二、静态代理
先看一个简单的例子:
public interface Star {
void sing(String songName);
void goShow(String city);
}
@Component
public class DengChao implements Star{
@Override
public void sing(String songName) {
System.out.println("请邓超唱一首:"+songName);
}
@Override
public void goShow(String city) {
System.out.println("让邓超去"+city+"参加节目");
}
}
DengChao实现了Star这个接口,假如我需要在实现类中两个方法的输出语句(System.out...)前后加入一些内容,比如:日志、校验等信息时,如果直接把这些逻辑写死在这两个方法里面,感觉不太好,不够灵活,所以为了不侵入原本的业务逻辑,使用静态代理,代码如下:
@Component
public class StaticProxy implements Star{
/**
* 委托类
*/
private DengChao dengChao;
public StaticProxy(DengChao dengChao) {
super();
this.dengChao = dengChao;
}
@Override
public void sing(String songName) {
System.out.println("前置信息");
dengChao.sing(songName);
System.out.println("后置信息");
}
@Override
public void goShow(String city) {
System.out.println("前置信息");
dengChao.goShow(city);
System.out.println("后置信息");
}
}
测试一下:
class TestStaticProxy {
@Test
void test() {
DengChao dengChao = new DengChao();
Star star = new StaticProxy(dengChao);
star.sing("我是超级英雄");
star.goShow("深圳");
}
}
结果为:
前置信息
请邓超唱一首:我是超级英雄
后置信息
前置信息
让邓超去深圳参加节目
后置信息
虽然实现了相关的功能,但是感觉还是不太好,因为
1、代理类要跟被代理类一样实现相同的接口,多个接口的实现类要被代理的话就需要写多个代理类,那么就会出现大量重复的代码,作为一个有思想的程序员是绝对不允许的。
2、如果接口增加一个方法时,实现类要修改,代理类也要修改,增加了代码维护的复杂度
更好的方式就是动态代理。
三、动态代理
动态代理:应用程序发布后,通过动态创建代理对象;动态代理可以动态地创建代理并动态地处理对所代理方法的调用。
其中动态代理又可分为:
1、JDK原生动态代理
JDK动态代理只能针对实现了接口的类生成代理。
只需要一个代理类,而不是针对每个类编写代理类。
新增一个动态代理类实现实现了 InvocationHandler 接口,那么必须实现该接口的 invoke 方法。
通过调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要
- 一个类加载器(通过可以从已经被加载的对象中获取其类加载器)
- 你希望代理实现的接口列表(不是类或抽象类)
- InvocationHandler接口的一个实现
动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造器传递给一个“实际”对象的引用,从而使得调用处理器在执行其中介任务时,可以将请求转发。
在invoke()内部,在代理上调用方法时需要格外小心,因为对接口的调用将被重定向为对代理的调用。
Method.invoke()将请求转发给被代理对象,并传入必需的参数。
代码如下:
public class DynamicProxyHander implements InvocationHandler{
private Object target;//用于接收具体实现类的实例对象
/**
* 使用带参数的构造器来传递具体实现类的对象
* @param object
*/
public DynamicProxyHander(Object target) {
this.target = target;
}
@SuppressWarnings("unchecked")
public <T> T getProxy() {
/**
* this:当前对象自己
*/
System.out.println("this is "+this);
System.out.println("Interfaces is" +Arrays.asList(target.getClass().getInterfaces()));
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
/**
* proxy:代理对象
* method:原对象被调用的方法
* args:方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置通知");
Object result = method.invoke(target, args);
System.out.println("后置通知");
return result;
}
}
测试一下:
@Test
void test() {
DynamicProxyHander dp = new DynamicProxyHander(new DengChao());
Star starProxy = dp.getProxy();
starProxy.sing("超级英雄");
starProxy.goShow("南昌");
}
结果如下:
this is com.cb.springstudy.dynamicproxy.DynamicProxyHander@3f49dace
/**两个接口是因为我让DengChao这个 类实现了Star和Actor两个接口**/
Interfaces is[interface com.cb.springstudy.dynamicproxy.Star, interface com.cb.springstudy.dynamicproxy.Actor]
前置通知
请邓超唱一首:超级英雄
后置通知
前置通知
让邓超去南昌参加节目
后置通知
JDK动态代理有个局限是只能针对实现了接口的类生成代理,如果没有实现接口的类想生成代理呢,那么就能用
CGLIB动态代理
2、CGLIB动态代理
CGLIB(CODE GENERLIZE LIBRARY)代理是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的所有方法,所以该类或方法不能声明称final的。
增加一个CGLibProxy类实现MethodInterceptor接口,要实现intercept这个方法
public class CGLibProxy implements MethodInterceptor{
private static CGLibProxy instance = new CGLibProxy();
private CGLibProxy() {
}
public static CGLibProxy getInstance() {
return instance;
}
public <T> T getProxy(Class<T> cls) {
return (T) Enhancer.create(cls, this);
}
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
System.out.println("前置日志");
Object result = arg3.invokeSuper(arg0, arg2);
System.out.println("后置日志");
return result;
}
}
代理没有实现接口的类,如:
@Component
public class DengChao{
public void sing(String songName) {
System.out.println("请邓超唱一首:"+songName);
}
public void goShow(String city) {
System.out.println("让邓超去"+city+"参加节目");
}
}
测试一下:
@Test
void test() {
DengChao dengChao = CGLibProxy.getInstance().getProxy(DengChao.class);
dengChao.sing("中华英雄");
}
结果:
前置日志
请邓超唱一首:中华英雄
后置日志