最近在研究java 里面的代理模式,有一些自己的感悟,在此跟大家分享一下。
一、代理的本质:
自己不想做或不放方便做的事情,交给代理来做,这样自己就可以安心做好自己的本质工作了。
二、代理的意义:
1.代理可以实现代码的松耦合,使各个模块各司其职,方便以后扩展和维护。
2.代理还可以,还可以对原有的功能进行扩展和加强,
三、代理的分类:
代理分为静态代理,动态代理(jdk 动态代理、cglib 动态代理)
四、动态代理的应用场景
1.统计每个api 的请求耗时。
2.统一的日志输出。
3.校验被调用的api 是否已登录和权限鉴定。
4.日志、监控、事务
5.框架中的应用:spring 中AOP 的切面编程;RPC 框架中的远程调用。
五、简单demo
1.静态代理
(1)IHuman接口
package com.example.demo.service;
public interface IHuman {
void eat();
void speak();
}
(2)Student 实现IHuman接口
package com.example.demo.po;
import com.example.demo.service.IHuman;
import lombok.extern.slf4j.Slf4j;
/**
* @description:
* @author:houqd
* @time: 2021/7/14 17:22
*/
@Slf4j
public class Student implements IHuman {
@Override
public void eat() {
log.info("student eat...");
}
@Override
public void speak() {
log.info("student speak...");
}
}
(3) Teacher 实现Ihuman 接口
package com.example.demo.po;
import com.example.demo.service.IHuman;
import lombok.extern.slf4j.Slf4j;
/**
* @description:
* @author:houqd
* @time: 2021/7/14 17:23
*/
@Slf4j
public class Teacher implements IHuman {
@Override
public void eat() {
log.info("teacher eat...");
}
@Override
public void speak() {
log.info("teacher speak...");
}
}
(4)测试
package com.example.demo.danamicproxy.jdkproxy;
import com.example.demo.po.Student;
import com.example.demo.service.IHuman;
import lombok.extern.slf4j.Slf4j;
/**
* @description:
* @author:houqd
* @time: 2021/7/14 17:25
*/
@Slf4j
public class ProxyStatic implements IHuman {
private IHuman human;
public ProxyStatic(IHuman human) {
this.human = human;
}
@Override
public void eat() {
log.info("eat before...");
this.human.eat();
log.info("eat after...");
}
@Override
public void speak() {
}
public static void main(String[] args) {
ProxyStatic proxyStatic = new ProxyStatic(new Student());
proxyStatic.eat();
}
}
2.jdk 动态代理
JDK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的,但是JDK中所有要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中有一定的局限性,而且使用反射的效率也不高
(1)实现InvocationHandler接口,及获取代理实例
package com.example.demo.danamicproxy.jdkproxy;
import com.example.demo.service.IHuman;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @description:
* @author:houqd
* @time: 2021/7/14 17:45
*/
@Slf4j
public class JdkDynamicInvocationhandler implements InvocationHandler {
private Object target;
public JdkDynamicInvocationhandler(Object target){
this.target = target;
}
/**
*
* @param proxy 通过类加载器和接口生成的代理对象
* @param method 被代理的方法
* @param args 被代理的方法参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("Do something Before");
Object invoke = method.invoke(target, args);// 通过java 反射实现对目标类的调用
log.info("Do something after");
return invoke;
}
/**
* 获取代理实例:
* 第一个参数为类加载器,
* 第二个参数为目标类接口,
* 第三个参数为InvocationHandler接口的自定义实现
* @param <T>
* @return
*/
public <T> T getProxy(){
return (T) Proxy.newProxyInstance(IHuman.class.getClassLoader(),target.getClass().getInterfaces(),this);
}
}
(2)测试
package com.example.demo.danamicproxy.jdkproxy;
import com.example.demo.po.Student;
import com.example.demo.po.Teacher;
import com.example.demo.service.IHuman;
/**
* @description:
* @author:houqd
* @time: 2021/7/14 17:52
*/
public class TestDynamic {
public static void main(String[] args) {
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
IHuman proxy = new JdkDynamicInvocationhandler(new Student()).getProxy(); // 必须用接口来接收,用类不可以
proxy.eat();
IHuman proxy2 = new JdkDynamicInvocationhandler(new Teacher()).getProxy();// 必须用接口来接收,用类不可以
proxy2.eat();
}
}
3.cglib 动态代理
使用cglib是实现动态代理,不受代理类必须实现接口的限制,因为cglib底层是用ASM框架,使用字节码技术生成代理类,你使用Java反射的效率要高,cglib不能对声明final的方法进行代理,因为cglib原理是动态生成被代理类的子类
(1)实现MethodInterceptor 拦截器
package com.example.demo.danamicproxy.cglib;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @description: 通过实现方法拦截器,来拦截父类方法,同时从切入点织入其他方法来增强功能
* @author:houqd
* @time: 2021/7/15 11:03
*/
public class TargetInterceptor implements MethodInterceptor {
/**
*
* @param obj 代理类对象
* @param method 当前被代理拦截的方法
* @param args 拦截方法的参数
* @param methodProxy 代理类对应目标类的代理方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib 代理前。。。");
Object result = methodProxy.invokeSuper(obj, args);//
System.out.println("Cglib 代理后。。。");
return result;
}
}
(2)生成cglib代理子类
package com.example.demo.danamicproxy.cglib;
import org.springframework.cglib.proxy.Enhancer;
/**
* @description: 生成Cglib 代理子类
* @author:houqd
* @time: 2021/7/15 11:02
*/
public class CglibProxy {
public static Object getProxy(Class<?> clazz){
// 通过Enhancer 来生成代理子类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new TargetInterceptor());
return enhancer.create();
}
}
(3)测试
package com.example.demo.danamicproxy.cglib;
import com.example.demo.po.Student;
import com.example.demo.po.Teacher;
import com.example.demo.service.IHuman;
/**
* @description:
* @author:houqd
* @time: 2021/7/15 11:15
*/
public class TestCglibProxy {
public static void main(String[] args) {
// 可以代理接口
IHuman human = (IHuman) CglibProxy.getProxy(Student.class);
human.eat();
// 也可以代理类(但是类必须是非fianl,被代理方法也是fianl)
Teacher teacher = (Teacher) CglibProxy.getProxy(Teacher.class);
teacher.eat();
}
}
六、小结
1.jdk 动态代理:
(1)代理对象:只能对实现接口的类进行代理。
(2)执行效率:jdk1.8 之前 不如cglib 快,jdk1.8做了优化甚至比cglib 还快。
(3)Spring 框架默认使用jdk 动态代理,当该类没有实现接口时转为cglib 动态代理。
(4)实现原理:
①通过实现 InvocationHandler 接口创建自己的调用处理器;
通过classLoader 和接口 ,生成代理类class。
②通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
③通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
④通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
2.cglib 动态代理:
(1)代理对象:对类实没实现接口不在意,只要该类是非fianl 类型,方法是非fianl的即可。
(2) CGLIB的核心类:
net.sf.cglib.proxy.Enhancer – 主要的增强类
net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是 Callback接口的子接口,需要用户实现
net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用。
net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法
public Object intercept(Object object, java.lang.reflect.Method method,
Object[] args, MethodProxy proxy) throws Throwable;
第一个参数是代理对像,第二和第三个参数分别是拦截的方法和方法的参数。原来的方法可能通过使用java.lang.reflect.Method对象的一般反射调用,或者使用 net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy通常被首选使用,因为它更快。
(3)实现原理:
cglib是一个java字节码的生成工具,它动态生成一个被代理类的子类,子类重写被代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。