最近在学习java代理,总结输出如下。
java代理分为三种实现方式JDK静态代理,JDK动态代理和CGLIB代理,三种代理的特点及比较如下表
代理方式 | 实现 | 优点 | 缺点 | 其他 |
---|---|---|---|---|
JDK静态代理 | 代理类与委托类实现同一接口,并且在代理类中需要硬编码接口 | 实现简单,容易理解 | 代理类需要硬编码接口,在实际应用中可能会导致重复编码,浪费存储空间并且效率很低 | 实现简单 |
JDK动态代理 | 代理类与委托类实现同一接口,主要是通过代理类实现InvocationHandler并重写invoke方法来进行动态代理的,在invoke方法中将对方法进行增强处理 | 不需要硬编码接口,代码复用率高 | 只能够代理实现了接口的委托类 | 底层使用反射机制进行方法的调用 |
CGLIB动态代理 | 代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了MethodInterceptor接口的对象,若存在则将调用intercept方法对委托方法进行代理 | 可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口 | 不能对final类以及final方法进行代理 | 底层将方法全部存入一个数组中,通过数组索引直接进行方法调用 |
如上表,静态代理需要代理类与目标类实现同一接口。
统一接口类Person,Man类实现Person接口;静态代理类StaticManProxy对Man类进行增强;即代理。
public interface Person{
String eat(String food);
String sleep(String where);
}
public class Man implements Person{
public String sleep(String name){
System.out.println(name+"睡觉了");
return "";
}
public String eat(String name){
System.out.println(name+"吃饭了");
return "";
}
}
静态代理类需要与目标类的接口,即StaticManProxy同样需要实现Person
public class StaticManProxy implements Person{
private Person target;
public StaticManProxy(Person target){
this.target=target;
}
public String eat(String name) {
System.out.println("静态代理 do something~");
target.eat(name);
return null;
}
public String sleep(String name) {
return null;
}
}
public class ProxyTest {
public static void main(String[] args){
//1、静态代理
Person person=new Man();
StaticManProxy manProxy=new StaticManProxy(person);
manProxy.eat("静态代理");
}
}
静态代理 do something~
静态代理吃饭了
如上StaticManProxy 即为Man的代理类,同时为其他实现Person接口的代理类;
JDK动态代理通过反射实现,直接使用JDK的java.lang.reflect.Proxy.newProxyInstance方法生成代理;
实现代码如下
public class ProxyFactory {
// 代理目标
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxy() {
return Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(),
target.getClass().getInterfaces(),
/**
* InvocationHandler接口只定义了一个invoke方法,因此对于这样的接口,
* 我们不用单独去定义一个类来实现该接口, 而是直接使用一个匿名内部类来实现该接口,new
* InvocationHandler() {}就是针对InvocationHandler接口的匿名实现类
*/
/**
* 在invoke方法编码指定返回的代理对象干的工作 proxy : 把代理对象自己传递进来 method:
* 把代理对象当前调用的方法传递进来 args: 把方法参数传递进来
*
* 当调用代理对象的方法时,
* 实际上执行的都是invoke方法里面的代码,
* 因此我们可以在invoke方法中使用method.getName()就可以知道当前调用的是代理对象的哪个方法
*/
new InvocationHandler() {
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
System.out.println("动态代理do something");
return method.invoke(target, args);
}
});
}
public Object getProxyByLambda() {
return Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(),
target.getClass().getInterfaces(), (proxy, method, args) -> {
System.out.println("Lambda动态代理do something");
return method.invoke(target, args);
});
}
}
测试代码段:
ProxyFactory proxy=new ProxyFactory(man);
//2、匿名函数 模式
Person p1=(Person)proxy.getProxy();
System.out.println(p1.eat("匿名函数"));
//3、Lambda 模式
Person p=(Person)proxy.getProxyByLambda();
System.out.println(p.eat("Lambda模式"));
结果
动态代理do something
匿名函数吃饭了
Lambda动态代理do something
Lambda模式吃饭了
如上,JDK动态代理通过Proxy.newProxyInstance直接生成代理对象,如需通过代理实现事物处理或者日志添加,则可通过以上代理类直接在代理Factory中增加对应需要处理的逻辑即可,代码调用时只需要将目标类传入则实现了对应的增强功能。针对所有接口实现类通用。
Spring中继承了CGLIB代理相关的代码,所以实现CGLIB代理只需要导入spring-core的jar包即可。
CGLIB代理的目标类不需要实现任何接口;
public class CgLibMan{
public String eat(String name){
System.out.println(name+"吃饭了");
return "";
}
public String sleep(String name){
System.out.println(name+"睡觉了");
return "";
}
}
CGLIB代理实现类需要实现MethodInterceptor接口。如下:
public class CgLibProxyFactory implements MethodInterceptor{
//维护目标对象
private Object target;
public CgLibProxyFactory(Object target){
this.target=target;
}
//给目标对象创建一个代理对象
public Object getCgLibProxy(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("开始事务...");
//通过反射 执行目标对象的方法
Object returnValue = method.invoke(target, args);
proxy.invokeSuper(o, args);
System.out.println("提交事务...");
return returnValue;
}
}
测试代码段:
//4、Cglib实现代理
//目标对象
CgLibMan ldhCg=new CgLibMan();
//代理对象
CgLibMan proxyCg=(CgLibMan) new CgLibProxyFactory(ldhCg).getCgLibProxy();
proxyCg.eat("Cglib");