写点什么

面试官问我:什么是静态代理?什么是动态代理?注解、反射你会吗?

用户头像
Java鱼仔
关注
发布于: 2021 年 01 月 17 日

听说微信搜索《Java 鱼仔》会变更强哦!

本文收录于https://github.com/OliverLiy/JavaStarter,里面有我完整的 Java 系列文章,学习或面试都可以看看哦


开场

一位穿着蓝色衬衫,牛仔裤,拿着一个白色保温杯的中年男子急匆匆地坐在你对面,看样子是项目上的东西很急,估摸面试时间不会太长,这样一想心情放松了许多......(后来我就被打脸了)


面试开始

面试官:小伙子,我看你的简历上说精通 java 基础对吧,那我先简单来问几个 java 基础。

好的好的,面试官你问。(一听到简单两个字就内心窃喜......)


面试官:你知道 Java 中有个东西叫代理吗?

知道知道,代理就是通过代理对象去访问实际的目标对象,比如我们在生活中租房,可以直接找房东,也可以通过某些租房平台去租房,通过租房平台的这种方式就是代理。在 java 中这种租房平台就被叫做代理类,代理类不仅能实现目标对象,还能增加一些额外的功能。据我所知 java 中的代理方式有静态代理和动态代理。(这个时候面试官很大概率会问你这两种代理模式)。


面试官:没想到你还能通过生活中的现象去理解代码,不错不错,我看你提到了静态代理和动态代理,那你给我说说什么是静态代理吧

(果然问了,还好我做了准备)静态代理就是在代码运行之前,这个代理类就已经存在了。还是以上面的租房为例,在代码中会首先创建一个通用的租房接口:

public interface Room {    void rent();}
复制代码


然后需要有一个被代理的类(或者称为真实的类)和一个代理类:

public class RealRoom implements Room {    private String roomname;    public RealRoom(String roomname) {        this.roomname = roomname;    }    public void rent() {        System.out.println("租了"+roomname);    }}
复制代码


代理类如下:

public class ProxyClass implements Room {    RealRoom realRoom;    public ProxyClass(RealRoom realRoom) {        this.realRoom = realRoom;    }    public void rent() {        System.out.println("租房前收取中介费");        realRoom.rent();        System.out.println("租房后收取服务费");    }}
复制代码


代理类可以在不改变被代理对象的情况下增加功能,最后我们测试一下这个静态代理:

public class Main {    public static void main(String[] args) {        RealRoom realRoom =new RealRoom("碧桂园");        ProxyClass proxyClass=new ProxyClass(realRoom);        proxyClass.rent();    }}
复制代码


然后观察结果:

租房前收取中介费租了碧桂园租房后收取服务费
复制代码


面试官:既然静态代理那么强大,那他有什么缺点吗?

由于静态代理在代码运行之前就已经存在代理类,因此对于每一个代理对象都需要建一个代理类去代理,当需要代理的对象很多时就需要创建很多的代理类,严重降低程序的可维护性。用动态代理就可以解决这个问题。


面试官:那你给我讲一讲动态代理吧

动态代理是指代理类不是写在代码中,而是在运行过程中产生的,java 提供了两种实现动态代理的方式,分别是基于 Jdk 的动态代理基于 Cglib 的动态代理


面试官:基于 JDK 的动态代理我忘了,你给我复习复习。

(我???算了算了) 实现 Jdk 的动态代理需要实现 InvocationHandler 接口,然后实现其中的 invoke 方法。如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类 invoke,由它实现处理内容。

public class ProxyHandler implements InvocationHandler {    Object object;    public ProxyHandler(Object object) {        this.object = object;    }    //proxy 代理对象    //method 要实现的方法    //args 方法的参数        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        System.out.println("代理执行之前:"+method.getName());        Object invoke = method.invoke(object, args);        System.out.println("代理执行之后:"+method.getName());        return invoke;    }}
复制代码


接下来在 main 方法中执行动态代理

public static void main(String[] args) {    Room room=new RealRoom("碧桂园");    //obj.getClass().getClassLoader()类加载器    //obj.getClass().getInterfaces() 目标类实现的接口    //InvocationHandler对象    InvocationHandler invocationHandler=new ProxyHandler(room);    Room proxyRoom = (Room) Proxy.newProxyInstance(room.getClass().getClassLoader(), room.getClass().getInterfaces(), invocationHandler);    proxyRoom.rent();}
复制代码


这段代码的核心是 Proxy.newProxyInstance,目的是运行期间生成代理类,最后通过代理类执行被代理的方法。最后结果如下:

代理执行之前:rent租了碧桂园代理执行之后:rent
复制代码


面试官:被你这么一说我想起来动态代理了,那他的优势呢?

之前我讲静态代理的时候说静态代理的缺点在于对于每一个被代理的对象,都需要建一个代理类。因为静态代理是在项目运行前就写好的。但是动态代理就不是这样,由于动态代理在运行时才创建代理类,因此只需要写一个动态代理类就好。比如我再创建一个被代理的对象卖房:

写一个通用接口 Sell

public interface Sell {    void sellRoom();}
复制代码


接着还是写一个被代理对象的类:

public class RealSell implements Sell {    public void sellRoom() {        System.out.println("卖房了");    }}
复制代码


接下来在 main 方法中执行动态代理

    public static void main(String[] args) {        Sell sell=new RealSell();        InvocationHandler invocationHandler=new ProxyHandler(sell);        Sell proxysell= (Sell) Proxy.newProxyInstance(sell.getClass().getClassLoader(),sell.getClass().getInterfaces(),invocationHandler);        proxysell.sellRoom();    }
复制代码


最终实现结果如下:

代理执行之前:sellRoom卖房了代理执行之后:sellRoom
复制代码


通过动态代理,我可以通过一个动态代理类,去代理多个对象。


面试官:如果我记的没错,通过这种方式只能代理接口吧,我看你上面的例子也都是代理接口,那我如果想代理类该怎么办呢?

jdk 动态代理确实只能代理接口,JDK 动态代理是基于接口的方式,换句话来说就是代理类和目标类都实现同一个接口。如果想要代理类的话可以使用 CGLib,CGLib 动态代理是代理类去继承目标类,然后实现目标类的方法。

创建一个目标类 CGRoom

public class CGRoom {    public void rent(String roomName){        System.out.println("租了"+roomName);    }}
复制代码


创建 cglib 的动态代理类,继承 MethodInterceptor ,实现其中的 intercept 方法

public class MyMethodInterceptor implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("代理执行之前:"+method.getName()); Object object=methodProxy.invokeSuper(o,objects); System.out.println("代理执行之后:"+method.getName()); return object; }}
复制代码


最后通过 enhance 对象来创建代理类

public static void main(String[] args) {    //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数    Enhancer enhancer=new Enhancer();    //设置目标类的字节码文件    enhancer.setSuperclass(CGRoom.class);    //设置回调函数    enhancer.setCallback(new MyMethodInterceptor());    //创建代理对象    CGRoom proxy= (CGRoom) enhancer.create();    proxy.rent("碧桂园");}
复制代码


最终实现以下结果:

代理执行之前:rent租了碧桂园代理执行之后:rent
复制代码


面试官:既然动态代理被你说的这么牛,那你平常工作中有使用到吗?

平常我的业务代码中虽然几乎没有使用过动态代理,但是我工作中使用的 Spring 系列框架中的 AOP,以及 RPC 框架中都用到了动态代理,以 AOP 为例,AOP 通过动态代理对目标对象进行了增强,比如我们最常用的前置通知、后置通知等。


面试官:不错!下面再考你几个基础,说说你对注解的理解,注解又解决了哪些问题?

Java 语言中的类、方法、变量、参数和包都可以用注解标记,程序运行过程中我们可以获取到相应的注解以及注解中定义的内容,比如说 Spring 中如果检测到说你的类被 @Component 注解标记的话,Spring 容器在启动的时候就会把这个类归为自己管理,这样你就可以通过 @Autowired 注解注入这个对象了。


面试官:那你知道如何自己去定义注解吗?

知道知道,自定义注解主要有以下四步:

第一步通过 @interface 声明注解:

public @interface Myannotation {    String key() default "";}
复制代码


第二步通过四种元注解修饰注解:(面试的时候说出这四种注解就可以了)

元注解的作用就是负责其他注解,java 中一共有四个元注解,分别是 @Target,@Retention,@Documented,@Inherited,下面先介绍以下四种注解的作用:

@Target:Target 说明了注解所修饰的对象范围,取值(ElementType)有:

  1. 用于描述构造器

  2. 用于描述属性

  3. 用于描述局部变量

  4. 用于描述方法

  5. 用于描述包

  6.  用于描述参数

  7.  用于描述类、接口(包括注解类型)或者 enum 声明

@Retention:Retention 定义了注解的保留范围,取值(RetentionPoicy)有:

  1. 在源文件中有效(即源文件保留)

  2. 在 class 文件中有效(即 class 保留)

  3. 在运行时有效(即运行时保留)

@Documented:Documented 用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。Documented 是一个标记注解,没有成员。

@Inherited:Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了 @Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该 class 的子类。 

@Target({ElementType.METHOD,ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface Myannotation {    String key() default "";}
复制代码


第三步使用注解,因为定义 Target 时定义了 MEHTOD 和 FIELD,因此可以在属性和方法中使用这个注解:

public class MyannotationTest {    @Myannotation(key = "javayz")    private String username;}
复制代码


第四步利用反射解析注解

public static void main(String[] args) {    Class myclass=MyannotationTest.class;    Field[] fields = myclass.getDeclaredFields();    for (Field field :fields){        if (field.isAnnotationPresent(Myannotation.class)){            System.out.println("配置了自定义注解");            Myannotation annotation = field.getAnnotation(Myannotation.class);            System.out.println("属性:"+field.getName()+"上的注解key为"+annotation.key());        }    }}
复制代码


输出结果:

配置了自定义注解属性:username上的注解key为javayz
复制代码


面试官:我看你上面第四步提到了反射是吧?那你给我讲讲什么是反射,它有啥特点:

(我晕,我就说了反射两个字啊,还好有准备)JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。

在上面第四步利用反射解析注解中,我通过 MyannotationTest.class 获取到了 MyannotationTest 的类对象,又用 myclass.getDeclaredFields();获取到了所有的属性。这就是反射。

结束

面试官:不错,这几块的基础算你过关了,下面我要开始真正的技术面试了!

天呐!竟然这才算开始,还好我关注了公众号《Java 鱼仔》,每天在地铁公交上都能学到知识点!

发布于: 2021 年 01 月 17 日阅读数: 27
用户头像

Java鱼仔

关注

你会累是因为你在走上坡路 2020.12.26 加入

微信搜索《Java鱼仔》

评论

发布
暂无评论
面试官问我:什么是静态代理?什么是动态代理?注解、反射你会吗?