1. AOP 的实现方式
AOP:面向切面编程,采用横向抽取的机制取代了传统的纵向继承体系
1.1 动态代理
Spring 中的 aop 通常是在运行时内存中临时生成代理类,故而又称作运行时增强。运行时增强其实就是动态代理,其底层实现有两种:
- 需增强的目标类有接口,采用 JDK 中的动态代理
这种实现要求目标类必须有接口,因为 JDK 动态代理生成的代理类
已经继承Proxy类
,Java 的单继承特性
决定了该方式只能通过接口来实现增强 - 目标类没有接口,采用 CGLIB 动态代理
CGLIB 的原理是通过字节码处理框架ASM来转换字节码并生成目标类的子类
,调用子类方法从而达到增强目的。该方式的缺陷在于,被代理类及被代理方法如果被 final 修饰则无法完成增强逻辑
在 JDK6、JDK7、JDK8 逐步对JDK动态代理优化后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率。只有当进行大量调用的时候,JDK6 和 JDK7 比CGLIB代理效率低一点,但到 JDK8 时JDK代理效率高于CGLIB代理
1.2 静态代理
在 Java 类加载期
通过字节码转换
,将增强逻辑织入切入点(目标类)完成增强的方式就是LTW(Load Time Weaving),即静态代理,也被称作编译时增强
这种方式主要依赖于Java探针技术
,其核心为 java.lang.instrument 包
。这个包在 JDK5.0 时引入,借助该包编写增强逻辑代码打成 jar 包,之后通过 -javaagent
参数来指定Java代理包即可启用,参数格式如下。代理包个数是不限的,指定多个则会按指定的先后执行,执行完各个 agent 后才会执行被代理类方法
-javaagent:<jarpath>[=options]
java.lang.instrument 包在 JVM 启动时会装配并应用 ClassTransformer,对类字节码进行转换,进而实现AOP的功能
- Java 探针技术已经在开源框架 spring-loaded 中应用,可以使用该框架实现 jar 包热部署
Refer:Java探针-Java Agent技术
2. @Value 注入 Map 类型数据
SpringBoot 提供的 @Value
注解可以很方便的完成常规属性的注入,但是在注入Map
类型的数据需要一些特别的处理。通常 Map 与 List 类型的属性在 application.yml
文件中配置如下
-
yml 配置
需注意配置为 Map 的 value 要使用双引号包裹,否则无法正确解析nathan: topics: topic1,topic2,topic3 maps: "{key1: 'value1', key2: 'value2'}"
-
Java 引用
注入 Map 时使用了#{}
包裹目标key,其实是表示使用 EL 表达式@Value("#{${nathan.maps}}") private Map<String, String> fundMatchFactor; @Value("${nathan.topics}") private List<String> topics;
3. Slf4j 日志框架打印堆栈
SpringBoot 项目集成 Slf4j日志框架
打印 log 的时候,调用其 log.error()
方法 传入一个参数只会打印出很简略的错误描述,缺少足够的信息定位问题。查看源码发现其提供了多个方法重载,其中有如下方法声明
void error(String var1);
void error(String var1, Object var2);
void error(String var1, Object var2, Object var3);
void error(String var1, Object... var2);
void error(String var1, Throwable var2);
当使用两个参数的方法 error(String message, Throwable t)
,且第二个参数为 Throwable
类型时,才会将完整的异常堆栈打印出来,正确使用示例如下
@Slf4j
public class ExceptionTest {
@Test
public void test() {
log.error("ExceptionDetail|", new InternalException(ErrorFundRouteEnum.RC_ERROR_INNER_ERROR));
}
}
4. Protobuf 的使用注意
在Java 中使用 Protobuf
时需要注意,当通过 build()
方法生成一个pb对象后,要再修改其中的内容需要调用 toBulider()
使其回到可编辑的状态,最后再调用build()
方法保存修改,否则修改不会写入到 pd 对象中。另外每调用一次build()
方法都会生成一个新的 pb 对象,这点尤其需要注意
RequestBasic requestBasic = RequestBasic.newBuilder().setClientInfo("hhh").build();
// 未调用 build() 方法保存修改,内容不会改变
requestBasic.toBuilder().setClientInfo("ggg");
System.out.println(requestBasic.toString());