经历过招银网络两轮面试后,部分面试题总结如下,题目不在多,而在于精,通过面试题回顾知识点,还是非常不错的复习方式呢,本文针对面试题进行了许多关联问题的扩展和总结。
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 注解来显示指定名称而不是依赖变量的名称。
public @interface Resource {
String name() default "";
Class<?> type() default Object.class;
}
如果仅指定 name 属性则注入方式为byName,如果仅指定type属性则注入方式为byType,如果同时指定name 和type属性(不建议这么做)则注入方式为byType+byName。
小总结:
Spring MVC是Spring中的一个重要模块,可赋予Spring快速构建MVC架构的Web程序的能力,MVC是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是将数据、视图显示、业务逻辑分离来组织组织代码。
使用 Spring 开发,则各种配置过于麻烦,比如开启某些 Spring 特性时,需要用 XML 或 Java 进行显式配置。Spring 旨在简化 J2EE 企业应用程序开发。Spring Boot 内置了各式样的starter启动器,旨在简化 Spring 开发(减少配置文件,开箱即用!)。
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
除了JDK Proxy和Cglib来实现代理,还可以使用 AspectJ
,Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了
AOP 切面编程设计到的一些专业术语:
术语 | 含义 |
---|---|
目标(Target) | 被通知的对象 |
代理(Proxy) | 向目标对象应用通知之后创建的代理对象 |
连接点(JoinPoint) | 目标对象的所属类中,定义的所有方法均为连接点 |
切入点(Pointcut) | 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点) |
通知(Advice) | 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情 |
切面(Aspect) | 切入点(Pointcut)+通知(Advice) |
织入(Weaving) | 将通知应用到目标对象,进而生成代理对象的过程动作 |
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。Spring AOP 已经集成了 AspectJ ,AspectJ 相比于 Spring AOP 功能更加强大,更复杂。
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。
AspectJ 定义的通知类型有哪些?
// 值越小优先级越高
@Order(3)
@Component
@Aspect
public class LoggingAspect implements Ordered {
// ...
}
@Component
@Aspect
public class LoggingAspect implements Ordered {
// ....
@Override
public int getOrder() {
// 返回值越小优先级越高
return 1;
}
}
扩展:微服务系统中,如果使用Gateway作为网关,定义过滤器的执行顺序也是通过设置过滤器的优先级来实现(order值越小,优先级越高),实现方法也是通过@Order
注解或实现Ordered接口,重写getOrder
来完成的
如果我们需要持久化Java对象,比如,将Java对象保存在文件中,或者在网络中传输Java对象,这些对象都需要用到序列化。
简单来说,
对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在 C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而 class 对应的是对象类型。
综上所述,序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中
@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;
}
基本很少或者说几乎不会直接使用这个序列化方式,主要原因有两个:
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");
}
}
}
hessian 是一个轻量级的,自定义描述的二进制 RPC 协议。hessian 是一个比较老的序列化实现了,并且同样也是跨语言的。
dubbo RPC 默认启用的序列化方式是 hession2 ,但是,Dubbo 对 hessian2 进行了修改,不过大体结构还是差不多。
Kryo 是专门针对 Java 语言序列化方式并且性能非常好,如果你的应用是专门针对 Java 语言的话可以考虑使用,并且 Dubbo 官网的一篇文章中提到说推荐使用 Kryo 作为生产环境的序列化方式。(文章地址:https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/)
Checked Exception 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch或者throws 关键字处理的话,就没办法通过编译。
比如下面这段 IO 操作的代码:
除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常 。常见的受检查异常有:IO 相关的异常、ClassNotFoundException 、SQLException…。
Unchecked Exception 即 不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
RuntimeException 及其子类都统称为非受检查异常,常见的有(建议记下来,日常开发中会经常用到):
getMessage()
: 返回异常发生时的简要描述printStackTrace()
:输出 Throwable 对象封装的异常信息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
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
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
面试问题比较基础,其内容有Spring、Springboot的基本使用及其部分原理,Java基础知识。