1. Spring的优点
- 轻量级和非侵入性:不需要引入大量的依赖和配置。
- 面向切面编程:Spring提供了强大的面向切面编程,允许用户定义横切关注点,并将其与核心业务逻辑分离,提高了灵活性。
- 依赖注入(DI)和控制反转:Spring的核心容器是IOC,他实现了依赖注入模式,通过配置文件或者注解来管理对象之间的依赖关系,降低了耦合度,提高了代码的可维护性和可测试性。
- 拥有大量的生态和活跃的社区。
2. Spring IOC容器初始化过程
一共有四个阶段,分别是启动、Bean定义注册、实例化和依赖注入、初始化。
- 启动阶段
- 配置加载:加载配置文件或者配置类,IOC容器首先需要加载应用程序的配置信息,这些配置信息可以是XML配置文件、Java配置类或者注解配置等方式。
- 创建容器:Spring创建IOC容器(BeanFactory、ApplicationContext),准备加载和管理Bean。
- Bean定义注册阶段
- 解析和注册:BeanDefinitionReader读取解析配置中的Bean定义,并将其注册到容器中,形成BeanDefinition对象。
- 实例化和依赖注入
- 实例化:根据BeanDefinition创建Bean实例
- 依赖注入:根据BeanDefinition中的依赖关系,可以通过构造函数注入、Setter注入或者字段注入、将依赖注册到Bean中。
- 初始化
- BeanPostProcessor:这些处理器会在Bean初始化生命周期中加入定义的处理逻辑,postProcessBeforeInitialization和postProcessAfterInitialization
- Aware接口调用:如果Bean实现了Aware接口(如BeanNameAware、BeanFactoryAware),Spring会回调这些接口,传递容器相关信息。
- 初始化方法调用:调用Bean的初始化方法(如通过@PostConstruct注解标注的方法,或者实现InitializingBean接口的bean会被调用afterPropertiesSet的方法)。
3. Spring Bean注册到容器有哪些方式
Spring Bean注册到容器的方式有以下几种
- 基于XML的配置:使用XML文件配置Bean,并定义Bean的依赖关系
- 基于@Component注解及其衍生注解:@Component、@Service、@Controller、@Repository
- 基于@Configuration(声明配置类)、@Bean(注解定义Bean)注解
- 基于@Import注解:@Import可以将普通类导入到Spring容器中,这些类会自动被注册为Bean。
4. @Qualifier注解作用
@Qualifier注解在Spring的主要作用是用于在依赖注入时消除歧义,当一个类型有多个实现时,@Qualifier注解可以指定需要注入哪一个具体的Bean。
例如,当Service有多个实现类的时候,可以通过@Qualifier指定名称选择对应的实现Bean。
@Component
public class Client {
private final Service service;
@Autowired
public Client(@Qualifier("serviceImpl1") Service service) {
this.service = service;
}
public void doSomething() {
service.serve();
}
}
扩展@Primary
@Primary注解用于指定当有多个候选Bean时默认注入哪个Bean,也就是指定了第一顺位。当结合@Qualifier使用时,可以覆盖@Primary的默认行为,例如:
即使DefaultService被标记为@Primary,但由于@Qualifier(“specificService”),所以最终注入的仍然是specificService
5. @Bean和@Component有什么区别
@Bean和@Component 都是用于定义spring容器中的Bean的注解,但是,他们的使用场景和方式有所不同:
- @Bean注解通常用于Java配置类的方法上,以声明一个Bean并将其添加到Spring容器中,用于显示声明。
- @Component注解用于类级别,将该类标记为Spring容器中的一个组件,自动检测并注册为Bean,用于自动扫描和注入。
特性 | @Bean | @Component |
---|---|---|
使用位置 | 方法级别(在@Configuration类中) | 类级别 |
扫描机制 | 不支持自动扫描,需要手动注册 | 支持自动扫描,通过@ComponentScan自动发现 |
主要用途 | 用于配置第三方库或者复杂对象 | 用于自动发现并注册自定义类 |
常见场景 | 手动配置复杂对象、第三方库类 | 自定义服务、DAO层、控制器等类的自动注册 |
灵活性 | 更灵活,适合复杂初始化 | 自动化更强,适合类的简单注册 |
扩展
@Bean注解用于显示声明spring容器管理的Bean,通常用于以下场景
- 手动创建复杂的对象:需要进行复杂的初始化过程,或者需要传递参数给构造函数的对象
- 如果某个类不是自己开发,且无法添加spring注解时,可以通过@Bean来手动注册。
@Component使用场景
@Component注解用于类级别的扫描和注入,spring会自动发现和管理这些类,他是spring中实现自动扫描Bean的基础。
@Component 是一个通用的注解,还有一些特定用途的衍生注解
- @Service
- @Repository
- @Controller
6. spring事务在什么情况下会失效
一般而言,失效的情况都是用了声明式事务即@Transactional注解,如果使用了这个注解那么在以下几种情况下会导致事务失效
- rollbackFor没设置对,比如默认没有任何设置(只有发生了RuntimeException或者Error才能回滚),则方法内抛出IOException则不会回滚。需要设置@Transactional(rollbackFor=Exception.class).
- 异常被捕获了,比如代码抛错,但是被catch了,这样事务无法正常获取到错误,因此不会回滚。
同一个类中的方法调用,因此事务是基于动态代理实现的,同类的方法调用不会走代理方法,因此,事务自然就失效了。
- @Transactional应用在非public修饰的方法上,spring事务管理器判断非公共方法则不应用事务。
- @Transactional应用在final和static方法上,因为aop默认是cglib代理,无法对final方法子类化。static是静态方法,属于类,不属于实例对象,无法被代理。
- propagation传播机制错误
- 多线程环境,因为@Transactional是基于ThreadLocal存储上下文的,多线程情况下每个线程都有自己的上下文,那么就无法保持事务同步。
- 用的是MySQL的MyISAM引擎,这个引擎本身不支持事务。
7. Spring启动过程
- 加载配置文件,初始化容器:spring启动时首先读取配置文件,包括配置数据库连接、事务管理、AOP配置等。
- 实例化容器:spring根据配置文件中的信息创建容器ApplicationContext,在容器启动阶段实例化BeanFactory,并加载容器中的BeanDefinitions。
- 解析BeanDefinitions:spring容器会解析配置文件中的BeanDefinitions,即声明的Bean元数据,包括Bean的作用域,依赖关系等信息。
- 实例化Bean:spring根据BeanDefinitions实例化Bean对象,将其放入容器管理。
- 注入依赖:spring进行依赖注入,将Bean之间的依赖关系进行注入,包括构造函数注入、属性注入等。
- 处理Bean生命周期初始化方法:
- spring调用Bean初始化方法,对Bean进行初始化
- 如果Bean实现了InitializingBean接口,spring会调用其afterPropertiesSet方法。
- 处理BeanPostProcessors:容器定义了很多BeanPostProcessor,处理其中的自定义逻辑,例如PostProcessBeforeInitializaiton会在Bean初始化前调用。PostProcessAfterInitializaiton则在Bean初始化之后调用。
- 发布事件:Spring可能会在启动过程中发布一些事件,比如容器启动事件。
- 完成启动:当所有Bean初始化完毕、依赖注入完成、AOP配置生效等都准备就绪时,Spring容器启动完成。
8. Spring的单例Bean是否有并发安全问题
存在并发安全问题
,因为Spring容器默认将Bean作为单例管理,因此同一个Bean的实例会在整个应用程序中被多个线程共享。在多线程环境中,如果Bean中包含全局可变状态(如实例变量或者非线程安全资源),则可能会引发线程安全问题。
解决方案
- 避免在单例Bean中使用可变状态:确保单例Bean是无状态的或仅使用线程安全的数据结构。
- 使用@Scope(“prototype”) :对于有状态的Bean,Spring提供了原型作用域,每次请求都会创建一个新的Bean实例,从而避免共享同一个实例带来的并发问题。
- 加锁:如果需要在单例Bean中管理共享资源,可以通过synchronize关键字或者其他线程同步机制来确保线程安全。
- 使用ThreadLocal保存变量。
9. Spring和Spring MVC的关系
Spring是基础,Spring MVC构建于Spring核心之上,利用其提供的容器管理、依赖注入、AOP等功能来实现Web层的处理。
Spring MVC的核心功能
- 基于MVC模式的Web开发
- Model:负责封装数据,可以是POJO、DTO或者其他形式对象
- View:负责展示数据
- Controller:负责用户请求处理。
请求处理流程
Spring MVC通过前端控制器拦截所有的请求,并将请求分发给合适的控制器进行处理。
- DispatcherServlet拦截请求
- HandlerMapping根据请求URL查找对应的控制器
- Controller处理业务逻辑,并返回数据
- ViewResolver 决定渲染哪个视图模板
- 将响应返回给客户端。
10. Spring MVC中的拦截器是什么?如何定义一个拦截器
拦截器用于在请求处理流程的不同阶段拦截HTTP请求和响应,并对其进行预处理或者后处理,拦截器可以用于实现注入权限验证、日志记录、性能监控等功能,而无需将这些逻辑直接耦合在控制器代码中。
定义一个拦截器的步骤
实现HandlerInterceptor接口:自定义的拦截器需要实现HandlerInterceptor接口,并且重写其三个核心方法
- preHandle(): 请求到达控制器之前的预处理
- postHandle():控制器执行之后但视图渲染之前的后处理
- afterCompletion(): 整个请求结束之后的回调。
11. Spring MVC如何处理异常
可以利用全局和局部的异常处理机制,用于捕获应用程序中的异常并返回适当的响应。
Spring MVC中处理异常的核心方式
- 局部异常处理:@ExceptionHandler注解,用于局部的异常处理,通常定义在控制器类中。他可以捕获特定的异常,并返回自定义的错误信息或者视图。
- 全局异常处理:@ControllerAdvice,应用于所有的控制器,通过这个注解,可以定义全局的异常处理逻辑,避免在每个控制器中重复编写相同的异常处理代码。
使用@ExceptionHandler处理局部异常
@Controller
public class UserController {
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id) {
if (id == null) {
throw new IllegalArgumentException("ID cannot be null");
}
// 业务逻辑
return "userProfile";
}
// 局部异常处理方法
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
}
使用@ControllerAdvice处理全局异常
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleAllExceptions(Exception ex) {
return new ResponseEntity<>("An error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}