文章目录
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 agent 探针技术(1)-JVM 启动时 premain 进行类加载期增强
Java agent 探针技术(2)-JVM 启动后 agentmain 进行类运行时转换
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:{\"1\":\"2,3\"}}}") private Map<String, List<Long>> 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对象后,要再修改其中的内容需要调用 toBuilder()
使其回到可编辑的状态,最后再调用build()
方法保存修改,否则修改不会写入到 pd 对象中。另外每调用一次build()
方法都会生成一个新的 pb 对象,这点尤其需要注意,因为当使用 pb 对象到方法内部获取数据时,build() 生成的新对象会导致传入的对象引用断开
RequestBasic requestBasic = RequestBasic.newBuilder().setClientInfo("hhh").build();
// 未调用 build() 方法保存修改,内容不会改变
requestBasic.toBuilder().setClientInfo("ggg");
System.out.println(requestBasic.toString());
5. Spring 非 web 应用模式启动
SpringBoot 框架内置了诸如 Tomcat 之类的 web 服务器,启动时默认为 web 应用模式,也就是会占用一个端口资源。但是有时候我们也会有使用 SpringBoot 框架但是自行实现服务器的需求,这就需要一些特殊的配置让框架以非 web 应用模式启动的。在 SpringBoot 2.0 及以上的版本中,以下两种方式可以实现应用模式的切换。需注意,如果使用非 web 应用模式启动 SpringBoot 后程序主线程没有持续运行,那么会立即运行结束退出
-
application.yml 配置文件配置
实际使用中,SpringBoot 是根据加载的相关类来实现应用模式的切换,这个可参考 WebFlux 服务启动流程 中的相关分析。另外 SpringBoot 中可以配置的属性其实都是由 meta 文件来定义的,这些属性可以在以下两个 json 文件中找到:
spring-configuration-metadata.json
additional-spring-configuration-metadata.json
spring: main: web-application-type: none # none, servlet, reactive
-
应用主类中代码配置
WebApplicationType
有 3 个枚举值:- NONE:非 web 模式
- SERVLET:基于 Java Servlet 的 web 应用模式,也就是 Spring Mvc
- REACTIVE:基于响应式的 web 应用模式,也就是 Spring Webflux
@SpringBootApplication public class WebFluxApplication { public static void main(String[] args) { new SpringApplicationBuilder(WebFluxApplication.class) .web(WebApplicationType.NONE) .run(args); } }
6. Spring RestTemplate 的请求 URL 经 urlencode 编码后异常的解决方案
使用 RestTemplate 时,如果 url 中存在特殊字符,可能存在编码前传进去不对,编码后传进去也不对的问题,解决步骤如下,具体可参考
- 首先对 requestUrl 完成 URL encode
- 再使用 URI.create(requestUrl) 保证 RestTemplate 不再对特殊字符转义