文章目录
- 1. 介绍一下Spring的IOC
- 2. 介绍一下Spring的AOP
- 3. 为什么Spring不建议使用基于字段的依赖注入?
- 4. Spring支持哪些注入方式?
- 5. Spring Bean的生命周期是怎么样的?
- 6. @PostConstruct、init-method和afterPropertiesSet执行顺序
- 7. Spring的事务传播机制有哪些?
- 8. Autowired和Resource的关系?
- 9. BeanFactory和FactroyBean的关系?
- 10. Spring在业务中常见的使用方式?
- 11. Spring中如何开启事务?
- 12. Spring中用到了哪些设计模式?
- 13. 什么是Spring的循环依赖问题?
- 14. 什么是Spring的三级缓存?
- 15. Spring事务失效可能是哪些原因?
- 16. SpringMVC是如何将不同的Request路由到不同Controller中的?
- 17. Springboot是如何实现自动配置的?
- 18. SpringBoot的启动流程是怎么样的?
- 19. Spring的AOP在什么场景下会失效?
- 20. SpringBoot和Spring的区别是什么?
- 21. 在Spring中如何使用Spring Event做事件驱动?
- 22. 为什么不建议直接使用Spring的@Async?
- 23. Spring 中的 Bean 是线程安全的吗?
- 24. Spring中如何实现多环境配置?
- 25. 如何自定义一个starter?
- 26. 如何根据配置动态生成Spring的Bean?
- 27. SpringBoot和传统的双亲委派有什么不一样吗?
参考:
https://www.yuque.com/hollis666、https://www.mianshiya.com、https://javaguide.cn
1. 介绍一下Spring的IOC
所谓的IOC(inversion of control),就是控制反转的意思。
在传统的程序设计中,应用程序代码通常控制着对象的创建和管理。例如,一个对象需要依赖其他对象,那么它会直接new出来对象。
而在IOC 中,控制关系发生了反转。控制权被转移到Spring容器中,容器负责创建和管理对象,并在需要的时候将它们注入到应用程序中。
所以,原来这个对象的控制权在我们的代码中,我们自己new的对象,在Spring中,应用程序不再控制对象的创建,而是被动地接受由容器注入的对象。
2. 介绍一下Spring的AOP
Spring 的 AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架中的一项核心特性,用于将应用中的横切关注点与业务逻辑解耦。横切关注点通常指的是日志记录、安全控制、事务管理、性能监控等,它们往往会散落在多个业务模块中,造成代码冗余和维护困难。AOP 的核心思想是通过“切面”将这些公共逻辑从业务代码中抽离出来,并在程序运行时以动态代理或字节码增强的方式将其织入到目标对象的方法调用中。
3. 为什么Spring不建议使用基于字段的依赖注入?
单一职责问题
根据SOLID设计原则来讲,一个类的设计应该符合单一职责原则,就是一个类只能做一件功能,当我们使用基于字段注入的时候,随着业务的暴增,字段越来越多,我们是很难发现我们已经默默中违背了单一职责原则的。
但是如果我们使用基于构造器注入的方式,因为构造器注入的写法比较臃肿,所以它就在间接提醒我们,违背了单一职责原则,该做重构了。
可能产生NPE
对于一个bean来说,它的初始化顺序为:
静态变量或静态语句块 -> 实例变量或初始化语句块 -> 构造方法 -> @Autowired
所以,在静态语句块,初始化语句块,构造方法中使用Autowired表明的字段,都会引起NPE问题
@Component
class Test {
@Autowired
private Bean bean;
private final String beanName;
public Test() {
// 此时bean尚未被初始化,会抛出NPE
this.beanName = bean.getName();
}
}
不利于测试
使用了Autowired注解,说明这个类依赖了Spring容器,这让我们在进行UT的时候必须要启动一个Spring容器才可以测试这个类,显然太麻烦,这种测试方式非常重,对于大型项目来说,往往启动一个容器就要好几分钟,这样非常耽误时间。
4. Spring支持哪些注入方式?
字段注入
@Autowired
private Bean bean;
构造器注入
@Component
class Test {
private final Bean bean;
@Autowired
public Test(Bean bean) {
this.bean = bean;
}
}
setter注入
@Component
class Test {
private Bean bean;
@Autowired
public void setBean(Bean bean) {
this.bean = bean;
}
}
5. Spring Bean的生命周期是怎么样的?
实例化:Spring容器根据配置文件或注解实例化Bean对象。属性注入:Spring将依赖(通过构造器、setter方法或字段注入)注入到Bean实例中。初始化前的扩展机制:如果Bean实现了BeanNameAware等aware接口,则执行aware注入。初始化前(BeanPostProcessor):在Bean初始化之前,可以通过BeanPostProcessor接口对Bean进行一些额外的
处理。初始化:调用InitializingBean接口的afterPropertiesSet方法或通过init-method属性指定的初始化方法。初始化后(BeanPostProcessor):在Bean初始化后,可以通过BeanPostProcessor进行进一步的处理。使用Bean:Bean已经初始化完成,可以被容器中的其他Bean使用。销毁:当容器关闭时,Spring调用DisposableBean接口的destroyO方法或通过destroy-method属性指定的销毁方
法。

6. @PostConstruct、init-method和afterPropertiesSet执行顺序
在Spring框架中,使用@PostConstruct、自定义的init-method方法,和InitializingBean接口和afterPropertiesSet方法都是用于在Bean初始化阶段执行特定方法的方式。他们的执行顺序是:构造函数>@PostConstruct > afterPropertiesSet > init-method
7. Spring的事务传播机制有哪些?
Spring的事务规定了7种事务的传播级别,默认的传播机制是REQUIRED
REQUIRED,如果不存在事务则开启一个事务,如果存在事务则加入之前的事务,总是只有一个事务在执行REQUIRES_NEW,每次执行新开一个事务,如果当前存在事务,则把当前事务挂起SUPPORTS,有事务则加入事务,没有事务则普通执行NOT_SUPPORTED,有事务则暂停该事务,没有则普通执行MANDATORY,强制有事务,没有事务则报异常NEVER,有事务则报异常NESTED,如果之前有事务,则创建嵌套事务,嵌套事务回滚不影响父事务,反之父事务影响嵌套事务
8. Autowired和Resource的关系?
支持方不同
Autowired是Spring提供的自动注入注解,只有Spring容器会支持。
Resource是JDK官方提供的自动注入注解(JSR-250)。它等于说是一个标准或者约定,所有的IOC容器都会支持这个注解。
byName和byType匹配顺序不同
Autowired在获取bean的时候,先是byType的方式,再是byName的方式。
Resource在获取bean的时候,和Autowired恰好相反,先是byName方式,然后再是byType方式。
作用域不同
Autowired可以作用在构造器,字段,setter方法上
Resource 只可以使用在字段,setter方法上,不支持构造器注入
可选性不同
Autowire可以通过 required = false属性设置依赖bean可以不必须存在。如果找不到要注入 Bean 时会注入 null,不报错。
Resource没有直接的 required属性。用它注入的Bean必须存在。如果找不到匹配的 Bean,会抛出异常。
9. BeanFactory和FactroyBean的关系?
BeanFactory是Spring IoC容器的一个接口,用来获取Bean以及管理Bean的依赖注入和生命周期。
FactoryBean是一个接口,用于定义一个工厂Bean,它可以产生某种类型的对象。当在Spring配置文件中定义一个Bean时,如果这个Bean实现了FactoryBean接口,那么Spring容器不直接返回这个Bean实例,而是返回FactoryBean#getObject()方法所返回的对象。
10. Spring在业务中常见的使用方式?
通过IoC实现策略模式
Spring IoC 可以通过 @Autowired 或 @Resource 自动注入所有策略实现类,然后通过 Map<String, Strategy> 选择使用。
public interface PaymentStrategy {
void pay();
}
@Component("alipay")
public class AliPayStrategy implements PaymentStrategy {
@Override
public void pay() {
System.out.println("支付宝支付");
}
}
@Component("wechat")
public class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay() {
System.out.println("微信支付");
}
}
@Service
public class PaymentService {
@Autowired
private Map<String, PaymentStrategy> paymentStrategies;
public void pay(String type) {
paymentStrategies.get(type).pay();
}
}
通过 IoC 容器自动管理和注入策略类,避免了 if-else 逻辑,提升可扩展性。
通过AOP实现拦截
- 参数校验
@Target({ ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamsCheck {
boolean ignore() default false;
}
@Aspect
@Component
public class ValidateAspect {
@Around("@annotation(com.hollis.annotation.ParamCheck)")
public void ParamCheckAround(JoinPoint joinPoint) throws Throwable {
// 判断是否需要校验
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
ParamsCheck paramsCheckAnnotation = method.getAnnotation(ParamsCheck.class);
if (paramsCheckAnnotation != null && paramsCheckAnnotation.ignore()) {
return joinPoint.proceed();
}
Object[] objects = joinPoint.getArgs();
for (Object arg : objects) {
if (arg == null) {
break;
}
// 校验参数,失败抛出异常
}
}
}
- 日志打印
@Aspect
@Component
public class LogAspect {
@Around("@annotation(org.springframework.web.bind.annotation.GetMapping)")
public Object logExecution(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
System.out.println("方法 " + joinPoint.getSignature() + " 执行耗时: " + (end - start) + "ms");
return result;
}
}
通过Event异步解耦
// 定义事件
public class OrderCreatedEvent extends ApplicationEvent {
public OrderCreatedEvent(Object source) {
super(source);
}
}
// 发布事件
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher publisher;
public void createOrder() {
System.out.println("订单创建成功");
publisher.publishEvent(new OrderCreatedEvent(this));
}
}
// 监听事件
@Component
public class NotificationListener {
@EventListener
@Async
public void onOrderCreated(OrderCreatedEvent event) {
System.out.println("发送通知:订单已创建");
}
}
通过Spring管理事务
@Component
public class TransactBean {
@Autowired
private TransactionTemplate transactionTemplate;
public void test() {
transactionTemplate.execute((status) -> {
// doTransaction
return null;
});
}
}
11. Spring中如何开启事务?
编程式事务
@Autowired
protected TransactionTemplate transactionTemplate;
public void test(){
return transactionTemplate.execute(transactionStatus -> {
//事务操作
});
}
public void test1(){
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
}
});
}
声明式事务
@Transactional
public void test() {
// 事务操作
}
12. Spring中用到了哪些设计模式?
单例模式:Spring 的Bean 默认是单例模式,通过Spring 容器管理Bean的生命周期,保证每个Bean只被创建-
次,并在整个应用程序中重用。工厂模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建并管理Bean对象。代理模式:SpringAOP基于动态代理技术,使用代理模式实现切面编程,提供了对AOP编程的支持。观察者模式:Spring中的事件机制基于观察者模式,通过ApplicationEventPublisher发布事件,由
ApplicationListener监听事件,实现了对象间的松耦合。模板方法模式:Spring中的JdbcTemplate使用了模板方法模式,将一些固定的流程封装在父类中,子类只需实现一些
抽象方法即可。策略模式:Spring中的Handlerlnterceptor和HandlerExecutionChain使用了策略模式,允许开发者自定义处理器拦
截器,按照一定顺序执行。责任链模式:Spring中的过滤器和拦截器使用了责任链模式,多个过滤器和拦截器按照一定顺序执行,每个过滤器和拦
截器可以拦并做出相应的处理
13. 什么是Spring的循环依赖问题?
在Spring框架中,循环依赖是指两个或多个bean之间相互依赖,形成了一个循环引用的情况。如果不加以处理,这种情况会导致应用程序启动失败。
在Spring中,解决循环依赖的方式就是引入了三级缓存。
在SpringBoot 2.6以前,是默认支持循环依赖的,但是在 SpringBoot 2.6 开始,默认已经不开启对循环依赖的支持了
14. 什么是Spring的三级缓存?
在Spring的BeanFactory体系中,BeanFactory是Spring IoC容器的基础接口,其DefaultSingletonBeanRegistry类实现了BeanFactory接口,并且维护了三级缓存:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
//一级缓存,保存完成的Bean对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//三级缓存,保存单例Bean的创建工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//二级缓存,存储"半成品"的Bean对象
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
}
singletonObjects是一级缓存,存储的是完整创建好的单例bean对象。 在创建一个单例bean时,会先从singletonObjects中尝试获取该bean的实例,如果能够获取到,则直接返回该实例,否则继续创建该bean。
earlySingletonObjects是二级缓存,存储的是尚未完全创建好的单例bean对象。 在创建单例bean时,如果发现该bean存在循环依赖,则会先创建该bean的"半成品"对象,并将"半成品"对象存储到earlySingletonObjects中。当循环依赖的bean创建完成后,Spring会将完整的bean实例对象存储到singletonObjects中,并将earlySingletonObjects中存储的代理对象替换为完整的bean实例对象。这样可以保证单例bean的创建过程不会出现循环依赖问题。
singletonFactories是三级缓存,存储的是单例bean的创建工厂。 当一个单例bean被创建时,Spring会先将该bean的创建工厂存储到singletonFactories中,然后再执行创建工厂的getObject()方法,生成该bean的实例对象。在该bean被其他bean引用时,Spring会从singletonFactories中获取该bean的创建工厂,创建出该bean的实例对象,并将该bean的实例对象存储到singletonObjects中。
15. Spring事务失效可能是哪些原因?
@Transactional 应用在非 public 修饰的方法上
public class MyService {
@Transactional
private void doInternal() {
System.out.println("Doing internal work...");
}
}
private方法,只会在当前对象中的其他方法中调用,也就是会进行对象的自调用,这种情况是用this调用的,并不会走到代理对象,而@Transactional是基于动态代理实现的,所以代理会失效。
同一个类中方法调用,导致@Transactional失效
public class MyService {
public void doSomething() {
doInternal(); // 自调用方法
}
@Transactional
public void doInternal() {
System.out.println("Doing internal work...");
}
}
final、static方法
由于AOP是通过创建代理对象来实现的,而无法对final方法进行子类化和覆盖,所以无法拦截这些方法。
还有就是调用static方法,因为这类方法是属于这个类的,并不是对象的,所以无法被AOP。
异常被catch捕获导致@Transactional失效
public class MyService {
@Transactional
public void doSomething() {
try{
doInternal();
}catch(Exception e){
logger.error(e);
}
}
}
因为异常被捕获,所以就没办法基于异常进行rollback了,所以事务会失效。
事务中用了多线程
@Service
public class OrderService {
@Transactional
public void createOrder() {
saveOrder(); // 数据库操作
new Thread(() -> updateStock()).start(); // 开新线程
}
public void updateStock() {
// 更新库存
}
}
如果是我们常用的@Transactional这种声明式事务的话,在多线程情况下是无法生效的。主要是因为@Transactional 的事务管理使用的是 ThreadLocal 机制来存储事务上下文,而 ThreadLocal 变量是线程隔离的,即每个线程都有自己的事务上下文副本。因此,在多线程环境下,Spring 的声明式事务会“失效”,即新线程中的操作不会被包含在原有的事务中。
16. SpringMVC是如何将不同的Request路由到不同Controller中的?
客户端请求:用户通过浏览器发送HTTP请求,所有请求都被DispatcherServlet接收。请求映射:DispatcherServlet根据配置的处理器映射器(HandlerMapping)查找与请求URL对应的控制器
(Controller)。调用控制器方法:找到控制器后,DispatcherServlet将请求转发给对应的控制器方法进行处理。控制器方法处理业务逻
辑后,通常返回一个ModelAndView对象,包含数据模型和视图名称。视图解析:DispatcherServlet根据控制器返回的视图名称,使用视图解析器(ViewResolver)将逻辑视图名称解析为实
际的视图(如JSP、Thymeleaf等)。视图渲染返回:视图渲染引擎根据数据模型渲染视图,并将生成的HTML响应返回给客户端
SpringMVC还支持返回JSON数据响应,用于构建RESTfulWeb服务,因此上述视图解析部分,如果是JSON响应,则SpringMVC会将对象序列化为JSON,后续返回给客户端。

17. Springboot是如何实现自动配置的?
SpringBoot的自动配置是通过@EnableAutoConfiguration注解实现,这个注解包含
@Import({AutoConfigurationImportSelector.class})注解,导入的这个类会去扫描classpath下所有的META-
INF/spring.factories中的文件,根据文件中指定的配置类加载相应的Bean的自动配置。
这些 Bean 通常会使用@ConditionalonClass、@ConditionalonMissingBean、@ConditionalonProperty 等条件注解。来控制
自动配置的加载条件,例如仅在类路径中存在某个类时,才加载某些配置。
18. SpringBoot的启动流程是怎么样的?
1.启动main方法
- 应用从main()方法启动,并通过SpringApplication.run()引导应用启动。
2.创建SpringApplication
- 应用会创建 SpringApplication对象,推断应用类型、设置初始化器、设置启动监听器、确定主应用类。
3.准备环境(ConfigurableEnvironment)
- SpringBoot在启动过程中准备应用环境,加载配置文件、系统环境变量以及命令行参数。
4.创建并刷新ApplicationContext
- 创建应用上下文,加载配置类和自动配置类,注册Bean并执行依赖注入等初始化操作。
5.在刷新上下文时启动嵌入式Web服务器
- 对于Web应用,SpringBoot会自动启动嵌入式Web容器(如Tomcat),并注册相关的Servlet和Filter。
6.发布应用已启动事件
- 对应监听stated事件逻辑会被触发。
7.执行CommandLineRunner和ApplicationRunner
- 在应用启动完成后,执行实现了CommandLineRuner 和 ApplicationRunner 接口的初始化逻辑。
8.发布ready事件、应用启动完成
- 触发ApplicationReadyEvent,应用进入运行状态,处理业务请求或任务。

19. Spring的AOP在什么场景下会失效?
- 私有方法调用
- 静态方法调用
- final方法调用
- 类内部自调用
20. SpringBoot和Spring的区别是什么?
Spring是一个非常强大的企业级Java开发框架,提供了一系列模块来支持不同的应用需求,如依赖注入、面向切面编程、事务管理、Web应用程序开发等。而SpringBoot的出现,主要是起到了简化Spring应用程序的开发和部署,特别是用于构建微服务和快速开发的应用程序。
相比于Spring,SpringBoot主要在这几个方面来提升了我们使用Spring的效率,降低开发成本:
自动配置:Spring Boot通过Auto-Configuration来减少开发人员的配置工作。我们可以通过依赖一个starter就把一坨东西全部都依赖进来,使开发人员可以更专注于业务逻辑而不是配置。内嵌Web服务器:Spring Boot内置了常见的Web服务器(如Tomcat、Jetty),这意味着您可以轻松创建可运行的独立应用程序,而无需外部Web服务器。约定大于配置:SpringBoot中有很多约定大于配置的思想的体现,通过一种约定的方式,来降低开发人员的配置工作。如他默认读取spring.factories来加载Starter、读取application.properties或application.yml文件来进行属性配置等。
21. 在Spring中如何使用Spring Event做事件驱动?
Spring Event的使用需要定义以下三个内容:
事件(Event):事件是一个普通的Java对象,用于封装关于事件发生的信息。通常,事件类会包含一些数据字段,以便监听器能够获取事件的相关信息。事件发布者(Event Publisher):事件发布者是负责触发事件并通知所有注册的监听器的组件。事件监听器(Event Listener):事件监听器是负责响应特定类型事件的组件。它们实现了一个接口或者使用注解来标识自己是一个事件监听器,并定义了在事件发生时需要执行的逻辑。
假设我们在用户注册成功之后,需要发送一条欢迎短信,那么就可以通过事件来实现。
首先定义一个Event,需要继承ApplicationEvent类。
public class RegisterSuccessEvent extends ApplicationEvent {
public RegisterSuccessEvent(RegisterInfo registerInfo) {
super(registerInfo);
}
}
然后再定义一个事件的监听者:
/**
* 案件中心内部事件监听器
*
* @author Hollis
*/
@Component
public class RegisterEventListener {
@EventListener(RegisterSuccessEvent.class)
//@Async
public void onApplicationEvent(RegisterSuccessEvent event) {
RegisterInfo registerInfo = (RegisterInfo) event.getSource();
//执行发送欢迎短信的逻辑
}
}
以上这个监听器,在监听到一个RegisterSuccessEvent事件被发出来之后,会调用onApplicationEvent方法,我们就可以在这个方法中实现我们的发送欢迎短信的业务逻辑。
有了监听者,那么发送者也需要定义:
@Service
public class RegisterService{
@Autowired
protected ApplicationContext applicationContext;
public RegisterResponse register(RegisterInfo registerInfo){
//用户注册核心代码
//发送一个注册完成的事件
applicationContext.publishEvent(new RegisterSuccessEvent(registerInfo));
}
}
默认情况下,Spring Event的调用时同步调用的。如果想要实现异步调用,也是支持的,最简单的方式就是借助@Async 注解,但是,一般来说不建议大家直接用@Async,最好是自定义线程池来实现异步
22. 为什么不建议直接使用Spring的@Async?
@Async中关于线程池的使用部分在AsyncExecutionInterceptor中,在这个类中有一个getDefaultExecutor方法, 当我们没有做过自定义线程池的时候,就会用SimpleAsyncTaskExecutor这个线程池。
@Override
protected Executor getDefaultExecutor(BeanFactory beanFactory) {
Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
}
SimpleAsyncTaskExecutor 并不是真的线程池,它是不会重用线程的,每次调用都会创建一个新的线程,也没有最大线程数设置。并发大的时候会产生严重的性能问题。
所以,我们应该自定义线程池来配合@Async使用,而不是直接就用默认的。
@Configuration
@EnableAsync
public class AsyncExecutorConfig {
@Bean("registerSuccessExecutor")
public Executor caseStartFinishExecutor() {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("registerSuccessExecutor-%d").build();
ExecutorService executorService = new ThreadPoolExecutor(100, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
return executorService;
}
}
/**
* 案件中心内部事件监听器
*
* @author Hollis
*/
@Component
public class RegisterEventListener {
@EventListener(RegisterSuccessEvent.class)
@Async("registerSuccessExecutor")
public void onApplicationEvent(RegisterSuccessEvent event) {
RegisterInfo registerInfo = (RegisterInfo) event.getSource();
//执行发送欢迎短信的逻辑
}
}
在@Async中指定registerSuccessExecutor即可。这样在后续执行时,就会用到我们自定义的线程池了。
23. Spring 中的 Bean 是线程安全的吗?
Prototype的Bean是线程安全的,无状态的Singleton的Bean是线程安全的。有状态的Singleton的Bean是非线程安全的。
Spring的Bean是否线程安全,这个要取决于他的作用域。Spring的Bean有多种作用域,其中用的比较多的就是Singleton和Prototype。
默认情况下,Spring Bean 是单例的(Singleton)。这意味着在整个 Spring 容器中只存在一个 Bean 实例。
如果将 Bean 的作用域设置为原型的(Prototype) ,那么每次从容器中获取 Bean 时都会创建一个新的实例。对于Prototype这种作用域的Bean,他的Bean 实例不会被多个线程共享,所以不存在线程安全的问题。
但是对于Singleton的Bean,就可能存在线程安全问题了,但是也不绝对,要看这个Bean中是否有共享变量。
如以下Bean:
@Service
public class CounterService {
private int count = 0;
public int increment() {
return ++count;
}
}
默认情况下,Spring Bean 是单例的,count字段是一个共享变量,那么如果多个线程同时调用 increment 方法,可能导致计数器的值不正确。那么这段代码就不是线程安全的。
我们通常把上面这种Bean叫做有状态的Bean,有状态的Bean就是非线程安全的,我们需要自己来考虑他的线程安全性问题。
那如果一个Singleton的Bean中是无状态的,即没有成员变量,或者成员变量只读不写,那么他就是个线程安全的。
24. Spring中如何实现多环境配置?
我们平常在开发的时候,会有多套环境,开发环境、测试环境、预发布环境以及生产环境,不同的环境中应用的配置可能不太一样。那么在如何利用Spring来管理这些配置呢?
定义 Profiles:在配置类或配置文件中定义不同的 Profiles,例如使用 @Profile(“dev”) 在 Java 配置类上。
@Configuration
@Profile("dev")
public class DevConfig {
// 开发环境的特定配置
}
@Configuration
@Profile("prod")
public class ProdConfig {
// 生产环境的特定配置
}
激活 Profile:可以通过多种方式激活 Profile,如设置环境变量、JVM 参数、Web 应用的上下文参数或在 application.properties 文件中使用 spring.profiles.active 属性。
环境变量:SPRING_PROFILES_ACTIVE=prod
JVM 参数:-Dspring.profiles.active=prod
25. 如何自定义一个starter?
在Spring Boot中,创建一个自定义starter可以简化特定功能或组件的配置过程,让其他项目能够轻松地重用这些功能。
26. 如何根据配置动态生成Spring的Bean?
基于条件的 Bean 注册
Spring提供了@Conditional(或者@CondationOnProperties)注解,允许你在满足特定条件时才创建Bean。你可以定义自己的条件,这些条件实现了Condition接口。
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("my.condition.enabled", Boolean.class, false);
}
}
@Configuration
public class MyConfiguration {
@Bean
@Conditional(MyCondition.class)
public MyBean myBean() {
return new MyBean();
}
}
my.condition.enabled可以通过配置文件或者配置中心进行配置,然后当my.condition.enabled属性为true时,MyBean才会被创建。
使用@ConfigurationProperties
@ConfigurationProperties注解可以将配置文件中的属性绑定到Bean的属性上,然后就可以基于他做动态配置了。
@ConfigurationProperties(prefix = "app.datasource")
public class DataSourceProperties {
private String url;
private String username;
private String password;
// 省略getters和setters
}
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceConfig {
@Autowired
private DataSourceProperties properties;
@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl(properties.getUrl());
dataSource.setUsername(properties.getUsername());
dataSource.setPassword(properties.getPassword());
return dataSource;
}
}
编程式Bean注册
我们还可以通过实现BeanDefinitionRegistryPostProcessor接口来编程式地注册Bean。
public class DynamicBeanRegistrar implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (满足某个条件) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(MyDynamicBean.class);
registry.registerBeanDefinition("myDynamicBean", beanDefinition);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 通常不需要在这个方法中实现任何逻辑
}
}
使用@Profile
@Profile注解允许根据活动的Spring Profiles来创建Bean。一般是用于区分开发、测试和生产环境的。
@Configuration
public class ProfileConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
// 返回生产环境的DataSource实例
}
}
27. SpringBoot和传统的双亲委派有什么不一样吗?
传统的采用双亲委派的类加载机制大家都知道,要加载一个类的额时候,先委托父类加载器加载该类;如果父类加载器无法找到(类不存在),才由当前 ClassLoader 加载;这样保证了核心类库(rt.jar)不会被重复加载,避免了类冲突问题。
Spring Boot 打破了传统的双亲委派模型,采用 自定义的 LaunchedURLClassLoader 进行类加载,主要为了支持 “Fat JAR”(可执行 JAR) 的运行。
在 Spring Boot 中,使用 Maven 或 Gradle 构建项目时,lib/ 目录中的第三方依赖是以 JAR 形式打包进主 JAR 内部,默认会生成一个包含所有依赖项的 fat jar。
传统的 Application ClassLoader 只能从 外部 classpath 加载类,无法直接加载 JAR 包内嵌的其他 JAR(fat jar)。 因此 Spring Boot 需要自定义类加载器。
为了支持 Fat JAR 运行模式,Spring Boot 使用 LaunchedURLClassLoader 替代 AppClassLoader,打破双亲委派机制,核心做法是:
- 先加载 BOOT-INF/classes 目录下的应用类(优先于 JDK 类)。
- 再加载 BOOT-INF/lib/ 目录下的依赖 JAR(传统 AppClassLoader 无法加载嵌套 JAR)。
- 最后才交给父类加载器(即 JDK 提供的 AppClassLoader)。
也就是说Spring Boot 先尝试加载自身的类和依赖 JAR,找不到才交给父类加载器,从而打破双亲委派。

482

被折叠的 条评论
为什么被折叠?



