一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是面试题,也是你 Java 知识点的扫盲贴。
回答在Spring中,@Enable*注解的作用是开启某种特定功能,以便简化开发配置流程。简单点来说就是@Enable*是一个开关,通过它我们可以非常方便的开启某种功能,例如AOP、事务、异步处理等等。比如:@EnableScheduling:开启任务调度。@EnableAsync:开启异步方法支持。@EnableTransactionManagement:开启事务管理。@Enable*背后的核心是组合式注解和扩展机制,它主要是通过@Import加载配置类或ImportSelector、ImportBeanDefinitionRegistrar动态注册相关Bean。详解我们已EnableTransactionManagement为例来讲解下@Enable*相关的原理。关于@Import相关的内容详见面试题:知道Spring中@lmport注解的作用吗?@Target(ElementType
回答@Import主要用于将指定的配置类注入到Spring容器中,它的作用类似于直接在@Configuration配置类中使用@Bean方法声明Bean,但它提供了一种更加灵活的方式。从简单的使用到高级的扩展,@Import能帮我们完成:导入配置类:把其他配置类加载进来,便于模块化管理。导入普通组件:直接将某个类(例如第三方库的类)注册为Bean。基于逻辑条件动态导入:通过实现ImportSelector或ImportBeanDefinitionRegistrar,根据需求动态注册Bean。详解@Import的三种用法注入普通类注入普通类,这种方式非常简单,如下:@Configuration@Import(SkService.class)publicclassSkServiceConfig{}publicclassSkService{publicvoiddoSomething(){Syst
回答Aware接口是Spring提供的一组标志性接口,所有以*Aware结尾的接口都具有"感知"的能力。通过实现这些接口,Spring会在Bean的生命周期中,自动将对应的容器资源(如ApplicationContext、BeanFactory等)注入到Bean中,从而使Bean能够与Spring容器进行交互。Aware接口的核心逻辑是依赖于BeanPostProcessor的实现。当Bean实现某个Aware接口时,Spring的ApplicationContextAwareProcessor会在Bean初始化后,调用对应的方法注入相关的资源。详解源码解析我们直接看Aware接口定义:/***Markersuperinterfaceindicatingthatabeaniseligibletobe*notifiedbytheSpringcontainerofapart
回答在Spring中,当一个接口有多个实现类时,需要通过一些特定的方法来明确指定注入的实现类,避免Spring在自动装配时出现歧义。目前主流的方式有两种:使用@Qualifier注解:在注入时指定具体的Bean名称。参考面试题:Spring中的@Qualifier注解的作用?使用@Primary注解:将一个实现类标记为默认优先注入的Bean。参考面试题:SpringBean如何设置为默认Bean?
回答Spring不推荐@Autowired字段注入而是构造器注入,主要是因为字段注入有如下几个缺点:容易违背了单一职责原则:使用基于字段注入的方式,添加依赖非常简单,要使用一个接口直接添加依赖即可,哪怕我们添加10个、20个也不觉得有什么问题,这就会导致过度使用,再加上对依赖的隐蔽性,会导致一个类承担过多的职责,很容易违背了单一职责原则。难以进行单元测试:由于与Spring容器强依赖,所以使得在不依赖Spring框架的情况下进行单元测试变得更加困难。不支持不可变性:字段注入不支持final字段,这意味着无法创建不可变的bean。关于Spring注入相关请参考面试题:Spring为什么建议使用构造器来注入?
回答首先我们需要明确@Component标注在类上,@Bean标注在方法上,如果同一个类里面既有@Component又有@Bean,那么Spring会同时将:该类本身作为一个Bean@Bean方法返回的对象也作为一个独立的Bean注册到容器中例如:@ComponentpublicclassSkService{@BeanpublicStringskJava(){return"ThisisaskJava!";}}在这种情况下:Spring容器会注册一个名为skService的Bean,该Bean对应SkService类的实例。Spring容器也会注册一个名为skJava的Bean,对应方法skJava()的返回值但是,在实际开发过程中,并不推荐@Component和@Bean搭配使用,而是使用@Configuration。
回答@Qualifier的主要作用事解决Spring容器中多实例注入时的歧义问题。当Spring容器中有多个同类型的Bean可供注入的时候,默认情况下Spring会不知道应该注入哪个,这个时候我们就可以通过@Qualifier明确指定需要注入哪个具体的Bean。详解在大多数情况下,我们都是使用@Autowired来实现Spring的依赖注入的,在默认情况下,Spring会根据类型(byType)查找匹配的Bean,如果Spring容器中有多个同类型的Bean,Spring会抛出异常:NoUniqueBeanDefinitionException:Noqualifyingbeanoftype[...]available:expectedsinglematchingbeanbutfound2:bean1,bean2具体情况请参考面试题:SpringBean如何设置为默认Bean?的多候选Bea
回答如果我们想将某个Bean设置为默认Bean,一般推荐使用@Primary。当应用中存在多个候选Bean的时候,Spring会优先选择标记了@Primary的那个作为注入的默认Bean。详解举个例子:@ComponentpublicclassSkServiceAimplementsSkService{//ImplementationofMyService}@Component@PrimarypublicclassSkServiceBimplementsSkService{//ImplementationofMyService}在这里,如果某个地方需要注入SkService,Spring会优先选择@Primary标注的SkServiceB。但是,如果我们使用了@Qualifier指定了具体的Bean名称,那么即使有@Primary,Spring也会注入指定的Bean。多候选Bean问题Sp
回答可以的。在Spring中,null和空字符串的注入有明确的语法支持。我们可以通过配置或者注解的方式注入null和空字符串。比如在XML中<null/>用于注入null,而空字符串可以直接通过""或者留空来注入。详解XML方式在XML中,<null/>是专门用于注入null的配置指令。Spring容器在解析配置时会自动将<null/>转换为Java的null。<beanid="userService"class="com.skjava.UserService"><propertyname="orderService"><null/></property></bean>对于空字符串也很好实现:<beanid=&
回答Spring事务中支持数据库事务的隔离级别有5种,他们定义了一个事务与其他事务如何交互,主要用来解决脏读、不可重复度和幻读问题。ISOLATION_DEFAULT:默认隔离级别,依赖于底层数据库的默认设置。ISOLATION_READ_UNCOMMITTED:允许读取未提交的数据。允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。ISOLATION_READ_COMMITTED:读已提交。保证一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻读。ISOLATION_REPEATABLE_READ:可重复读。可以防止脏读、不可重复读,但是可能出现幻读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读。ISOLATION_SERIA
回答在Spring中,目前最主流的管理事务的方式有两种,编程式事务和声明式事务。编程式事务编程式事务需要我们开发者手动编写代码来控制事务的开启、提交和回滚。我们可以利用TransactionTemplate或者PlatformTransactionManager来实现:TransactionTemplate是Spring提供的一种事务管理模板类,它封装了事务的开始、提交和回滚逻辑,我们只需要在模板中编写具体的业务代码就可以了。PlatformTransactionManager是Spring事务管理的核心接口,我们可以直接使用它来手动管理事务的生命周期。编程式事务的优点在于它控制的事务粒度非常精细,我们可以非常灵活地控制事务的范围、传播行为、回滚逻辑。缺点就是增加了代码的复杂度,同时事务的管理和业务代码也耦合了。所以,它比较适用于那些需要灵活控制事务边界的场景。声明式事务声明式事务是目前S
回答在Spring中同时使用@Transactional和@Async会导致事务失效。这是因为@Async会开启一个独立的线程来执行方法,而事务管理依赖于当前线程的上下文。当事务开启后,事务的上下文信息是绑定在当前线程上的。如果@Async切换了线程,那么事务的上下文就无法传播到新的线程中,导致事务不会生效。详细情况请参考这几篇面试题:为什么不建议直接使用Spring的@AsyncSpring的事务在多线程下生效吗?为什么?@Async注解一定会异步执行吗?@Transactional注解的失效场景
回答在Spring中,有多种方式创建Bean:通过XML配置文件创建Bean这是一种比较传统的方式,通过<bean>标签在XML配置文件中定义Bean的id和class属性。通过注解方式创建Bean利用注解@Component、@Service、@Repository或@Controller等注解来标记类,Spring会自动扫描并将其注册为Bean。通过Config配置类创建Bean基于Java的配置方式,结合@Configuration和@Bean注解,定义方法来返回实例,这些方法的返回值会被Spring容器管理为Bean。通过工厂方法创建Bean实现FactoryBean接口,我们可以自定义更加复杂创建Bean的逻辑。使用@Import方式使用@Import可以将外部类直接注册为Bean。@Import+ImportSelector方式ImportSelector是一个接口
回答可以的。这个Spring提供的一个非常实用的特性,这种特性支持将所有某种类型的Bean自动注入到一个Map中,Map的key是Bean的名称,value则是具备Bean实例。详解在实际应用中,如果我们有多个实现同一个接口的类,而我们又需要根据某些条件动态选择使用某个实现的时候,这种特性就特别有用了。如下:我们先定义一个接口:publicinterfaceSkService{voiddoSomething();}然后定义两个实现类:@Component("firstSkService")publicclassFirstSkServiceImplimplementsSkService{@OverridepublicvoiddoSomething(){System.out.println("FirstSkSerivce...");}}@Componen
回答在Spring中,事务的传播机制并不会在多线程环境下生效。这是因为Spring事务的实现是基于ThreadLocal。一个事务上下文与单个线程绑定。所以,如果我们在一个方法里面开启一个新的线程来执行任务,这个新线程是无法继承原有事务上下文的,会导致事务隔离。举个简单的例子,在一个主线程中开启了事务,而在这个事务过程中启动了一个新的线程,这个新线程的操作并不会受到主线程事务的控制。即使主线程事务回滚,子线程的事务依然会提交。详解以@Transaction为例。我们知道@Transaction的底层是基于AOP的,而TransactionInterceptor则负责拦截代理对象目标方法,在前后增加事务控制的逻辑,事务方法的执行最终都会由TransactionInterceptor的invoke()拦截增强:publicObjectinvoke(MethodInvocationinvocat
回答Spring支持根据特定条件来动态地创建Bean,主要有三种方式:使用@Conditional注解:通过条件化的方式在特定条件下创建Bean。使用@Profile注解:根据不同的环境条件来选择性地创建Bean。基于配置文件的条件:根据外部配置文件中的属性值来控制Bean的创建。这里第一种方案是最常见和优雅的方式。详解使用@Conditional注解@Conditional支持条件式创建Bean,它可以根据复杂条件来决定是否创建一个Bean。比如:@Configuration@ConditionalOnProperty(prefix="distributed.lock",value="redis",havingValue="1")publicclassRedisDistributedLock{}如果配置文件中distribute
回答在实际应用场景中,多环境配置是非常常见的一个场景,比如开发环境、测试环境、生产环境,各个环境的配置都不相同,而Spring就提供了一种非常优雅的方式来支持这种需求。即通过@Profile和外部配置文件(如application.yml或application.properties)来实现。即,我们可以根据不同的环境定义不同的Bean或者配置文件,然后通过激活特定环境来加载对应的配置,例如,我们可以在application.yml中定义多个环境配置块(如dev、test、prod),然后通过spring.profiles.active属性指定当前运行的环境。详解实现步骤Spring允许我们为不同的环境创建不同的配置文件,文件命名规则是application-{profile}.properties或application-{profile}.yml,其中{profile}是环境的名称,例
回答Spring利用三级缓存可以解决单例模式下的Setter注入或者字段注入方式的循环依赖,但是无法解决构造器注入的循环依赖。而使用@Lazy它可以破除循环依赖,但是我们也可以认为它没有解决,因为当@Lazy应用于一个Bean时,Spring容器会为它生成一个代理对象,然后将这个代理对象注入。所以,使用@Lazy确实是解决了循环依赖,但是在实际开发过程中不推荐使用@Lazy来解决循环依赖,出现了循环依赖问题属于类的设计问题,我们调整他们的结构来破除问题,而不是采用这种投机取巧的方式来规避问题。详解@Lazy详解在默认情况下,Spring容器会在应用程序启动时初始化所有的单例Bean。但是如果某个Bean使用了@Lazy,则Spring在启动阶段会暂时不对其进行实例化,而是在首次需要该Bean时再进行实例化。使用@Lazy的目的可以加快应用的启动速度,尤其时特别适用于一些只在特定场景下才会使
回答所谓缓存预热,是指在应用程序启动时提前加载和缓存一些常见的数据,以提高应用启动后的性能,避免在用户首次请求时再进行昂贵的计算或数据库查询。在Spring应用程序中,我们可以使用如下四种手段来做缓存预热:使用@PostConstruct注解使用CommandLineRunner或ApplicationRunner使用启动监听事件这里,大明哥推荐第三种方案,因为它解耦了缓存预热逻辑和应用启动流程,当然第二种方案也不错,但是不推荐第一种方案,将Bean的初始化进程与缓存预热耦合在一起,实在是不够优雅。详解使用@PostConstruct注解@PostConstruct是SpringBean生命周期注解,当我们使用该注解标注在某个Bean的方法上面时,Spring会确保在该Bean初始化完成之后执行该方法。那么,在这个方法中我们就可以调用服务层或者数据访问层的方法,将数据加载到缓存中。如下:@
回答在Spring中,@PostConstruct、init-method和afterPropertiesSet都是用于在Bean初始化阶段执行自定义初始化逻辑的方法。他们的执行顺序如下:@PostConstruct:首先是@PostConstruct执行。@PostConstruct属于JSR-250标准注解,为JavaEE标准中的注解。Spring管理Bean的生命周期时会自动识别@PostConstruct,并在属性注入完成之后执行。所以,@PostConstruct执行时机比其他Spring专有的初始化方法更早。afterPropertiesSet:是Spring专门为Bean的初始化设计的一个方法,定义在InitializingBean接口中,用来定义一些初始化逻辑。Spring在创建Bean的过程中会检查Bean是否实现了InitializingBean接口,如果实现了则在@P
回答在Spring中,@Service、@Component、@Repository都是用于用类表示为Spring容器中的Bean。从技术层面来讲,@Service、@Repository和@Controller都是@Component的衍生版本,他们主要在概念上面进行了优化,以体现特定的分层架构角色。@Component是Spring框架的基础注解,用于将类声明为Bean组件,使其受Spring容器管理。它可以用在任何层次的类上,并不限定它的应用层次。@Service用于表示业务逻辑层(Service)的类,尽管在功能上它和@Component没有本质的区别,但是它代表的是一个业务服务。在语义上,@Service表示这个Bean的业务层服务。@Repository用于表示持久层(DAO),主要用于数据库访问和操作。它除了标注类外一个DAO外,还有一个核心功能:当持久层操作发生异常时,Sp
回答不一定需要!Spring三级缓存是解决循环依赖根本所在,但是,并不是说每种情况下解决循环依赖都必须使用三级缓存。对于无代理的单例Bean,Spring也可以仅需要二级缓存就可以解决循环依赖问题。我们知道二级缓存是在创建实例后加入到缓存中的,如果要使用二级缓存来解决有代理场景下的循环依赖,我们只需要将AOP的代理工作提前到“提前暴露实例”的阶段执行就可以了,有就是说在创建实例阶段我们直接就创建代理对象,然后将代理对象放入到二级缓存中。但是这样设计的话就和SpringAOP的设计原则相驳:AOP的实现需要与bean的正常生命周期的创建分离;按照SpringAOP的设计,代理对象的生成应该是在创建实例、属性填充、初始化之后再生成的。所以,使用二级缓存是可以解决循环依赖,包括AOP的场景,但是,Spring为了遵循SpringAOP的设计原则,采用了三级缓存来解决循环依赖。详解关于三级缓存请阅
回答Spring中的三级缓存是一种用于解决循环依赖问题的机制,Spring通过三级缓存,让尚未完全初始化的Bean也能够注入到其他Bean中,以此来打破循环依赖的问题。在Spring中,有一级、二级、三级缓存:一级缓存:也叫单例池,为最常用的缓存,用于存放已经完成初始化的单例Bean。二级缓存:为早期对象缓存,用于存放还没有完全初始化好的的Bean实例。当一个Bean实例已经实例化了但还没有完全初始化时(例如,还没有执行完@PostConstruct注解的方法或者实现了InitializingBean接口的方法)时,它的实例会被放在这个缓存中。当其他的Bean需要依赖这个还没有完全完成初始化的Bean时,就可以直接从二级缓存中获取它的早期对象完成注入。三级缓存:为单例工厂缓存,用于存放创建Bean实例的工厂对象。当一个Bean的实例需要被创建时,Spring容器会先为该Bean对象创建一个
回答在Spring中,@Async用来标记一个方法为异步执行。当某个在某个方法上面使用@Async来标注,那么当应用执行该方法时,可能会以异步的方式执行该方法。那为什么不推荐直接使用它呢?这是因为Spring使用TaskExecutor作为异步方法执行的底层机制,@Async注解会告诉Spring将该方法的执行放入一个线程池中,而线程池是由TaskExecutor提供,如果我们直接使用@Async,不手动配置线程池,则Spring会使用默认的SimpleAsyncTaskExecutor,该线程池会在每次调用时创建一个新的线程,而不是复用线程池中的线程,这就会导致大量线程会被创建,导致资源浪费。详解SimpleAsyncTaskExecutor是Spring提供的一个TaskExecutor实现,它的主要特点是每次调用时都会创建一个新的线程,而不是使用线程池:publicvoidexecu
回答在Spring中,SpringEvent能够实现不同组件之间的解耦,其主要作用是在应用程序内部的组件之间进行消息的传递,使得组件可以松散耦合。简单来说,SpringEvent可以监听特定事件的发生,发布者只需要发布事件而不需要关系具体谁会来接收它,怎么处理它,这样就可以实现模块化和解耦了。在SpringEvent中,一般分为事件、事件发布者、事件监听器三大角色。每当事件发布者发布一个事件时,Spring会自动调用对应的监听器来处理该事件。这种机制的核心在于观察者模式,SpringEvent本质上是一个事件发布-订阅的设计模式实现。详解SpringEvent机制三个核心组成部分:事件、事件发布者和事件监听器。事件(Event):在Spring中,事件是由ApplicationEvent类或者它的子类来实现的。一个事件对象可以包含事件发生时的一些具体数据,供监听者来使用。事件发布者(App
回答在Spring中,ShutdownHook的作用是保证在应用程序关闭时能够执行一些必要的清理工作,比如关闭数据库连接、停止正在运行的任务等等其他类型的清理操作,这是应用程序在终止运行时比较优雅的释放资源的动作。ShutdownHook是Java的一个特性,Spring在其生命周期管理中利用这一特性,确保所有注册的bean在关闭时能够被适当地销毁。当Spring应用程序运行时,如果它接收到关闭信号,JVM会执行已注册的ShutdownHook,Spring会在其上下文关闭时,调用所有注册bean的destroy()方法,确保所有资源都能够得到正确释放。详解ShutdownHook介绍ShutdownHook是Java提供的一种机制,它允许我们在JVM关闭时执行一些特定的操作,比如释放资源、保存状态等等,这是一种在程序终止之前进行清理工作的比较有效的方式。我们可以将ShutdownHook
回答@Autowired和@Resource都是用于实现依赖注入的注解,虽然作用一样,但是他们还是存在一些差异。@Autowired是Spring框架提供的注解,主要用于根据类型自动装配Bean。它可以与构造函数、方法或字段一起使用,并且支持可选的属性设置,我们可以添加required=false,这就表明如果找不到依赖,Spring不会抛出异常@Resource是JavaEE规范中的注解,它首先会根据名称进行装配,如果找不到匹配的Bean,才会根据类型进行装配,如果还找不到就会抛出异常。总的来说就是,一个是Spring特有的,一个属于JSR-250标准,适用于所有的JavaEE环境。详解@Autowired@Autowired是Spring提供的注解,它只按照byType方式注入,在默认情况下它要求依赖对象必须存在,如果允许不存在,则我们可以配置它的required属性为false。然而
回答过滤器和拦截器是我们JavaWeb开发中常用的两个功能,虽然他们的用途相似,都是用于对请求进行预处理和后置垂类的,但是他们还是存在如下5个不同之处:实现原理不同过滤器基于JavaServletAPI实现,需实现javax.servlet.Filter接口,并由Servlet容器管理;而拦截器是Spring框架提供的,通常通过实现HandlerInterceptor接口,依赖于Spring上下文。使用范围不同过滤器适用于整个Web应用,能够处理所有HTTP请求,常用于全局性功能;拦截器主要用于SpringMVC,针对特定控制器请求,适合处理业务逻辑相关的操作。触发时机不同过滤器在请求到达Servlet之前和响应返回之前触发,适用于预处理和后处理;拦截器在控制器方法执行前后触发,更专注于控制器逻辑的处理。执行顺序不同过滤器的执行顺序由web.xml配置或SpringBoot设置决定,多个过
回答@Component和@Bean两个注解都是用于将对象注册到Spring容器中,作用相同,但是他们的使用场景和方式存在一些区别:@Component:是一种类级别的注解,用于自动检测和注入Spring容器管理的组件,用于自动检测和注入Spring容器管理的组件。它可以和@Service、@Repository等注解一起使用,配合@ComponentScan扫描路径时自动将Bean注入容器。@Component适用于我们自定义的类。@Bean:是一种用于方法级别的注解,用于将方法返回的对象注册为Spring容器的一个Bean。通常情况下,它需要和@Configuration注解配合使用,用于配置第三方框架中的类。详解区别详解一、注解使用位置不同@Component作用于类上,比如我们写的一个Service类。Spring容器会通过扫描的方式自动检测到带有@Component注解的类,将其
回答在Spring中@Async表示执行异步操作,但是使用了它并不一定会让方法异步执行,因为它依赖于SpringAOP代理机制,所以它需要满足几个条件:启用异步支持。我们需要在配置类上面标注@EnableAsync,告诉Spring容器启用异步功能。方法必须为public,且不能被同一个类内部直接调用。因为@Async依赖SpringAOP代理机制。@Async的方法必须为void或者Future详解Spring中@Async注解基于SpringAOP实现异步调用,运行时会创建一个代理对象,当方法被调用时,代理会将方法交给线程池执行,而不是在主线程中执行。一、启用异步支持异步执行在Spring中并不是默认开启的,所以要启用它就必须在配置类或者主启动类上添加@EnableAsync注解,显式启用异步支持。@EnableAsync就是告诉Spring容器,要它去扫描和识别@Async注解的方法