招银网络面试总结

经历过招银网络两轮面试后,部分面试题总结如下,题目不在多,而在于精,通过面试题回顾知识点,还是非常不错的复习方式呢,本文针对面试题进行了许多关联问题的扩展和总结。

1、@Autowired和@Resource的区别是什么

Spring内置的@Autowired以及jdk内置的@Resource、@Inject 都可以用于Bean的注入。

Annotaion Package
@Autowired org.springframework.bean.factory(Spring2.5+)
@Resource javax.annotation (Java JSR-250)
@Inject javax.inject(Java JSR-330)
  • @Autowired属于Spring的注解,默认的注入方式为byType(根据类型进行匹配),即会优先根据接口类型去匹配并注入Bean(接口的实现类)

  • 问题所在:当一个接口存在多个实现类,选择byType的方式就无法正确注入对象了,因为这时Spring会同时找到多个满足条件的选择,默认情况下它自己并不知道该选择哪一个实现类,其注入方式应该就变为byName(根据名称进行匹配),该名称通常为类名(首字母小写),举例如下:

- // smsService 就是进行匹配的名称(byName)
@Autowired
private SmsService smsService;

SmsService 接口有两个实现类: SmsServiceImpl1和 SmsServiceImpl2,且它们都已经被 Spring 容器所管理。

// 报错,byName 和 byType 都无法匹配到 bean
@Autowired
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Autowired
private SmsService smsServiceImpl1;

// 正确注入  SmsServiceImpl2 对象对应的 bean(推荐使用)
// smsServiceImpl2 就是我们上面所说的名称
@Autowired
@Qualifier(value = "smsServiceImpl2 ")
private SmsService smsService;

推荐使用 @Qualifier 注解来显示指定名称而不是依赖变量的名称。

  • @Resource属于 JDK 提供的注解,默认注入方式为 byName。如果无法通过名称匹配到对应的实现类的话,注入方式会变为byType。
    @Resource 有两个比较重要且日常开发常用的属性:name(名称)、type(类型)
public @interface Resource {
    String name() default "";
    Class<?> type() default Object.class;
}
  • 如果仅指定 name 属性则注入方式为byName,如果仅指定type属性则注入方式为byType,如果同时指定name 和type属性(不建议这么做)则注入方式为byType+byName。

  • 小总结:

    • @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。
    • Autowired 默认的注入方式为byType(根据类型进行匹配),@Resource默认注入方式为 byName(根据名称进行匹配)。
    • 当一个接口存在多个实现类的情况下,@Autowired 和@Resource都需要通过名称才能正确匹配到对应的 Bean。
    • @Autowired 可以通过 @Qualifier 注解(value属性)来显示指定名称,@Resource可以通过 name 属性来显示指定名称。

2、Spring,Spring MVC,Spring Boot 之间什么关系

  • Spring 包含多个功能模块,其中最重要的是 Spring-Core(主要提供 IoC 依赖注入功能的支持) 模块, Spring 中的其他模块(比如 Spring MVC)的功能实现基本都需要依赖于该模块。
  • 下图对应的是 Spring4.x 版本。目前最新的 5.x 版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件

招银网络面试总结_第1张图片

  • Spring MVC是Spring中的一个重要模块,可赋予Spring快速构建MVC架构的Web程序的能力,MVC是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是将数据、视图显示、业务逻辑分离来组织组织代码。
    招银网络面试总结_第2张图片

  • 使用 Spring 开发,则各种配置过于麻烦,比如开启某些 Spring 特性时,需要用 XML 或 Java 进行显式配置。Spring 旨在简化 J2EE 企业应用程序开发Spring Boot 内置了各式样的starter启动器,旨在简化 Spring 开发(减少配置文件,开箱即用!)

3、AOP 了解吗?有什么用?切面执行顺序如何控制?

3.1、AOP简述

  • AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

  • Spring AOP 就是基于动态代理的如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:

  • 招银网络面试总结_第3张图片
    除了JDK Proxy和Cglib来实现代理,还可以使用 AspectJ ,Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了

AOP 切面编程设计到的一些专业术语:

术语 含义
目标(Target) 被通知的对象
代理(Proxy) 向目标对象应用通知之后创建的代理对象
连接点(JoinPoint) 目标对象的所属类中,定义的所有方法均为连接点
切入点(Pointcut) 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点)
通知(Advice) 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情
切面(Aspect) 切入点(Pointcut)+通知(Advice)
织入(Weaving) 将通知应用到目标对象,进而生成代理对象的过程动作

3.2、Spring AOP 和 AspectJ AOP

  • Spring AOP 属于运行时增强,而 AspectJ 是编译时增强Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。Spring AOP 已经集成了 AspectJ ,AspectJ 相比于 Spring AOP 功能更加强大,更复杂。

  • 如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。

  • AspectJ 定义的通知类型有哪些?

    • Before(前置通知):目标对象的方法调用之前触发
    • After (后置通知):目标对象的方法调用之后触发
    • AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
    • AfterThrowing(异常通知) :目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
    • Around:(环绕通知)编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意地 在目标对象的方法调用前后执行,甚至不调用目标对象的方法。

3.3、切面执行顺序如何控制

  • 1)通常使用@Order 注解直接定义切面顺序
// 值越小优先级越高
@Order(3)
@Component
@Aspect
public class LoggingAspect implements Ordered {
   //  ...
}
  • 2、实现Ordered 接口重写 getOrder 方法
@Component
@Aspect
public class LoggingAspect implements Ordered {

    // ....

    @Override
    public int getOrder() {
        // 返回值越小优先级越高
        return 1;
    }
}

扩展:微服务系统中,如果使用Gateway作为网关,定义过滤器的执行顺序也是通过设置过滤器的优先级来实现(order值越小,优先级越高),实现方法也是通过@Order注解或实现Ordered接口,重写getOrder来完成的

4、SpringMVC 工作原理了解吗

招银网络面试总结_第4张图片
流程说明(重要):

  • 1)客户端(浏览器)发送请求, DispatcherServlet拦截请求。
  • 2)DispatcherServlet 根据请求信息调用 HandlerMapping 。HandlerMapping 根据 uri 去匹配查找能处理的 Handler(也就是我们平常说的 Controller 控制器) ,并会将请求涉及到的拦截器和 Handler 一起封装。
  • 3)DispatcherServlet 调用 HandlerAdapter适配执行 Handler 。
  • 4)Handler 完成对用户请求的处理后,会返回一个 ModelAndView 对象给DispatcherServlet,ModelAndView 顾名思义,包含了数据模型以及相应的视图的信息。Model 是返回的数据对象,View 是个逻辑上的 View。
  • 5)ViewResolver 会根据逻辑 View 查找实际的 View。
  • 6)DispaterServlet 把返回的 Model 传给 View(视图渲染)。
  • 7)把 View 返回给请求者(浏览器)

5、什么是序列化?常见的序列化协议有哪些?

5.1、序列化和反序列化

如果我们需要持久化Java对象,比如,将Java对象保存在文件中,或者在网络中传输Java对象,这些对象都需要用到序列化。
简单来说,

  • 序列化:将数据结构或对象转换成二进制字节流的过程
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程

对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在 C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而 class 对应的是对象类型。

综上所述,序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中

5.2、实际开发中的使用场景

  • 对象在进行网络传输(如远程方法调用RPC)之前需要先被序列化,接收到序列化的对象之后,需要再进行反序列化
  • 将对象存储到文件中,需要进行序列化,读取文件取出对象需要进行反序列化
  • 将对象存储到缓存数据库(如Redis)时需要用到序列化,从缓存数据库中读取对象需要反序列化

5.3、序列化协议

TCP/IP 四层模型如下图所示:
招银网络面试总结_第5张图片

  • 在OSI七层协议模型中,表示层主要是将应用层的用户数据进行处理转换为二进制流,将二进制流转换为应用层的数据数据,即对应的是序列化和反序列化。
  • OSI 七层协议模型中的应用层、表示层和会话层对应的都是 TCP/IP 四层模型中的应用层,所以序列化协议属于 TCP/IP 协议应用层的一部分。

5.4、常见的序列化协议对比

  • JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且部分版本有安全漏洞。比较常用的序列化协议有 hessian、kyro、protostuff(基于二进制的序列化协议)。
  • JDK 自带的序列化方式
    JDK 自带的序列化,只需实现 java.io.Serializable接口即可。
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@ToString
public class RpcRequest implements Serializable {
    private static final long serialVersionUID = 1905122041950251207L;
    private String requestId;
    private String interfaceName;
    private String methodName;
    private Object[] parameters;
    private Class<?>[] paramTypes;
    private RpcMessageTypeEnum rpcMessageTypeEnum;
}

基本很少或者说几乎不会直接使用这个序列化方式,主要原因有两个:

  • 不支持跨语言调用 : 如果调用的是其他语言开发的服务的时候就不支持了。
  • 性能差 :相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。

1)Kryo

Kryo 是一个高性能的序列化/反序列化工具,由于其变长存储特性并使用了字节码生成机制,拥有较高的运行速度和较小的字节码体积。

另外,Kryo 已经是一种非常成熟的序列化实现了,已经在 Twitter、Groupon、Yahoo 以及多个著名开源项目(如 Hive、Storm)中广泛的使用。

guide-rpc-framework[1] 就是使用的 kyro 进行序列化,见- Github 地址:https://github.com/EsotericSoftware/kryo 。序列化和反序列化相关的代码如下:

@Slf4j
public class KryoSerializer implements Serializer {
    /**
     * Because Kryo is not thread safe. So, use ThreadLocal to store Kryo objects
     */

    private final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> {
        Kryo kryo = new Kryo();
        kryo.register(RpcResponse.class);
        kryo.register(RpcRequest.class);
        return kryo;
    });

    @Override
    public byte[] serialize(Object obj) {
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             Output output = new Output(byteArrayOutputStream)) {
            Kryo kryo = kryoThreadLocal.get();
            // Object->byte:将对象序列化为byte数组
            kryo.writeObject(output, obj);
            kryoThreadLocal.remove();
            return output.toBytes();
        } catch (Exception e) {
            throw new SerializeException("Serialization failed");
        }
    }

    @Override
    public <T> T deserialize(byte[] bytes, Class<T> clazz) {
        try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
             Input input = new Input(byteArrayInputStream)) {
            Kryo kryo = kryoThreadLocal.get();
            // byte->Object:从byte数组中反序列化出对对象
            Object o = kryo.readObject(input, clazz);
            kryoThreadLocal.remove();
            return clazz.cast(o);
        } catch (Exception e) {
            throw new SerializeException("Deserialization failed");
        }
    }

}

2)hessian

  • hessian 是一个轻量级的,自定义描述的二进制 RPC 协议。hessian 是一个比较老的序列化实现了,并且同样也是跨语言的。
    招银网络面试总结_第6张图片

  • dubbo RPC 默认启用的序列化方式是 hession2 ,但是,Dubbo 对 hessian2 进行了修改,不过大体结构还是差不多。

3)总结

Kryo 是专门针对 Java 语言序列化方式并且性能非常好,如果你的应用是专门针对 Java 语言的话可以考虑使用,并且 Dubbo 官网的一篇文章中提到说推荐使用 Kryo 作为生产环境的序列化方式。(文章地址:https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/)
招银网络面试总结_第7张图片

6、Exception 和 Error 有什么区别?

6.1、Java 异常类层次结构图概览

招银网络面试总结_第8张图片

  • 在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:
  • Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)
    Error :Error 属于程序无法处理的错误 ,我们没法通过 catch 来进行捕获 。例如 Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止

6.2、Checked Exception 和 Unchecked Exception 有什么区别?

  • Checked Exception 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch或者throws 关键字处理的话,就没办法通过编译
    比如下面这段 IO 操作的代码:
    招银网络面试总结_第9张图片
    除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常 。常见的受检查异常有:IO 相关的异常、ClassNotFoundException 、SQLException…。

  • Unchecked Exception 即 不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译

  • RuntimeException 及其子类都统称为非受检查异常,常见的有(建议记下来,日常开发中会经常用到):

    • NullPointerException(空指针错误)
    • IllegalArgumentException(参数错误比如方法入参类型错误)
    • NumberFormatException(字符串转换为数字格式错误,IllegalArgumentException的子类)
    • ArrayIndexOutOfBoundsException(数组越界错误)
    • ClassCastException(类型转换错误)
    • ArithmeticException(算术错误)
    • SecurityException (安全错误比如权限不够)
    • UnsupportedOperationException(不支持的操作错误比如重复创建同一用户)
    • (并发修改异常)

6.3、Throwable 类常用方法有哪些

  • String getMessage(): 返回异常发生时的简要描述
  • String toString():返回异常发生时的详细信息
  • String getLocalizedMessage():返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
  • void printStackTrace():输出 Throwable 对象封装的异常信息

6.4、try-catch-finally 如何使用

  • try块 :用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
  • catch块 :用于处理 try 捕获到的异常。
  • finally 块 :无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行,即会先执行return语句,再执行finally语句
try {
    System.out.println("Try to do something");
    throw new RuntimeException("RuntimeException");
} catch (Exception e) {
    System.out.println("Catch Exception -> " + e.getMessage());
} finally {
    System.out.println("Finally");
}

输出:

Try to do something
Catch Exception -> RuntimeException
Finally
  • 注意:不要在 finally 语句块中使用 return! 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。
  • jvm 官方文档中有明确提到:
    招银网络面试总结_第10张图片
public static void main(String[] args) {
    System.out.println(f(2));
}

public static int f(int value) {
    try {
        return value * value;
    } finally {
        if (value == 2) {
            return -1;
        }
    }
}

输出:

-1

6.5、finally 中的代码一定会执行吗

  • finally 中的代码不一定会执行!在某些情况下,finally 中的代码不会被执行。
    就比如说 finally 之前虚拟机被终止运行的话,finally 中的代码就不会被执行
try {
    System.out.println("Try to do something");
    throw new RuntimeException("RuntimeException");
} catch (Exception e) {
    System.out.println("Catch Exception -> " + e.getMessage());
    // 终止当前正在运行的Java虚拟机
    System.exit(1);
} finally {
    System.out.println("Finally");
}

输出:

Try to do something
Catch Exception -> RuntimeException
  • 另外,在以下 2 种特殊情况下,finally 块的代码也不会被执行:
    • 程序所在的线程死亡。
    • 关闭 CPU。
      详情请参考issue:https://github.com/Snailclimb/JavaGuide/issues/190。

6.6、异常使用有哪些需要注意的地方?

  • 不要把异常定义为静态变量,因为这样会导致异常栈信息错乱每次手动抛出异常,我们都需要手动 new 一个异常对象抛出。另外,一定要让抛出的异常信息要有意义。
  • 建议抛出更加具体的异常,比如字符串转换为数字格式错误的时候应该抛出NumberFormatException而不是其父类IllegalArgumentException。
  • 使用日志打印异常之后就不要再抛出异常了(两者不要同时存在一段代码逻辑中)。

7、总结

面试问题比较基础,其内容有Spring、Springboot的基本使用及其部分原理,Java基础知识。

你可能感兴趣的:(Spring,面试总结,Java后端开发,面试,java,spring)