设计模式------Proxy
文章目录
什么是代理?
增强一个对象的功能
买火车票,app就是一个代理,代理了火车站的售票处
Java中如何实现代理?
Java实现代理的两种办法:静态代理和动态代理
代理的名词
代理对象: 增强后的对象
目标对象: 被增强的对象
他们的身份不是绝对的,会根据情况发生变化
静态代理
继承
代理对象继承目标对象,重写需要增强的方法。
缺点:代理类过多,复杂
public class LogExtendsTime extends Time {
@Override
public void query(){
/** ... 一些log相关的代理方法 */
super.query();
}
}
聚合
目标对象和代理对象实现同一个接口,代理对象当中要包含目标对象。
缺点:也会产生类爆炸,只不过比继承少
public class Test {
public static void main(String[] args) {
// 实现了本来的功能 + log
Service target = new LogService(new ServiceImpl);
// 又添加了time功能
Service proxy = new TimeService(target);
proxy.query();
}
}
总结:如果在不确定的情况下,尽量不要使用静态代理。
动态代理
静态代理会为每一个业务增强都提供一个代理类, 由代理类来创建代理对象, 而动态代理并不存在代理类, 代理对象直接由代理生成工具动态生成.
原文:https://blog.youkuaiyun.com/yhl_jxy/article/details/80586785
JDK动态代理
JDK动态代理基于拦截器和反射来实现,JDK代理不需要第三方库支持,只需要JDK环境就可以进行代理。JDK动态代理是基于接口的方式,换句话来说就是代理类和目标类都实现同一个接口,那么代理类和目标类的方法名就一样了
使用条件:
1)必须实现InvocationHandler接口;
2)使用Proxy.newProxyInstance产生代理对象;
3)被代理的对象必须要实现接口
使用JDK动态代理的五大步骤:
1)通过实现InvocationHandler接口来自定义自己的InvocationHandler;
2)通过Proxy.getProxyClass获得动态代理类;
3)通过反射机制获得代理类的构造方法,方法签名为getConstructor(InvocationHandler.class);
4)通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入;
5)通过代理对象调用目标方法
//接口
public interface IHello {
void sayHello();
}
//接口实现
public class HelloImpl implements IHello {
@Override
public void sayHello() {
System.out.println("Hello world!");
}
}
//实现InvocationHandler接口,继承InvocationHandler的invoke方法。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
/** 目标对象 */
private Object target;
public MyInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------插入前置通知代码-------------");
// 执行相应的目标方法
Object rs = method.invoke(target,args);
System.out.println("------插入后置处理代码-------------");
return rs;
}
}
//代理类
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
/**
* 使用JDK动态代理的五大步骤:
* 1.通过实现InvocationHandler接口来自定义自己的InvocationHandler;
* 2.通过Proxy.getProxyClass获得动态代理类
* 3.通过反射机制获得代理类的构造方法,方法签名为getConstructor(InvocationHandler.class)
* 4.通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入
* 5.通过代理对象调用目标方法
*/
public class MyProxyTest {
public static void main(String[] args)
throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
// =========================第一种==========================
// 1、生成$Proxy0的class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 2、获取动态代理类
Class proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);
// 3、获得代理类的构造函数,并传入参数类型InvocationHandler.class
Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
// 4、通过构造函数来创建动态代理对象,将自定义的InvocationHandler实例传入
IHello iHello1 = (IHello) constructor.newInstance(new MyInvocationHandler(new HelloImpl()));
// 5、通过代理对象调用目标方法
iHello1.sayHello();
// ==========================第二种=============================
/**
* Proxy类中还有个将2~4步骤封装好的简便方法来创建动态代理对象,
*其方法签名为:newProxyInstance(ClassLoader loader,Class<?>[] instance, InvocationHandler h)
*/
IHello iHello2 = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), // 加载接口的类加载器
new Class[]{IHello.class}, // 一组接口
new MyInvocationHandler(new HelloImpl())); // 自定义的InvocationHandler
iHello2.sayHello();
}
}
CGLIB动态代理
CGLib动态代理是代理类去继承目标类,然后重写其中目标类的方法啊,这样也可以保证代理类拥有目标类的同名方法;
JDK动态代理必须要有接口, 但如果要代理一个没有接口的类该怎么办呢? 这时我们可以使用CGLIB动态代理. CGLIB动态代理的原理是生成目标类的子类, 这个子类对象就是代理对象, 代理对象是被增强过的.Cglib是无法代理final修饰的方法的,因为final类型的方法无法继承。
注意: 不管有没有接口都可以使用CGLIB动态代理, 而不是只有在无接口的情况下才能使用.
//没有接口的业务类情况下,使用CGLIB动态代理
public class HelloService {
public HelloService() {
System.out.println("HelloService构造");
}
/**
* 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
*/
final public String sayOthers(String name) {
System.out.println("HelloService:sayOthers>>"+name);
return null;
}
public void sayHello() {
System.out.println("HelloService:sayHello");
}
}
//定义一个MyMethodInterceptor实现cglib的MethodInterceptor接口
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 自定义MethodInterceptor
*/
public class MyMethodInterceptor implements MethodInterceptor{
/**
* sub:cglib生成的代理对象
* method:被代理对象方法
* objects:方法入参
* methodProxy: 代理方法
*/
@Override
public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("======插入前置通知======");
Object object = methodProxy.invokeSuper(sub, objects);
System.out.println("======插入后者通知======");
return object;
}
}
//生成代理对象,调用目标方法
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
public class Client {
public static void main(String[] args) {
// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(HelloService.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
HelloService proxy= (HelloService)enhancer.create();
// 通过代理对象调用目标方法
proxy.sayHello();
}
}
JDK和CGLIB动态代理区别
1.何时使用jdk还是cglib
1)如果目标对象或Bean实现了接口,默认情况下会采用JDK的动态代理实现AOP
2)如果目标对象或Bean实现了接口,可以强制使用CGLIB实现AOP
3)如果目标对象或Bean没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换
2.如何强制使用cglib
1)添加CGLIB库(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar)
2)在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>
3.区别
1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法, 并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final, 对于final类或方法,是无法继承的
4.为什么继承只能使用CGLib
因为JDK代理生成的代理类,默认会继承一个Proxy类,由于java是单继承,所以当原始类继承一个类的时候,只能使用CGLib动态代理