AOP-动态代理-反射

1、什么是反射机制呢?

2、什么是代理模式呢?

3、静态代理和动态代理的区别是什么呢?


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实现动态代理

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实现动态代理基本代码:

  1. 声明一个被代理接口以及被代理的方法
    public interface Hello { //被代理接口
        void sayHello();//被代理方法
    }
  2. 实现被代理接口,实现被代理方法
    public class HelloImpl implements  Hello { // 实现接口
        @Override
        public void sayHello() {
            // 你要写的业务代码
            System.out.println("Hello World");
        }
    }
  3. 实现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;
        }
    }
  4. 使用动态代理
    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();
        }
    }
  5. 步骤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实现动态代理基本代码:

  1.  声明一个被代理接口以及被代理的方法
    public interface Hello { //被代理接口
        void sayHello();//被代理方法
    }
  2. 实现被代理接口,实现被代理方法
    public class HelloImpl implements  Hello { // 实现接口
        @Override
        public void sayHello() {
            // 你要写的业务代码
            System.out.println("Hello World");
        }
    }
  3. 实现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;
        }
    }
  4. 使用动态代理
    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 }

你可能感兴趣的:(JAVA面试,java,代理模式,后端)