跳到主要内容

Spring 面试题

服务滚动发布,如何确保进程退出期间,待处理和处理中的请求 服务正确处理请求,不出现业务异常呢?

https://mp.weixin.qq.com/s/wyhuaXNurA8PY4-xwiFORg

进程退出时,有应用线程从Spring GetBean,然而Spring 不允许BeanFactory 销毁期间获取bean,导致请求处理失败、数据不一致的未知结果

JVM 优雅退出机制

  • 正常退出
  • 异常退出
  • 强制退出

正常退出

正常退出时,JVM会执行shutdown hook 钩子程序。钩子提供了java代码响应进程退出的机制,例如Spring等框架通过注册shutdownHook钩子程序,监测到进程要退出的信号,然后关闭Spring上下文。

正常关闭的4种方式

  1. 所有非守护线程退出
  2. System.exit(0)
  3. Ctrl+ C 命令行中退出进程
  4. kill Pid 通知进程退出。

异常退出

OOM是指一个线程新申请一块内存,JVM发现内存不足,于是触发Full GC,但是FullGC以后依然内存不足,于是该线程触发OOM异常。一般情况下线上进程如果出现OOM,需要及时关闭,否则可能不停触发OOM,导致更多的异常。

OOM只是在一个线程抛出异常,如果异常没有捕获,最多只会关闭这一个线程,不会殃及整个JVM。但是服务既然出现OOM,已经说明服务的内存模型存在问题,可能存在内存泄漏,这种情况下理应退出进程,避免更多的失败。

一般情况下,我们无法获知服务在哪行代码哪个线程出现 OOM,自然无法有效 catch 异常,执行System.exit 退出进程。好在JVM提供参数 配置,可以在 OOM 发生后执行某个策略

  1. -XX:+HeapDumpOnOutOfMemoryError参数表示当JVM发生OOM时,自动生成DUMP文件。-XX:HeapDumpPath= 目录,也可以指定文件名称,例如:−XX:HeapDumpPath={目录},也可以指定文件名称,例如:- XX:HeapDumpPath=目录,也可以指定文件名称,例如:−XX:HeapDumpPath={目录}/java_heapdump.hprof
  2. -XX:OnOutOfMemoryError 在程序发生OOM异常时,执行指定命令,该参数接下来会详细介绍,也是JVM优雅退出的关键参数
  3. -XX:+ExitOnOutOfMemoryError 在程序发生OOM异常时,强制退出
  4. -XX:+CrashOnOutOfMemoryError 在程序发生OOM异常时,强制退出,并生成Crash日志

可以在OnOutOfMemberError 配置脚本,通过脚本kill进程。可以使用以下参数配置,在OOM发生后,kill进程,如果进程在60s未退出,执行kill-9强制关闭。

-XX:OnOutOfMemoryError="kill -15 %p && sleep 60 && kill -9 %p &"

强制退出

  1. Kill -9 关闭进程
  2. Runtime.halt()
  3. 机器断电、操作系统关机
  4. 操作系统强制杀死一个进程
  5. 进程Crash

JVM 的 ShutdownHook钩子程序

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
System.out.println("shutdown hook started");
}
}));

<span style="color: red;">严禁在钩子程序中调用 System.exit方法</span>

shutdownhook 程序会并发执行,无法指定其执行的顺序。

Spring 提供的关闭扩展点

  1. 发布 ContextClosedEvent,可以使用 EventListener 注解,监听该事件
  2. 执行 Lifecycle 类型Bean 的所有stop方法。
  3. 关闭BeanFactory,单例Bean中声明init-methoddestroy-method 的会被销毁。如果单例bean未声明两个方法,则不会被销毁。

1、使用 EventListener 注解监听 ContextClosedEvent 事件

@EventListener  
public void handleContextClosedEvent(ContextClosedEvent event) {
log.info("use @EventListener:Application context is closed!");
}

2、Lifecycle 的 stop 方法

IoC 的实现

SpringBoot 自动配置的底层实现

面试题

  1. Bean 的生命周期
  2. Bean 的作用域
  3. Spring MVC 的工作原理
  4. AOP
  5. BeanFactory 和 FactoryBean 的区别?
  6. 三级缓存?

查看 springboot 和 springcloud 的版本对于关系

  1. 进入 https://spring.io/projects/spring-cloud#learn
  2. 选择要用到的 springcloud 的版本 Reference Doc
  3. 可以看见 Release Train Version 和 Supported Boot Version

Spring

Ioc 容器:存放的是 Bean

Spring 的使用

  1. 创建 applicationContext.xml 文件
  2. 创建 ApplicationContext 对象

如果让你来设计 Ioc 容器,你会怎么设计?

首先,容器采用 HashMap<String,Object> 来存储 Bean。

  • String, Object // Bean 实例
  • String,ObjectFactor //
  • String, BeanDefinition // Bean 的定义信息

Bean 的生命周期

  1. Spring 对 Bean 进行实例化

  2. Spring 将值和 bean 引用注入到 bean 对应的属性中

  3. 如果 Bean 实现了 BeanNameAware 接口,Spring 将 Bean 的 id 传递给 setBeanName() 方法

  4. 如果 bean 实现了 BeanFactoryAware 接口,Spring 将调用 setBeanFactory() 方法,将 BeanFactory 容器实例传入

  5. 如果 Bean 实现了 ApplicationContextAware 接口,Spring 将调用 setApplicationContext() 方法,将 bean 所在的应用上下文的引用传入进来

  6. 如果 Bean 实现了 BeanPostProcessor 接口,Spring 将调用它们的 postProcessBeforeInitialization() 方法

  7. 如果 Bean 实现了 InitializingBean 接口,Spring 将调用它们的 afterPropertiesSet() 方法。类似地,如果 bean 使用 init-method 声明了初始化方法,该方法也会被调用

  8. 如果 Bean 实现了 BeanPostProcessor 接口,Spring 将调用它们的 postProcessAfterInitialization() 方法

  9. 此时 Bean 已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁。

  10. 如果 bean 实现了 DisposableBean 接口,Spring 将调用它的 destroy() 接口方法。同样,如果 bean 使用 destroy-method 声明了销毁方法,该方法也会被调用

  11. 实例化 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance

  12. 填充属性 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean

  13. 检查 Aware 接口,将 Spring 容器中其他对象传入创建的 bean 。 (BeanNameAware、BeanFactoryAware、ApplicationContextAware) org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeAwareMethods

  14. BeanPostProcessor 前置处理 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization

  15. 初始化方法 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeInitMethods

  16. BeanPostProcessor 后置处理方法 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization

  17. bean 已经被创建

  18. 销毁。

Spring Boot 配置

Spring Boot 的配置文件有

  1. bootstrap.yml
  2. bootstrap.properties
  3. application.yml
  4. application.properties
  5. application-{profile}.properties

加载顺序就是上面列出来的顺序。

源码对应:org.springframework.boot.context.config.ConfigFileApplicationListener

bootstrap.yml 和 bootstrap.properties 用于 Spring Cloud 项目中,用于配置一些需要在应用程序启动早期就加载的属性,比如配置服务 (config server) 的地址。

application.yml 和 application.properties 是 Spring Boot 项目的主要配置文件,用于配置数据库连接,服务器端口,Spring MVC,安全性,JPA等属性。

redirect-和-forward-的区别

区别

redirect:https://www.baidu.com 报错

解决方法:

  1. 使用 spring 自带定内部视图解析器
@Bean
public InternalResourceViewResolver viewResolver(){
return new InternalResourceViewResolver();
}
  1. 使用HttpServletResponse response重定向的方法:
response.sendRedirect(url);

ObjectProvider

使用 ObjectProvider<T> 代表要注入的对象 T ,可以避免不存在或存在多个符合注入的对象时的启动报错。

  • 如果注入实例为空时,使用ObjectProvider则避免了强依赖导致的依赖对象不存在异常;
  • 如果有多个实例,ObjectProvider的方法会根据Bean实现的Ordered接口或@Order注解指定的先后顺序获取一个Bean。从而了提供了一个更加宽松的依赖注入方式。
@Bean  
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}

换成不使用 lambda 表达式的代码

@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
restTemplateCustomizers.ifAvailable(new Consumer<List<RestTemplateCustomizer>>() {
@Override
public void accept(List<RestTemplateCustomizer> customizers) {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
}
});
}
};
}

Ioc 控制反转

把创建对象的过程交给Spring来进行管理。

BeanFactory

原理

通过 工厂 + 反射 实现的。

流程

  1. ApplicationContext ctx = new FileSystemXmlApplicationContext ("spring-beans/src/test/resources/beans.xml");

设置资源加载器、设置资源定位、刷新容器(IOC容器初始化)。

面向切面编程

基本原理:动态代理

切面:

spring 创建的是这个切面的代理对象。

  • JDK 动态代理
    • 需要实现 InvocationHandler 接口
    • Proxy.newProxyInstance() 生成一个代理对象
    • 被代理的类必须是一个实现了接口的类。
  • CGlib 动态代理
    • 通过 ASM 字节码生成框架,直接对需要代理的类的字节码进行操作,生成这个类的字类,并重写所有可重写的 方法。
    • CGLib 的实现方式是继承,被代理的类不能是 final 类。
    • 生成代理对象的效率慢,但是方法执行速度快。

AOP 代理失效的情况

  1. 目标对象内部方法调用

这种调用是通过直接方法调用,而不是通过代理对象进行的。

解决方法:通过 AopContext.currentProxy() + @EnableAspectJAutoProxy(proxyTargteClass = true, exposeProxy = true) 获取到目标对象的代理对象。

  1. 目标类被 final 修饰

  2. 目标方法为 private

在 SpringBoot 中使用 AOP

引入依赖

  <!-- aop切面 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

启用

SpringBoot 启动类中添加 @EnableAspectJAutoProxy 注解。

注意:当值使用 @EnableAspectJAutoProxy 时,接口的代理会使用 JDK 动态代理,类的代理会使用 CGLIB 代理。 但是如果使用 @EnableAspectJAutoProxy(proxyTargetClass = true) 时,不管是接口还是类都会使用 CGLIB 代理。

相关注解

  • @Aspect
  • @Before
  • @Around
  • @AfterReturning
  • @After
  • @AfterThrowing
  • @Pointcut

底层原理

Spring 中,AOP 的增强的过程是在 Bean 初始化后执行的。

如果被代理的类实现了接口,且没有强制使用 cglib 代理的化,Spring 会使用 JDK 动态代理来代理这个类。

如果被代理的类没有实现接口,Spring 会使用 cglib 代理。

最后把生成的代理对象交给 Spring 进行管理。

失效原因

  1. 没有开启 AOP 功能
  2. 没有在 Spring 容器中注册切面
  3. 被增强的方法不是 public 的。
  4. 被增强的方法使用 final、static 修饰了。

ResponseEntity

作用

ResponseEntity 表示整个 HTTP 响应,包括状态码,标头,正文。

用法

/**
* 通用下载请求
* download接口 其实不是很有必要
* @param fileName 文件名称
*/
@Operation(summary = "下载文件")
@GetMapping("/download")
public ResponseEntity<byte[]> fileDownload(String fileName, HttpServletResponse response) {
try {
if (!FileUploadUtils.isAllowDownload(fileName)) {
// 返回类型是ResponseEntity 不能捕获异常, 需要手动将错误填到 ResponseEntity
ResponseDTO<Object> fail = ResponseDTO.fail(
new ApiException(Business.COMMON_FILE_NOT_ALLOWED_TO_DOWNLOAD, fileName));
return new ResponseEntity<>(JacksonUtil.to(fail).getBytes(), null, HttpStatus.OK);
}

String filePath = FileUploadUtils.getFileAbsolutePath(UploadSubDir.DOWNLOAD_PATH, fileName);

HttpHeaders downloadHeader = FileUploadUtils.getDownloadHeader(fileName);

response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
return new ResponseEntity<>(FileUtil.readBytes(filePath), downloadHeader, HttpStatus.OK);
} catch (Exception e) {
log.error("下载文件失败", e);
return null;
}
}

启动参数

1、上下文

# spring-boot 1.x 使用的是  server.context-path
-Dserver.context-path=/abi

# spring-boot 2.x 使用的是 server.servlet.context-path
-Dserver.servlet.context-path=/abi

2、端口号

-Dserver.port=12582

3、工作目录

# abi
-Desen.abi.workdir=/Users/wangzhy/wangzhydev/workdir/abi543

# eiscp
-Desen.eiscp.workdir=/Users/wangzhy/wangzhydev/workdir/eiscp2.0_mysql_cc
-Desen.ecore.workdir=/Users/wangzhy/wangzhydev/workdir/eiscp2.0_mysql_cc
-Dworkdir=/Users/wangzhy/wangzhydev/workdir/eiscp2.0_mysql_cc

# edg

spring-boot-占位符

  • @...@
  • {...}

配置

  • 配置项 useDefaultDelimiters,可以控制是否使用默认占位符。如果为 true,则 ${*}@*@ 这两种占位符始终有效,可以同时使用
  • 配置项 delimiter,既可以写默认占位符,也可以自定义占位符,比如上文中的 #

spring-boot-实战

Spring Boot 使用案例

在一个 Singleton 的组件对象中,获取一个 Prototype 的组件对象

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeBean {

}
@Component
public class SingletonBean{

@Autowired
private ApplicationContext context;

public PrototypeBean getPrototypeBean() {
return context.getBean(PrototypeBean.class);
}
}

全局异常处理

@ControllerAdvice
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {

/**
* 异常处理
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public ResultInfo exceptionHandler(Exception e) {
log.error("", e);
return ResultInfo.error(ExceptionCodeConstant.UNKNOWN_EXCEPTION, e.getMessage(), false);
}

/**
* 自定义异常处理
* @param e
* @return
*/
@ExceptionHandler(XxxExceptionBase.class)
public ResultInfo XxxExceptionHandler(XxxExceptionBase e) {
log.error("", e);
return ResultInfo.error(e.getErrorCode(), e.getMessage(), false);
}

}

springboot-最佳实践

1、包目录风格

基于类型

  • src
    • main
      • java
        • com.app
          • confing
          • controller
          • exception
          • model
            • common
            • entity
            • enums
            • response
            • request
          • repository
          • security
          • service
          • util
          • AppApplication.java
      • resources
        • application.yml
        • application-dev.yml
        • application-prod.yml
        • application-test.yml
        • banner.txt
        • logback-spring.xml
        • logback.xml
        • static
        • templates
      • test

基于模块

把每个模块的代码放在一个包下,比如:user、order、product等模块,每个模块下面都有controller、service、repository等包。

2、使用设计模式

3、使用 Spring Boot starter

这是 Spring Boot 的一个很酷的功能。 我们可以非常轻松地使用启动器依赖项,而无需一一添加单个依赖项。这些入门依赖项已与所需的依赖项捆绑在一起。 例如,如果我们添加 spring-boot-starter-web 依赖项,默认情况下它会与 jackson、spring-core、spring-mvc 和 spring-boot-starter-tomcat 依赖项捆绑在一起。 所以我们不需要关心单独添加依赖项。 它还可以帮助我们避免版本不匹配。

4、使用生产版本的依赖性

始终建议使用最新的稳定 GA 版本。 有时它可能会因 Java 版本、服务器版本、应用程序类型等而有所不同。 不要使用同一包的不同版本,如果存在多个依赖项,请始终使用 <properties> 指定版本。

5、使用 Lombok

使用 Lombok 可以减少代码量。

常用注解

  • @Data
  • @Slf4j
  • @NoArgsConstructor
  • @AllArgsConstructor
  • @RequiredArgsConstructor
  • @NonNull

6、将构造函数注入与 Lombok 一起使用

@RequiredArgsConstructor + final/@NonNull

7、使用 slf4j 日志

使用 @Slf4j

始终使用 slf4j{} 占位符语法,避免在记录器消息中使用字符串插值。因为字符串插值会消耗更多的内存。

如果我们处于微服务环境中,则可以使用 ELK 技术栈。

8、控制器仅用于路由

控制器专用于路由。

控制器是请求的最终目标,请求将交给服务层并由服务层处理。

业务逻辑不应位于控制器中。

9、使用 Service 来实现业务逻辑

完整的业务逻辑包含验证、缓存等。 与持久层通信并接收结果。 Service也是单例的

10、避免空指针异常

我们还可以使用空安全库。例如:Apache Commons StringUtils 对已知对象调用 equals() 和 equalsIgnoreCase() 方法。 使用 valueOf() 而不是 toString() 使用基于 IDE 的 @NotNull 和 @Nullable 注释。

11、使用集合框架的最佳实践

对我们的数据集使用适当的集合。 将 forEach 与 Java 8 功能结合使用,并避免使用旧版 for 循环。 使用接口类型而不是实现。 使用 isEmpty() 而不是 size() 以获得更好的可读性。 不返回空值,可以返回空集合。 如果我们使用对象作为要存储在基于哈希的集合中的数据,则应重写 equals() 和 hashCode() 方法。请查看这篇文章“HashMap 内部是如何工作的”。

12、使用分页

13、使用缓存

在谈论应用程序性能时,缓存是另一个重要因素。 默认情况下,Spring Boot 通过 ConcurrentHashMap 提供缓存,我们可以通过 @EnableCaching 注解来实现这一点。 如果我们对默认缓存不满意,可以使用 Redis、Hazelcast 或任何其他分布式缓存实现。 Redis 和 Hazelcast 是内存缓存方法。我们还可以使用数据库缓存实现。

14、使用自定义异常处理程序和全局异常处理

这在使用大型企业级应用程序时非常重要。 除了一般异常之外,我们可能还会有一些场景来识别某些特定的错误情况。 异常顾问可以使用@ControllerAdvice 创建,我们可以创建具有有意义细节的单独异常。 它将使得将来识别和调试错误变得更加容易。

15、使用自定义响应对象

  • 自定义响应对象可用于返回包含某些特定数据的对象,并满足 HTTP 状态代码、API 代码、消息等要求。
  • 我们可以使用构建器设计模式来创建具有自定义属性的自定义响应对象。

16、删除不必要的代码、变量、方法和类。

  • 未使用到的变量会占用一部分内存
  • 增加代码可读性
  • 避免嵌套循环。

17、使用注释

18、对类、方法、函数、变量和其他属性使用有意义的词语。

  • 类、接口、枚举、注解:名词或名词短语
  • 方法:动词或动词短语
  • 避免缩写

19、使用正确的大小写进行声明

  • UPPERCASE
  • lowercase
  • camelCase
  • PascalCase
  • snake_case
  • SCREAMING_SNAKE_CASE
  • kebab-case

20、简单点

同样逻辑的代码,选择使用可读性较高的方式。

21、使用通用的代码格式样式

22、使用 SonarLint 插件

避免不必要的错误和提高代码质量。

spring-boot

1.@configuration 作用:用来设定Spring的环境配置(相当于applicationContext.xml) 在类上加入这个注解就表示此类成为Spring的配置类 在这个类的方法上加入@Bean注解,则这个方法返回的类就会 被注册为Spring容器管理的bean

2.@Scope 对象在Spring容器中的生命周期,也可以理解为 对象在Spring容器中的创建方式。 五种(后面三种是在Spring 2.0 之后新增的) 1.singleton 单例 2.prototype 每次都会重新生成一个新的对象给请求方 在将对象实例返回给请求方之后,容器就不在拥有当前对象的 引用 3.request 4.session 5.global session

3.Environment 表示整个应用运行时的环境。

4.@PostConstruct Java的注解 @PostConstruct 用来修饰一个非静态的void()方法 被修饰的方法会在服务器加载Servlet的时候运行,并且只会 执行一次,在构造函数之后,init()之前执行。

@Valid 和 @Validated 区别,以及不生效的原因。

不生效的原因:springboot 2.3 开始,需要引入 spring-boot-starter-validation 工程。

需求

一、在 springboot 项目启动之后,要执行某些代码

实现 ApplicationRunner 接口,实现 run 方法即可。可以在同一应用程序上下文中定义多个 ApplicationRunner bean,并且可以使用Ordered接口或@Order注释进行排序.

注意:如果 run 方法抛出了异常,会终止 springboot 的启动。

learned from xuechen 数聚军科项目机构用户同步 on 2023.11.20

二、@Order 接口的使用

order 的值越小,优先级越高。

@Order(1) 优先级高于 @Order(2)

三、ApplicationRunner 与 CommandLineRunner 的区别

主要的区别是接口参数不同。

CommandLineRunner: void run(String... args) throws Exception; //

ApplicationRunner: void run(ApplicationArguments args) throws Exception;

Spring Boot 插件开发模式思想- SPI 机制

https://juejin.cn/post/7287028255987580947

Java SPI 机制

本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类

核心方法:(azul-17) : java.util.ServiceLoader.LazyClassPathLookupIterator#nextProviderClass

private Class<?> nextProviderClass() {  
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null) {
configs = ClassLoader.getSystemResources(fullName);
} else if (loader == ClassLoaders.platformClassLoader()) {
// The platform classloader doesn't have a class path,
// but the boot loader might. if (BootLoader.hasClassPath()) {
configs = BootLoader.findResources(fullName);
} else {
configs = Collections.emptyEnumeration();
}
} else {
configs = loader.getResources(fullName);
}
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return null;
}
pending = parse(configs.nextElement());
}
String cn = pending.next();
try {
return Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service, "Provider " + cn + " not found");
return null;
}
}

Spring Boot SPI

通过 org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames 方法,读取所有的 META-INF/spring.factories 文件中的配置的类的全限定名称,通过反射机制实例化对象,就形成了具体的工厂实例,工厂实例来生成组件具体需要的 bean,这就是Spring Boot自动配置的核心原理。

/**  
* 使用给定的类加载器从"META-INF/spring.factories"加载给定类型的工厂实现的完全限定类名
* @param factoryClass the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading resources; can be
* {@code null} to use the default
* @throws IllegalArgumentException if an error occurs while loading factory names
* @see #loadFactories
*/
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String propertyValue = properties.getProperty(factoryClassName);
for (String factoryName : StringUtils.commaDelimitedListToStringArray(propertyValue)) {
result.add(factoryName.trim());
}
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}

Spring 注解

1.@Component 和 @Named

2.@Configuration

3.@ComponentScan

默认以配置类所在包作为基础包(base package) 来扫描组件。

@ComponentScan
@ComponentScan("com.wangzhy.spring")
@ComponentScan(basePackages="com.wangzhy.spring")
@ComponentScan(basePackages={"com.wangzhy.spring1","com.wangzhy.spring2"})
@ComponentScan(basePackageClasses={CDPlayer.class}) // 将类所在包作为组件扫描的基础包

4.@AutoWiring 和 @Inject

@AutoWiring 注解可以用在类的任何方法上。构造方法、Setter方法等。

@AutoWiring
@AtuoWiting(required=false)

5.@Bean

@Bean 注解会告诉 Spring 这个方法会返回一个对象,该对象要注册为 Spring 应用上下文中的 bean。

默认情况下,bean 的 ID 与方法名一致。

@Bean
@Bean(name="lonelyHeartsClubBand")

通过 Java 代码装配 Bean

Spring 会拦截添加了 @Bean 注解的方法,确保是返回该方法创建的 bean(已创建过了直接返回,未创建过则先创建再返回),并不会每次都对其进行实际的调用。

@Bean  
public CompactDisc getCompactDisc(){
return new SgtPeppers();
}

@Bean
public MediaPlayer getPlayer(){
return new CDPlayer(getCompactDisc());
}

建议使用下面这种声明方式

@Bean  
public MediaPlayer setPlayer(CompactDisc cd){
return new CDPlayer(cd);
}

指定 Bean 的 init、destory 方法的三种方式

1、@Bean
@Bean(initMethod="init",destoryMethod="destory")
2、@PostConstruct、@preDestory
@PostConstruct
public void init() {...}
@PreDestroy
public void destroy() {...}
3、XML
<bean id ="beanid" class="com.wangzhy.xxx" init-method="init" destory-method="destory">

6.@Import 和 @ImportResource

将两个配置类组合起来

@Configuration
@Import({CDPlayerConfig.class,CDConfig.class})
public class SoundSystemConfig{ ... }

@ImportResource("classpath:cd-config.xml")

7.@Profile

@Profile 可以指定某个 bean 属于哪一个 profile。

@Profile("dev")

激活 profile

spring.profiles.active
spring.profiles.default

在单元测试中,激活 profile

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={XxxxText.classes})
@ActiveProfiles("dev")
public class XxxxText {}

8、@Conditional

@Conditional 的使用方式

首先需要一个实现 Condition 接口的类。

@Bean
@Conditional(XxxCondition.class)
Condition 接口的定义

ConditionContext 接口

AnnotatedTypeMetadata

@Profile 是借助 @Conditional 注解实现的

@Profile 的定义

ProfileCondition

9、@Primary 标示首选的 bean

@Primary 注解主要用在自动装配的时候,找到多个符合条件的类的情况。

@Component
@Primary
public class IceCream implements Dessert {...}

10. @Qualifier 限定自动装配的 bean

@Qualifier 注解的定义

使用方式一:

说明:iceCream 是一个 Bean 的 ID。 缺点: 1、"iceCream" 是一个字符串,需要手动输入,没有编译器的检查,很容易出错。 2、当修改了 IceCream 类名的时候,beanid 会随之改变。

使用方式二:

说明:在 IceCream 类加上 @Qualifier("cold") 注解。然后在自动注入的时候加上@Qualifier("cold")。 缺点: 1、当多个类都声明了 @Qualifier("cold") ,在自动注入的时候还是找不到类。 2、当需要增加 @Qualifier("creamy") 缩小选择范围的时候,编译器会报错。因为 Java 不允许在同一个条目上使用两个相同的注解。虽然在 JDK8 中支持了,但是需要在注解定义的时候加上 @Repeatable 注解。我们可以从 @Qualifier 注解的定义中看到,并没有加上 @Repeatable 注解。

使用方式三:

说明:使用自定义注解。在自定义注解的定义上加上 @Qualifier 注解。

11. @Scope

@Scope 用于指定 Bean 的作用域。

作用域说明
单例singletonConfigurableBeanFactory.SCOPE_SINGLETON
原型prototypeConfigurableBeanFactory.SCOPE_PROTOTYPE
会话sessionWebApplicationContext.SCOPE_SESSION
请求requestWebApplicationContext.SCOPE_REQUEST
@Component
@Scope()
public class Xxxx{}

12. @Requestparam 、@PathVariable、@RequestBody、@RequestHeader、@CookieValue

@RequestMapping("/path/{id}")
public String pathVariable(@PathVariable(name = "id") String id,
@RequestParam(name = "name") String name,
@RequestBody(required = false) ReqMsg reqMsg,
ReqMsg reqMsg1,
@RequestHeader("Content-Type") String contentType,
@CookieValue("myCookie") String cookie) {

JSONObject result = new JSONObject();
result.put("id", id);
result.put("name", name);
result.put("reqMsg", reqMsg);
result.put("reqMsg1", reqMsg1);
result.put("contentType", contentType);
result.put("cookie", cookie);
return JsonPrettifier.prettifyJson(result.toString());
}

13. @NotNull @NotEmpty @NotBlank 的区别

  • @NotNull 不能为 null,但可以为空字符串 ""
  • @NotEmpty 不能为 null,而且长度必须大于 0, 可以是 " "
  • @NotBlank 只能作用在字符串上,不能为 null,而且去掉前后空格后(trim),长度必须大于 0

@Aspect

@Aspect

@Aspect 需要与 @Componet 一起使用。 声明当前类是一个切面类。

@Pointcut

@Pointcut 用来定义一个切点。切入点定义了事件触发时机。

获取被代理类、所访问方法、请求参数信息

  • execution()
    • @Pointcut("execution(* com.wangzhy.controller..*.*(..))") com.wangzhy.controller 包下的任意方法。
  • annotation()
    • @Pointcut("@annotation(com.wangzhy.annotation.RateLimit)")
  1. @Around

  2. @Before

  3. @After

  4. @AfterReturning

@AfterReturning(pointcut="pointcut()",returning="result")
public void doAfterReturning(JoinPoint joinPoint,Object result){ // result 是方法的返回值

}
  1. @AfterThrowing
@AfterThrowing(pointcut = "pointCut()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
}

通常用 aop 实现权限校验、日志记录等功能,其实现原理是动态代理

@Import 注解

  1. 给Spring容器自动注入导入的类。
  2. 给容器导入一个ImportSelector

@InitBinder

@InitBinder : 用于自定义数据绑定的规则

在 Spring MVC 中,当接收到 HTTP 请求时,Spring 会将请求参数自动绑定到对应的 Java 对象中。 而有时候,我们需要对这些参数进行进一步的处理,例如转换数据类型或者对数据进行验证等操作。 这时,我们可以使用 @InitBinder 注解来自定义数据绑定的规则。

使用 @InitBinder 注解时,需要在控制器类中定义一个带有 WebDataBinder 参数的方法, 并在该方法上添加 @InitBinder 注解。WebDataBinder 是 Spring MVC 中的一个工具类, 它可以用于数据绑定、类型转换和验证等操作。在该方法中,可以通过 WebDataBinder 对象来 添加一些自定义的属性编辑器、格式化器或者验证器,从而实现对数据绑定过程的自定义控制。

/**
*
* 将前台传递过来的日期格式的字符串,自动转化为Date类型
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
// Date 类型转换
binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) {
setValue(DateUtil.parseDate(text));
}
});
}