一、什么是代理?
代理分类:
- 静态代理
- 动态代理
JDK动态代理 ——对于实现了接口的类生成代理,而不是针对类
Cglib动态代理 ——对于类实现代理,只要是对于指定的类生成一个子类来继承其中的方法
二、静态代理
在说动态代理之前,先提一下静态代理是什么?以及它的局限性在哪?
2.1 什么是静态代理?
为现有的每一个类都编写一个对应的代理类,并且让它实现和目标类相同的接口。
下面通过代码案例来看一下静态代理的实现方式。
- 首先定义一个接口
public interface Action {
/**
* walk
*/
void walk();
/**
* talk
*/
void talk();
}
- 被代理类
public class ActionImpl implements Action{
@ Override
public void walk() {
System.out.println("没事,走两步...");
}
@Override
public void talk() {
System.out.println("没事,说两句...");
}
}
- 静态代理类
public class ActionStaticProxy implements Action{
private final Action action = new ActionImpl();
@Override
public void walk() {
System.out.println("walk前..");
action.walk();
System.out.println("walk后..");
}
@Override
public void talk() {
System.out.println("talk前..");
action.talk();
System.out.println("talk后..");
}
}
- 测试
public class Test {
public static void main(String[] args) {
Action action = new ActionStaticProxy();
action.walk();
System.out.println("------------next method-------------");
action.talk();
}
}
输出:
walk前..
没事,走两步...
walk后..
------------next method-------------
talk前..
没事,说两句...
talk后..
分析:在代理类ActionStaticProxy中,在调用每个方法前后都加了输出语句,这个开头所说的代理模式的思想,在方法的前后增加消息处理。
2.2 静态代理的局限性
由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐,增加不必要的工作。
那么,有没有一种能够少写或者甚至不写代理类的方法来实现代理的功能呢?答案是: 动态代理
三、动态代理
相比于静态代理,动态代理就是不用自己手写代理类,转而由JDK在运行时通过反射生成代理对象。JDK提供了 java .lang.reflect.InvocationHandler 接口和 java.lang.reflect.Proxy 类来进行获得代理对象。
例如,像下面这样:
Class<?> actionProxyClazz = Proxy.getProxyClass(ClassLoader loader, Class<?> interface);
- loader:被代理类的类加载器,目的是为了加载该类实现的接口到内存中
- interface:代理对象需要和目标对象实现相同的接口
现在说说为什么要使用 getProxyClass() 这个方法。
用通俗的话说,getProxyClass()这个方法,会从你传入的接口Class中,“拷贝”类结构信息到一个新的Class对象中,但新的Class对象带有 构造器 ,是可以创建对象的。所以,一旦我们明确接口,完全可以通过接口的Class对象,创建一个代理Class,通过代理Class即可创建代理对象。
3.1 案例说明
- 接口Action
public interface Action {
/**
* walk
*/
void walk();
/**
* talk
*/
void talk();
}
- 接口实现类ActionImpl
public class ActionImpl implements Action{
@Override
public void walk() {
System.out.println("没事,走两步...");
}
@Override
public void talk() {
System.out.println("没事,说两句...");
}
}
根据代理Class的构造器创建对象时,需要传入 InvocationHandler 。 通过构造器传入一个引用,那么必然有个成员变量去接收。 没错这个成员变量就是 InvocationHandler。 而且代理对象的每个方法内部都会调用handler.invoke()。InvocationHandler对象成了代理对象和目标对象的桥梁,不像静态代理这么直接。
- 通过反射创建代理对象的实例
public class DynamicProxyTest {
public static void main(String[] args) {
try {
Action action = new ActionImpl();
/**
* args1: 类加载器
* args2: 代理对象和目标对象实现相同的接口
*/
Class<?> actionProxyClazz = Proxy.getProxyClass(action.getClass().getClassLoader(),
action.getClass().getInterfaces());
// 得到有参构造器 $Proxy0(InvocationHandler h)
constructor <?> constructor = actionProxyClazz.getConstructor(InvocationHandler.class);
// 通过有参构造器new出一个代理对象
Action proxyAction = (Action) constructor.newInstance(new InvocationHandler() {
@Override
public object invoke(Object proxy, method method, Object[] args) throws Throwable {
System.out.println("方法执行前...");
Object invoke = method.invoke(action, args);
System.out.println("方法执行后...");
return invoke;
}
});
proxyAction.walk();//调用方法
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
- 输出结果
方法执行前...
没事,走两步...
方法执行后...
3.2 代码优化
在实际场景中,一般直接调用Proxy代理类的下面这个方法来简化代理对象的创建:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
相比于getProxyClass()方法,这里面多了个InvocationHandler对象,而InvocationHandler是一个接口,因此首先要创建一个类实现该接口。
- InvocationHandler的实现类
public class DynamicProxy implements InvocationHandler {
/**
* 用于接收所有实现了不同接口的实例对象
*/
private Object object;
/**
* 绑定委托对象,并返回代理类
* @param object
* @return
*/
public Object getProxyAfterBind(Object object){
this.object = object;
//绑定该类实现的所有接口,获取代理对象,返回类型为Object,具体类型需要在调用方强转
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
}
/**
* @param proxy 代理对象本身
* @param method 反射调用invoke方法
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这里就可以进行所谓的AOP编程了
System.out.println("调用方法前..执行相应的逻辑");
Object result = method.invoke(object, args);
System.out.println("调用方法后..执行相应的逻辑");
return result;
}
}
- 代码测试
public class DynamicProxyTest {
public static void main(String[] args) {
Action action = new ActionImpl();
DynamicProxy dynamicProxy = new DynamicProxy();
// 获取ActionImpl类的代理对象
Action proxyAction = (Action) dynamicProxy.getProxyAfterBind(action);
// 此处调用只是个壳子,正在的实现是通过反射调用DynamicProxy中的invoke方法
proxyAction.talk();
System.out.println("-----------分割线--------------");
// 此处调用只是个壳子,正在的实现是通过反射调用DynamicProxy中的invoke方法
proxyAction.walk();
}
}
输出:
调用方法前..执行相应的逻辑
没事,说两句...
调用方法后..执行相应的逻辑
-----------分割线--------------
调用方法前..执行相应的逻辑
没事,走两步...
调用方法后..执行相应的逻辑
3.3 JDK动态代理一定需要实现类吗?
答案是:不一定。
应用场景:Mybatis的Mapper接口代理,Mybatis是在invoke方法里面通过调用SqlSession最后进入到doQuery()方法来执行sql语句并返回对应的结果。
- 定义一个Subject接口
public interface Subject {
/**
* 根据id查询数据
* @param id
* @return
*/
Object selectById(Integer id);
}
- 接口代理对象
public class SubjectProxy<T> implements InvocationHandler {
private Class<T> proxyInterface;
public SubjectProxy(Class<T> proxyInterface) {
this.proxyInterface = proxyInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class<?> declaringClass = method.getDeclaringClass();
// 这里做一个说明:如果调用的是Object自带的方法,比如:toString()、hashCode()方法,则会进入该分支
if (Object.class.equals(declaringClass)) {
System.out.println("declaringClass= " + declaringClass);
} else {
if ("selectById".equals(method.getName())) {
System.out.println("反射调用该方法名称为selectById");
}
}
return null;
}
@SuppressWarnings("unchecked")
public T getProxy() {
return (T) Proxy.newProxyInstance(proxyInterface.getClassLoader(), new Class[]{proxyInterface}, this);
}
}
- 测试
public class Test {
public static void main(String[] args) {
SubjectProxy subjectProxy = new SubjectProxy(Subject.class);
Subject subject = (Subject) subjectProxy.getProxy();
subject.selectById(1);
// 这会进入到if(Object.class.equals(method.getDeclaringClass()))分支
subject.toString();
}
}
四、总结
- 动态代理的核心目的是: 动态的代理目标实例,并附加一些代理逻辑 。
- 其实无论是静态代理还是动态代理本质都是最终生成 代理对象 ,区别在于静态代理对象需要人 手动生成 ,而动态代理对象是运行时,JDK通过 反射 动态生成的代理类最终构造的对象,JDK生成的类在加载到内存之后就删除了,所以看不到类文件。
- 动态代理的作用:为了进行aop编程,也就是可以在 目标类 的 目标方法 执行 前后 添加一些额外的操作。比如进行 权限校验 , 记录日志 等操作