什么是代理模式?
代理(Proxy)是一种设计模式,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
优点
- 职责清晰:真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
- 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
- 高扩展性
代理模式分为静态代理、动态代理
静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
静态代理
例子
目标接口:
public interface Login {
//登录
void login();
}
目标对象:
public class LoginImpl implements Login {
//实现登录方法
public void login() {
System.out.println("----正在上线----");
}
}
代理对象:
public class LoginProxy implements Login{
//接收保存目标对象
private Login target;
public LoginProxy(Login target){
this.target=target;
}
public void save() {
System.out.println("---开始校验---");
target.login();//执行目标对象的方法
System.out.println("---成功上线---");
}
}
静态代理总结:
- 优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
- 缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
动态代理
JDK动态代理
利用JDK的API,动态的在内存中构建代理对象,采用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
该方法是在Proxy类中是静态方法,且接收的三个参数依次为:
- ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的
- Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型
- InvocationHandler :事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
实例: 代理类
public class LoginProxyHandler implements InvocationHandler {
private Object target;
public Object getInstance(final Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
/**
* 当用户调用对象中的每个方法时都通过下面的方法执行
* proxy 被代理后的对象
* method 将要被执行的方法信息(反射)
* args 执行方法时需要的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---登录前校验---");
Object result = method.invoke(target, args);
System.out.println("---登录后动作---");
return result;
}
}
测试方法
public class LoginProxyTest {
public static void main(String[] args) {
Login login = new LoginImpl();
Login loginproxy = (Login) new LoginProxyHandler().getInstance(login);
loginproxy.login();
}
}
CGLiB动态代理
利用字节码处理框架ASM开源包,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。
例子
被代理对象(无接口)
public class LoginImpl{
public void login() {
System.out.println("---正在登录---");
}
}
代理类
public class LoginCglib implements MethodInterceptor {
private Object target;
//相当于JDK动态代理中的绑定
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer(); //创建加强器,用来创建动态代理类
enhancer.setSuperclass(this.target.getClass()); //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
//设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
enhancer.setCallback(this);
// 创建动态代理类对象并返回
return enhancer.create();
}
// 实现回调方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("---登录前校验---");
proxy.invokeSuper(obj, args); //调用业务类(父类中)的方法
System.out.println("---登录后动作---");
return null;
}
测试类
public class CglibTest {
public static void main(String[] args){
LoginImpl login = new LoginImpl();
LoginImpl loginProxy = (LoginImpl)new LoginCglib().getInstance(login);
loginProxy.login();
}
}
JDK和CGLIB动态代理对比
JDK动态代理和CGLIB字节码生成的区别?
- JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
- CGLIB是针对类实现代理,主要是动态生成被代理类的子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final, 对于final类或方法,是无法继承的。
CGlib比JDK快?
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。 在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。
在spring aop中何时使用?
- 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
- 如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
- 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。