java面试常问:AOP 如何实现?动态代理是基于什么原理实现的?
简单回答:AOP通过动态代理实现,动态代理基于反射机制实现。
既然要搞清楚AOP的实现,那么我们就搞清楚动态代理的具体原理。要了解动态代理,那我们必须要谈的就是java的反射机制。
1、什么是反射机制呢?
反射机制是Java语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。
2、什么是代理模式呢?
代理模式 可以详细控制访问某个(某类)对象的方法,在调用这个方法前做前置处理或者调用这个方法后做一些后置处理。从而实现将统一流程代码放到代理类中处理。代理模式又分为静态代理和动态代理
简单来说就是在你主要的业务功能实现前、后帮你做一些事情。
例如:
1、你写了一个方法a去查询数据库数据,在你的方法a执行前需要进行数据库连接操作,在执行后需要关闭数据库连接。在你使用mybatis的时候mybatis会帮你去进行连接和关闭连接的操作。而你只需要写查询数据库数据的方法,这就是代理
2、rpc调用的时候,代理类会帮我们寻找下游服务器并建立连接,并且调用结束后帮我们关闭连接。
3、静态代理和动态代理的区别是什么呢?
- 静态代理:自己写代理类,去实现某个指定的类的某个方法的代理,没有任何扩展性。
- 动态代理:动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制。一个代理类可以服务于所有的业务对象。
4、动态代理主要的实现方式和区别?
- 实现动态代理的方式很多,比如JDK自身提供的动态代理,就是主要利用了反射机制。还有其他的实现方式,比如利用传说中更高性能的字节码操作机制,类似ASM、cglib(基于ASM)、Javassist等。
- JDK和cglib实现动态代理的区别
- JDK实现动态代理主要是以接口为中心。JDK提供的代理类,会实现被代理对象的接口。
- cglib主要原理是为被代理类生成子类,通过继承被代理对象的方法去实现动态代理
- 如果对象有接口实现,选择JDK代理,如果没有接口实现选择CGILB代理
- JDK和cglib实现动态代理的各自优势
- JDK:
- 最小化依赖关系,减少依赖意味着简化开发和维护,JDK本身的支持,可能比cglib更加可靠。
- 平滑进行JDK版本升级,而字节码类库通常需要进行更新以保证在新版Java上能够使用。
- 代码实现简单。
- cglib:
- 有的时候调用目标可能不便实现额外接口,从某种角度看,限定调用者实现接口是有些侵入性的实践,类似cglib动态代理就没有这种限制。
- 只操作我们关心的类,而不必为其他相关类增加工作量。
- cglib性能高于JDK。
- 被final修饰的方法,无法通过cglib实现动态代理
- JDK:
5、AOP的动态代理是怎么实现的?
- Spring 中的 AOP,有接口就用 JDK 动态代理,没有接口就用 Cglib 动态代理;并优先使用 JDK 动态代理
- SpringBoot 中的 AOP,在 2.0 版本之前和 Spring 一样
- SpringBoot 中的 AOP,在 2.0 版本之后首选 Cglib 动态代理,如果用户想要使用 JDK 动态代理,需要自己手动配置
- 参考链接:SpringBoot中的Aop优先使用的是JDK动态代理还是Cglib_桐花思雨的博客-CSDN博客_spring默认使用jdk动态代理
6、知识扩展1-JDK实现动态代理基本代码:
- 声明一个被代理接口以及被代理的方法
public interface Hello { //被代理接口 void sayHello();//被代理方法 }
- 实现被代理接口,实现被代理方法
public class HelloImpl implements Hello { // 实现接口 @Override public void sayHello() { // 你要写的业务代码 System.out.println("Hello World"); } }
- 实现InvocationHandler接口,写你要做的代理操作
public class MyInvocationHandler implements InvocationHandler { private Object target; public MyInvocationHandler(Object target) { //暂时存放target this.target = target; } // 实现InvocationHandler中的invoke方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // method就是被代理对象的被代理方法 args是执行被代理方法所需的参数 // proxy System.out.println("Invoking sayHello"); Object result = method.invoke(target, args);// 这里的target就是被代理对象 return result; } }
- 使用动态代理
public class AA { public static void main (String[] args) { //创建被代理对象 HelloImpl hello = new HelloImpl(); // 创建代理(hello就是步骤3中的target) MyInvocationHandler handler = new MyInvocationHandler(hello); // 构造代码实例 Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), handler); // 调用代理方法 proxyHello.sayHello(); } }
- 步骤3和4还可以合成一步(使用lamda表达式)
public class AA { public static void main(String[] args) { // 创建被代理对象 HelloImpl hello = new HelloImpl(); // 构造代码实例 Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(),(p,method,param)->{ System.out.println("Invoking sayHello"); Object result = method.invoke(target,param); return result; }); // 调用代理方法 proxyHello.sayHello(); } }
7、知识扩展2-cglib实现动态代理基本代码:
- 声明一个被代理接口以及被代理的方法
public interface Hello { //被代理接口 void sayHello();//被代理方法 }
- 实现被代理接口,实现被代理方法
public class HelloImpl implements Hello { // 实现接口 @Override public void sayHello() { // 你要写的业务代码 System.out.println("Hello World"); } }
- 实现MethodInterceptor接口,写你要做的代理操作
public class MyMethodInterceptor implements MethodInterceptor { private Object target; public MyMethodInterceptor(Object target) { //暂时存放target this.target = target; } /** * 实现MethodInterceptor中的intercept方法 * 参数 o: cglib 动态生成的代理类的实例 * method:实体类所调用的都被代理的方法的引用 * objects 参数列表 * methodProxy:生成的代理类对方法的代理引用 **/ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("Invoking sayHello"); Object object= methodProxy.invoke(target,objects); System.out.println("Destory sayHello"); return object; } }
- 使用动态代理
public class AA { public static void main(String[] args) { //创建被代理对象 HelloImpl hello = new HelloImpl(); //创建代理类 Cglib cglib=new Cglib(hello); //可以通过Enhancer对象中的create()方法可以去生成一个类,用于生成代理对象 Enhancer enhancer=new Enhancer(); enhancer.setSuperclass(hello.getClass());//设置父类(将被代理类作为代理类的父类) enhancer.setCallback(cglib);//设置拦截器(代理类中的intercept就是) Hello proxyHello = (Hello) enhancer.create();//生成一个被代理类的子类对象 // 调用代理方法 proxyHello.sayHello(); } }
8、知识扩展3-反射机制及其演进
反射提供的AccessibleObject.setAccessible(boolean flag)。它的子类也大都重写了这个方法,这里的所谓accessible可以理解成修饰成员的public、protected、private,这意味着我们可以在运行时修改成员访问限制!
setAccessible的应用场景非常普遍,遍布我们的日常开发、测试、依赖注入等各种框架中。比如,在O/R Mapping框架中,我们为一个Java实体对象,运行时自动生成setter、getter的逻辑,这是加载或者持久化数据非常必要的,框架通常可以利用反射做这个事情,而不需要开发者手动写类似的重复代码。
另一个典型场景就是绕过API访问控制。我们日常开发时可能被迫要调用内部API去做些事情,比如,自定义的高性能NIO框架需要显式地释放DirectBuffer,使用反射绕开限制是一种常见办法。
但是,在Java 9以后,这个方法的使用可能会存在一些争议,因为Jigsaw项目新增的模块化系统,出于强封装性的考虑,对反射访问进行了限制。Jigsaw引入了所谓Open的概念,只有当被反射操作的模块和指定的包对反射调用者模块Open,才能使用setAccessible;否则,被认为是不合法(illegal)操作。如果我们的实体类是定义在模块里面,我们需要在模块描述符中明确声明:
module MyEntities {
// Open for reflection
opens com.mycorp to java.persistence;
}
因为反射机制使用广泛,根据社区讨论,目前,Java 9仍然保留了兼容Java 8的行为,但是很有可能在未来版本,完全启用前面提到的针对setAccessible的限制,即只有当被反射操作的模块和指定的包对反射调用者模块Open,才能使用setAccessible,我们可以使用下面参数显式设置。
--illegal-access={ permit | warn | deny }