Java proxy 代理java proxy server
目录
- 静态代理
- jdk动态代理
- cglib动态代理
- 静态代理、动态代理的区别
- jdk动态代理、cglib代理的区别
- 代理与目标类的关系
- 使用建议
proxy 代理:在目标类的基础上进行扩展,做一些额外处理,增加一些额外功能,代替目标类进行工作。
静态代理
/** * 静态代理 */ public class UserServiceProxy implements UserService { /** * 把目标接口实例作为成员变量 */ private UserService userService; /** * 提供构造方法或者setter方法注入目标接口实例 */ public UserServiceProxy(UserService userService) { this.userService = userService; } /** * 依次为:前增强、调用目标方法、后增强,前、后增强可选 */ @Override public void login() { System.out.println("前增强..."); userService.login(); System.out.println("后增强..."); } /** * 如果不需要增强,直接调用目标方法即可 */ @Override public void register() { userService.register(); } }//目标对象 UserService userServiceImpl = new UserServiceImpl(); //代理 UserService userServiceProxy = new UserServiceProxy(userServiceImpl); //使用代理代替目标对象进行工作 userServiceProxy.login();
核心点
- 代理需要实现目标接口或继承目标类:以便重写目标接口、目标类中的方法,代替目标方法进行工作
- 注入目标实例:可以通过带参构造器,或者无参构造器+setter方法进行注入
- 重写目标方法
jdk动态代理
/** * jdk动态代理 */ public class UserServiceProxyFactory { /** * 传入目标实例,获取代理 * jdk动态代理只能代理接口,所以声明的目标类型只能是目标接口 */ public static UserService getProxyInstance(UserService userService) { //获取目标的类加载器、接口 ClassLoader classLoader = userService.getClass().getClassLoader(); //因为要getInterfaces()获取接口,所以只能代理接口,目标必须要是实现了接口的 Class<?>[] interfaces = userService.getClass().getInterfaces(); //使用lambda表达式实现InvocationHandler接口,编写增强 //参数依次为:Object proxy 将返回的代理, Method method 目标方法, Object[] args 实参表 InvocationHandler invocationHandler = (proxy, method, args) -> { System.out.println("前增强..."); //调用目标方法,传入目标实例、实参表,returnValue是目标方法的返回值 Object returnValue = method.invoke(userService, args); System.out.println("后增强..."); //返回目标方法的返回值 return returnValue; }; //创建代理 Object userDaoProxy = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); //返回值是Object,强转为目标类型返回 return (UserService) userDaoProxy; } }//目标实例 UserService userServiceImpl = new UserServiceImpl(); //代理 UserService userServiceProxy = UserServiceProxyFactory.getProxyInstance(userServiceImpl); //使用代理代替目标对象进行工作 userServiceProxy.login();
核心点
- 只能代理接口:这句话的意思是说声明的目标类型只能是接口、不能是类,可以代理目标接口的所有实例,只要目标继承了接口就可以被代理。
- 不能代理 static 方法
- 不需要实现目标接口或继承目标类
- 需要把Object型的代理强转为目标接口类型返回:不然代理是Object类型,userServiceProxy.login() 通过代理调用目标方法,都通不过编译
看invoke()的参数,在编译时不能确定要增强的方法,运行时根据传入的参数进行增强,动态生成代理,所以叫做动态代理;静态代理在编译时就确定了要增强的方法。
使用jdk自带的反射机制实现的动态代理,叫做jdk动态代理;使用第三方的cglib库实现的代理叫做cglib代理。
cglib动态代理
需要添加cglib的依赖
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>/** * cglib代理 */ public class UserServiceProxyFactory implements MethodInterceptor { /** * 目标实例,可以声明为目标接口或目标类 */ private UserService userService; /** * 注入目标实例 */ public UserServiceProxyFactory(UserService userService) { this.userService = userService; } /** * 拦截目标方法进行增强 */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("前增强..."); //调用目标方法,传入目标实例、实参表,returnValue是目标方法的返回值 Object returnValue = method.invoke(userService, objects); System.out.println("后增强..."); //返回目标方法的返回值 return returnValue; } /** * 获取代理,返回值是目标类型 */ public UserService getProxyInstance() { //Enhancer是操作字节码的工具类 Enhancer en = new Enhancer(); //设置基类,即会继承目标类 en.setSuperclass(userService.getClass()); //设置回调,即使用目标对象的方法时会使用当前对象(代理)的intercept()进行拦截,代替目标方法 en.setCallback(this); //创建代理,强转为目标类型返回 return (UserService) en.create(); } }//目标实例 UserService userServiceImpl = new UserServiceImpl(); //获取代理 UserService userServiceProxy = new UserServiceProxyFactory(userServiceImpl).getProxyInstance(); //使用代理代替目标对象进行工作 userServiceProxy.login();
核心点
- 需要引入cglib的依赖
- 可以代理接口、类,返回的代理属于目标接口、目标类的实例
- 需要实现MethodInterceptor接口,注入目标实例
cglib代理的实现原理
- 借助ASM(一个短小精悍的字节码操作框架),在运行时动态修改目标类的字节码,根据修改后的字节码创建代理。并非修改.class文件中的字节码,只是修改jvm加载到内存中Class对象,本质仍是基于字节码操作,但织入时机是运行时。
- 修改字节码时要setSuperclass()继承目标类,基于继承目标类实现,所以不能代理 final 类,不能代理 private 方法、static方法。
静态代理、动态代理的区别
静态代理
- 可以代理接口、类,在编译时就确定了要增强的方法
- 需要编写代理类,代理是一个新的类,编译会生成代理类的.class文件;
- 代理需要实现目标接口或继承目标类,代理本身属于目标接口、目标类的实例;
- 优点是简单、灵活,缺点是代码侵入强、耦合度高,编码量大,不好维护
动态代理
- 不需要实现目标接口或继承目标类,在运行时根据传入的参数动态生成代理
- 参数中包含了目标实例、Method、实参表,可根据这些参数对目标类、目标方法进行筛选
- 优点是编码量小,对代码侵入小,好维护;缺点是性能比静态代理差。
jdk动态代理、cglib代理的区别
jdk动态代理
- 基于jdk自带的反射机制实现,不需要引入第三方依赖
- 只能代理接口,目标实现了接口才能被代理;
- 不能代理 static 方法
- 不需要实现目标接口或继承目标类,但需要把创建的Object型的代理强转为目标接口类型返回
cglib代理
- 需要引入cglib的依赖
- 可以代理接口、类(不要求目标实现接口)
- 基于运行时动态修改字节码、继承目标类实现,不能代理 final 类,不能代理 private 方法、static方法
代理与目标类的关系
- 静态代理:需要实现目标接口或继承目标类,代理属于目标接口|类的实例
- cglib代理基于继承目标类实现,代理自然也是目标类的实例
- jdk动态代理:虽然没有实现目标接口或继承目标类,但会把Object型的代理转换为目标接口类型,所以也是目标接口的实例。
3种代理都属于目标类型的实例。
使用建议
静态代理、cglib动态代理都可以代理接口、类,jdk动态代理只能代理接口。
如果要针对目标类中的众多方法设置不同的增强,用静态代理,静态代理只能小范围使用,不然重复代码多、不好维护。
如果对目标类中的方法的增强逻辑都一样,用动态代理,实现了目标实现了接口,用jdk动态代理、cglib代理均可,如果目标没有实现接口,则用cglib代理。
正向代理 forward proxy、反向代理 reverse proxy、透明代理 transparent proxy反向代理与正向代理
Charles——charles代理菜单proxy总结——代理设置proxy settingCharles Proxy
【java项目实战】代理模式(Proxy Pattern),静态代理 VS 动态代理java proxy 动态代理
什么是正向代理(forward proxy)和反向代理(reverse proxy)反向代理与正向代理
反向代理Reverse proxyreverse proxy
反向代理(Reverse Proxy)reverse proxy
linux proxy 【linux 代理】linux proxy server