1.Spring常用的设计模式
- 单例模式
- Spring 容器默认情况下,Bean 的作用域是单例的。这意味着在整个应用程序生命周期内,一个 Bean 只有一个实例。例如,对于数据库连接池这个 Bean,只需要一个实例来管理所有的数据库连接请求。单例模式减少了对象的创建和销毁开销,节省了系统资源。
- Spring 通过维护一个单例 Bean 的缓存来确保只有一个实例存在。当第一次请求某个单例 Bean 时,Spring 会创建它并将其存储在缓存中,后续的请求直接从缓存中获取这个 Bean,而不是再次创建。
- 工厂模式
- 简单工厂模式:Spring 的 BeanFactory 就是简单工厂模式的体现。它负责创建和管理 Bean 对象。就像一个工厂生产产品一样,BeanFactory 根据配置(如 XML 配置文件或 Java 配置类)创建 Bean 实例。例如,在基于 XML 配置的 Spring 应用中,BeanFactory 读取
<bean>
标签的配置信息,包括类名、属性等,然后利用反射机制创建对应的 Bean。
- 简单工厂模式:Spring 的 BeanFactory 就是简单工厂模式的体现。它负责创建和管理 Bean 对象。就像一个工厂生产产品一样,BeanFactory 根据配置(如 XML 配置文件或 Java 配置类)创建 Bean 实例。例如,在基于 XML 配置的 Spring 应用中,BeanFactory 读取
- 代理模式
- JDK 动态代理:Spring AOP(面向切面编程)在为接口类型的 Bean 创建代理时,常常使用 JDK 动态代理。当有切面逻辑(如事务管理、日志记录)需要应用到一个接口的实现类时,Spring 会在运行时动态生成一个代理类,这个代理类实现了目标接口。代理类拦截对目标接口方法的调用,在方法执行前后插入切面逻辑。例如,在事务管理中,代理类在目标方法执行前开启事务,方法执行成功后提交事务,出现异常时回滚事务。
- CGLIB 代理:对于没有实现接口的类,Spring AOP 使用 CGLIB 代理。CGLIB 通过继承目标类生成一个子类作为代理。例如,当要对一个具体的类进行性能监控切面时,CGLIB 代理类会重写目标类的方法,在方法调用前后记录时间,从而实现性能监控,而不需要修改目标类的代码。
- 模板方法模式
- Spring 的 JdbcTemplate 是模板方法模式的典型应用。它定义了操作数据库的基本模板,包括获取数据库连接、执行 SQL 语句、处理结果集、释放连接等步骤。其中获取连接和释放连接等步骤是固定的,而执行 SQL 语句和处理结果集这些步骤可以由用户通过提供回调方法(如
RowMapper
接口的实现)来定制。例如,在执行查询操作时,用户可以通过RowMapper
接口实现将查询结果映射为 Java 对象的逻辑,JdbcTemplate 则按照其内部定义的模板方法流程完成整个数据库操作。
- Spring 的 JdbcTemplate 是模板方法模式的典型应用。它定义了操作数据库的基本模板,包括获取数据库连接、执行 SQL 语句、处理结果集、释放连接等步骤。其中获取连接和释放连接等步骤是固定的,而执行 SQL 语句和处理结果集这些步骤可以由用户通过提供回调方法(如
2.@Autowired和@Resource之间的区别?
-
来源和所属框架
- @Autowired:是 Spring 框架提供的注解。它是 Spring 依赖注入(Dependency Injection,DI)机制的重要组成部分,用于自动装配 Bean。Spring 通过扫描类路径,根据类型(byType)自动匹配并注入对应的 Bean。
- @Resource:是 Java EE(Java Platform, Enterprise Edition)规范中的注解,在 JSR - 250 中定义。它并不是 Spring 特有的,不过 Spring 也支持这个注解用于依赖注入。
-
注入方式
- @Autowired:
- 默认是按照类型(byType)进行注入。例如,如果有一个接口UserService,并且有两个实现类UserServiceImpl1和UserServiceImpl2,当在一个需要注入UserService的类中使用
@Autowired
注解时,Spring 会根据类型寻找对应的 Bean。如果只有一个UserService
类型的 Bean,那么就会成功注入;如果有多个,就需要通过其他方式(如@Qualifier
注解)来指定具体注入哪一个。 - 也可以结合
@Qualifier
注解来按照名称(byName)进行注入。例如,@Autowired @Qualifier("userServiceImpl1") private UserService userService;
,这里就是明确指定要注入名称为userServiceImpl1
的UserService
类型的 Bean。
- 默认是按照类型(byType)进行注入。例如,如果有一个接口UserService,并且有两个实现类UserServiceImpl1和UserServiceImpl2,当在一个需要注入UserService的类中使用
- @Resource:
- 默认是按照名称(byName)进行注入。它会先在容器中查找名称与注解中指定的名称(可以通过
name
属性设置,如@Resource(name = "userServiceImpl1")
)相同的 Bean 进行注入。如果没有指定名称,就会按照变量名作为 Bean 名称来查找。 - 如果按照名称找不到匹配的 Bean,才会按照类型进行注入。这与
@Autowired
的默认行为正好相反。
- 默认是按照名称(byName)进行注入。它会先在容器中查找名称与注解中指定的名称(可以通过
- @Autowired:
3. Spring常用注解
- @Component
- 作用:这是一个通用的 Spring 注解,用于将一个类标记为 Spring 容器中的组件。当 Spring 扫描到带有 @Component 注解的类时,会将其作为一个 Bean 进行管理。
- 示例:假设我们有一个简单的服务类
UserService
,在类定义上添加@Component
注解后,Spring 就会自动把这个类纳入容器管理。
- 使用场景:用于自定义的普通 Java 类,只要希望被 Spring 容器管理,都可以使用这个注解。
- @Service
- 作用:@Service 是 @Component 的一个特化注解,主要用于标注业务逻辑层(Service 层)的类。它的功能和 @Component 基本相同,只是语义上更加明确,用于区分不同层次的组件。
- 使用场景:在构建企业级应用的三层架构(Controller - Service - DAO)时,用于标记服务层的类,让开发人员能够更直观地理解代码结构。
- @Repository
- 作用:这是专门用于数据访问层(DAO 层)的注解,用于将数据访问类标记为 Spring 容器中的 Bean。它还具有将数据访问异常(如 SQLException)转换为 Spring 的
DataAccessException
的功能,使得数据访问层的异常处理更加统一和易于管理.
- 作用:这是专门用于数据访问层(DAO 层)的注解,用于将数据访问类标记为 Spring 容器中的 Bean。它还具有将数据访问异常(如 SQLException)转换为 Spring 的
- 使用场景:在和数据库交互的应用中,用于标记所有和数据访问相关的类,包括对各种数据库(如 MySQL、Oracle 等)的操作类。
- @Controller
- 作用:用于标记 Spring MVC 中的控制器类。它将一个类标识为处理 HTTP 请求的前端控制器,配合 Spring MVC 的其他注解(如 @RequestMapping 等)可以方便地处理各种请求路径和请求方法。
- 使用场景:在构建 Web 应用时,用于标记所有接收和处理用户请求的类,这些类通常会调用 Service 层的方法来完成业务逻辑,并将结果返回给视图层。
- @Autowired
- 作用:用于自动注入 Bean。Spring 会根据类型自动寻找匹配的 Bean 并注入到对应的变量、方法参数或者构造函数参数中。
- 使用场景:在需要在一个类中使用其他 Bean 时,避免手动获取和实例化,通过这个注解可以方便地完成依赖注入,提高代码的可维护性和可测试性。
- @RequestMapping
- 作用:用于在 Spring MVC 中映射 HTTP 请求路径和请求方法。它可以用在类级别和方法级别,类级别的注解用于定义一个基础路径,方法级别的注解用于具体定义每个方法对应的路径和方法组合。
- 使用场景:在构建 Web 应用的控制器中,用于定义每个请求处理方法对应的 URL 路径和请求方式,是实现前后端交互的关键注解之一。
- @Configuration
- 作用:用于标记一个类为 Spring 的配置类。在这个类中,可以通过
@Bean
注解定义各种 Bean,替代传统的 XML 配置文件。
- 作用:用于标记一个类为 Spring 的配置类。在这个类中,可以通过
- 使用场景:在基于 Java 配置的 Spring 项目中,用于集中定义和管理所有的 Bean,使得配置更加灵活和易于维护,尤其适用于大型项目和团队协作开发。
- @Bean
- 作用:在
@Configuration
类中使用,用于定义一个方法,该方法返回的对象将作为一个 Bean 被 Spring 容器管理。
- 作用:在
- 使用场景:用于在 Java 配置类中创建和配置各种对象,包括自定义对象、第三方库对象等,是构建 Spring 容器的重要手段之一。
4.什么是Spring IOC ?
- 深入理解控制反转(IOC)概念
- 控制权的转移
public class LoginController {private UserService userService = new UserService();private UserDao userDao = new UserDaoImpl();public void login(String username, String password) {// 调用userService和userDao的方法来处理登录逻辑} }
- 在传统的程序设计中,对象的控制权完全在开发者编写的代码手里。例如,在一个简单的 Java Web 应用程序中,如果要实现用户登录功能,开发者可能会在
LoginController
类中手动创建UserService
和UserDao
的实例来处理用户登录的业务逻辑。像这样: - 这种方式下,
LoginController
类不仅要负责自身的业务逻辑(处理用户登录请求),还要负责创建和管理它所依赖的对象(UserService
和UserDao
)。而在 Spring IOC 模式下,控制权被反转到了 Spring 容器。Spring 容器就像一个大管家,它负责创建UserService
和UserDao
这些对象,LoginController
只需要从容器中获取已经创建好并且配置好依赖关系的对象就可以了。
- 在传统的程序设计中,对象的控制权完全在开发者编写的代码手里。例如,在一个简单的 Java Web 应用程序中,如果要实现用户登录功能,开发者可能会在
- 控制权的转移
- 详细分析依赖注入(DI)
- 构造函数注入的细节
public class OrderService {private OrderDao orderDao;public OrderService(OrderDao orderDao) {this.orderDao = orderDao;}// 其他业务方法 }
- 构造函数注入的细节
- 参数匹配:当使用构造函数注入时,Spring 容器会根据构造函数的参数类型来匹配要注入的对象。例如,有一个
OrderService
类,它的构造函数接受一个OrderDao
类型的参数: - 假设在 Spring 配置中有一个
OrderDao
的bean
定义,容器在创建OrderService
的实例时,会查找类型为OrderDao
的bean
,并将其作为参数传递给OrderService
的构造函数。如果有多个OrderDao
的实现类,还可以通过在配置中指定bean
的名称或者使用@Qualifier
注解等来精确匹配要注入的对象。 - 对象的完整性保证:构造函数注入的一个优点是可以保证注入的对象在
OrderService
实例化时就已经准备好。这样可以确保OrderService
对象从创建的那一刻起就处于一个完整的状态,不会出现因为某些依赖对象没有注入而导致的NullPointerException
等问题。
- Setter 方法注入的更多细节
- 灵活性:Setter 方法注入提供了一种更加灵活的方式来注入依赖。例如,
ProductService
类有一个setProductDao
的 Setter 方法:
public class ProductService {private ProductDao productDao;public void setProductDao(ProductDao productDao) {this.productDao = productDao;}// 其他业务方法 }
- 在某些情况下,可能并不需要在
ProductService
对象创建时就注入ProductDao
对象。比如,ProductService
对象可能有一个初始化阶段,在这个阶段之后才需要ProductDao
对象来执行一些数据查询操作。此时,Setter 方法注入就可以在需要的时候再注入ProductDao
对象,而不是像构造函数注入那样必须在对象创建时就完成注入。 - 循环依赖处理:Setter 方法注入在处理某些循环依赖的情况时可能会更加灵活。例如,
A
类依赖B
类,B
类又依赖A
类。在这种情况下,使用 Setter 方法注入可能更容易解决循环依赖问题,因为对象可以先被创建,然后再通过 Setter 方法来解决相互之间的依赖关系,当然,Spring 在处理循环依赖时还有一些复杂的机制和限制。
- 灵活性:Setter 方法注入提供了一种更加灵活的方式来注入依赖。例如,
5.什么是SpringAOP?
- 概念定义
- Spring AOP(Aspect - Oriented Programming,面向切面编程)是 Spring 框架提供的一种编程范式,用于在不修改原有业务逻辑代码的基础上,实现横切关注点(Cross - Cutting Concerns)的功能。横切关注点是指那些会分散在多个业务模块中的功能,如日志记录、事务管理、安全验证等。
- 例如,在一个包含多个业务方法的系统中,可能每个业务方法都需要记录日志来跟踪其执行情况。如果不使用 AOP,就需要在每个业务方法中添加日志记录的代码,这样会导致代码冗余且难以维护。而 Spring AOP 允许将日志记录这个横切关注点从业务方法中分离出来,集中进行管理。
- AOP 中的基本概念
- 切面(Aspect):切面是一个包含了横切关注点相关逻辑的模块。它是 AOP 中的核心概念,将横切逻辑(如日志记录逻辑)封装在一个独立的单元中。切面由切点和通知组成。
- 切点(Pointcut):切点用于定义在哪些连接点(Join Point)上应用切面的通知。连接点是程序执行过程中的某个特定位置,如方法调用、方法执行、异常抛出等。切点可以使用表达式来精确地指定在哪些类的哪些方法上应用切面。例如,可以定义一个切点,使其应用于某个包下所有以 “save” 命名的方法。
- 通知(Advice):通知是切面中定义的在切点所匹配的连接点上执行的具体动作。Spring AOP 中有五种通知类型:
- 前置通知(Before Advice):在目标方法执行之前执行的通知。例如,在业务方法执行前记录开始时间用于性能分析。
- 后置通知(After Advice):在目标方法执行之后执行的通知。它不管目标方法是否抛出异常都会执行。比如,在业务方法执行后关闭数据库连接。
- 返回通知(After - Returning Advice):在目标方法正常返回结果后执行的通知。可以用于对返回结果进行处理,如对返回的数据进行加密。
- 异常通知(After - Throwing Advice):在目标方法抛出异常时执行的通知。用于处理异常情况,如记录异常信息到日志文件。
- 环绕通知(Around Advice):环绕通知可以在目标方法执行前后都进行操作,它就像一个拦截器一样,可以控制目标方法是否执行、何时执行以及如何执行。例如,可以在环绕通知中实现缓存机制,先检查缓存中是否有目标方法的结果,如果有则直接返回,否则执行目标方法并将结果存入缓存。
- 工作原理
- Spring AOP 是基于代理(Proxy)模式实现的。当一个目标对象(被代理的对象,如业务服务类)被配置为需要应用 AOP 时,Spring 会为其创建一个代理对象。这个代理对象会拦截对目标对象的方法调用。
- 以 JDK 动态代理为例(当目标对象实现了接口时使用),代理对象和目标对象实现相同的接口。当客户端调用目标对象的接口方法时,实际上调用的是代理对象的方法。代理对象会根据切点的定义,判断是否需要执行切面的通知。如果需要,就会按照通知的类型(前置、后置等)在合适的时机执行通知中的逻辑,然后再调用目标对象的真实方法。
- 例如,有一个
UserService
接口和它的实现类UserServiceImpl
,并且配置了一个切面来记录UserService
方法的执行日志。当客户端调用UserService
接口的saveUser
方法时,实际上调用的是 Spring 为UserServiceImpl
创建的代理对象的saveUser
方法。代理对象首先会检查日志记录这个切面的切点是否匹配saveUser
方法,如果匹配,就会执行前置通知(记录方法开始执行的日志),然后调用UserServiceImpl
的真实saveUser
方法,最后执行后置通知(记录方法执行结束的日志)。 - 应用场景
- 日志记录:如前面所述,AOP 可以方便地在多个业务方法中统一添加日志记录功能。可以记录方法的执行时间、参数、返回结果等信息,便于系统的监控和调试。
- 事务管理:在企业级应用中,事务管理是非常重要的。AOP 可以用于在多个业务方法周围添加事务控制逻辑。例如,对于数据库操作方法,在方法执行前开启事务,在方法正常执行完成后提交事务,在方法抛出异常时回滚事务。
- 安全验证:可以在需要进行安全验证的方法调用前添加验证逻辑。例如,验证用户是否具有访问某个资源的权限。如果没有权限,就抛出异常或者返回错误信息,而不需要在每个业务方法中都编写安全验证的代码。
6.Spring的循环依赖问题(重点)
- 循环依赖的概念
- 循环依赖是指在 Spring 容器中,两个或多个 Bean 之间存在相互依赖的关系。例如,有 Bean A 和 Bean B,Bean A 依赖于 Bean B,同时 Bean B 又依赖于 Bean A。这种相互依赖的情况可能会导致在对象创建和初始化过程中出现问题。
- 从代码角度看,假设存在两个类
ClassA
和ClassB
,在 Spring 配置中定义为两个 Bean。ClassA
中有一个属性是ClassB
类型,ClassB
中也有一个属性是ClassA
类型,像这样:
public class ClassA {private ClassB classB;// 构造函数或Setter方法注入ClassB } public class ClassB {private ClassA classA;// 构造函数或Setter方法注入ClassA }
- 循环依赖产生的问题及原因
- 对象创建的死循环:在没有适当处理机制的情况下,当 Spring 容器试图创建
ClassA
的 Bean 时,它发现ClassA
依赖于ClassB
,所以它会去创建ClassB
。但在创建ClassB
的过程中,又发现ClassB
依赖于ClassA
,于是又回头去创建ClassA
,这样就陷入了一个无限循环的创建过程,导致程序无法正常运行。 - 未初始化的对象引用:即使通过某种方式避免了死循环,还可能出现另一个问题。例如,如果采用构造函数注入,当
ClassA
在构造函数中等待ClassB
的完全初始化实例,而ClassB
又在其构造函数中等待ClassA
的完全初始化实例,这样两个对象都无法完成初始化,最终得到的是两个未完全初始化的对象引用,这可能会导致后续使用这些对象时出现NullPointerException
或其他错误。
- 对象创建的死循环:在没有适当处理机制的情况下,当 Spring 容器试图创建
- Spring 解决循环依赖的方式
- 三级缓存机制(针对单例 Bean)
- Spring 使用了三级缓存来解决单例 Bean 的循环依赖问题。这三级缓存分别是:
- singletonObjects:这是一级缓存,用于存放完全初始化好的单例 Bean。一旦一个 Bean 完成了创建、属性注入和初始化等所有步骤,就会被放入这个缓存中。
- earlySingletonObjects:这是二级缓存,用于存放提前暴露的单例 Bean(还没有完成所有初始化步骤)。当一个 Bean 在创建过程中发现存在循环依赖,并且需要将自己提前暴露出来以解决依赖问题时,就会先将自己放入这个缓存。
- singletonFactories:这是三级缓存,存放的是创建单例 Bean 的工厂对象。这个工厂对象可以用于创建 Bean,同时也可以用于在循环依赖情况下获取提前暴露的 Bean。
- 当 Spring 容器创建一个单例 Bean 时,首先会尝试从
singletonObjects
缓存中获取,如果没有找到,就会创建这个 Bean。在创建过程中,如果发现存在循环依赖,就会将这个 Bean 提前暴露(放入earlySingletonObjects
或通过singletonFactories
来提供),使得其他依赖它的 Bean 可以获取到这个未完全初始化的 Bean,从而避免死循环。 - 例如,假设创建
ClassA
,发现依赖ClassB
,在创建ClassB
时又发现依赖ClassA
。此时,ClassA
还没有完成全部初始化,但可以通过三级缓存机制提前暴露自己。ClassB
就可以获取到这个提前暴露的ClassA
,完成自己的创建,然后ClassA
再获取已经创建好的ClassB
,完成自己的剩余初始化步骤,最后将完全初始化好的ClassA
放入singletonObjects
缓存。
- Spring 使用了三级缓存来解决单例 Bean 的循环依赖问题。这三级缓存分别是:
- 构造函数注入的限制:Spring 在处理循环依赖时,对于构造函数注入有一定的限制。因为构造函数注入要求在对象创建时就注入所有依赖,所以如果存在循环依赖且都是通过构造函数注入的情况,Spring 无法解决,会抛出
BeanCurrentlyInCreationException
异常。这是因为在构造函数注入的场景下,很难像 Setter 方法注入那样先创建一个未完全初始化的对象并暴露出来解决循环依赖。 - Setter 方法注入的优势:Setter 方法注入在处理循环依赖时相对更灵活。在这种情况下,Spring 可以先创建对象,将其放入缓存(通过三级缓存机制),然后再通过 Setter 方法注入依赖。例如,
ClassA
和ClassB
通过 Setter 方法注入相互依赖,Spring 可以先创建ClassA
,将其放入缓存,然后在创建ClassB
时,从缓存中获取ClassA
,注入到ClassB
中,反之亦然。
- 三级缓存机制(针对单例 Bean)
7.Bean的生命周期(重点)
在 Spring 框架中,Bean 的生命周期是指从 Bean 的创建、初始化到销毁的整个过程。以下是 Bean 生命周期的详细过程:
-
实例化
Spring 容器根据配置信息,如 XML 配置文件或注解,使用反射机制创建 Bean 的实例。例如,当配置了一个@Component
注解的类时,Spring 会在启动时扫描并实例化该类。 -
属性赋值
在 Bean 实例化后,Spring 会按照配置为 Bean 的属性进行赋值。可以通过 XML 中的<property>
标签或@Autowired
、@Value
等注解来指定属性的值或依赖关系。 -
初始化前
在 Bean 的属性赋值完成后,会执行BeanPostProcessor
的postProcessBeforeInitialization
方法。这个阶段可以对 Bean 进行一些前置处理,例如修改 Bean 的属性值等。 -
初始化
Spring 会检查 Bean 是否实现了InitializingBean
接口,如果实现了,则调用其afterPropertiesSet
方法。此外,还可以通过在 Bean 类的方法上添加@PostConstruct
注解来指定初始化方法,该方法也会在这个阶段被调用。初始化方法通常用于执行一些需要在 Bean 创建完成后立即进行的操作,如资源的初始化、数据的加载等。 -
初始化后
执行BeanPostProcessor
的postProcessAfterInitialization
方法。这个阶段可以对 Bean 进行后置处理,例如为 Bean 生成代理对象等。 -
使用
Bean 初始化完成后,就可以在应用中被其他组件使用了。此时,Bean 已经处于可用状态,可以提供相应的服务和功能。 -
销毁前
当 Spring 容器关闭时,会先执行DestructionAwareBeanPostProcessor
的postProcessBeforeDestruction
方法,允许在 Bean 销毁前进行一些自定义的处理。 -
销毁
Spring 会检查 Bean 是否实现了DisposableBean
接口,如果实现了,则调用其destroy
方法。此外,还可以通过在 Bean 类的方法上添加@PreDestroy
注解来指定销毁方法。销毁方法通常用于释放 Bean 占用的资源,如关闭数据库连接、释放文件资源等。 -
销毁后
Bean 销毁完成后,它所占用的资源被释放,不再在 Spring 容器中管理,从内存中移除。
8.Mybatis的一级、二级缓存
MyBatis 提供了两级缓存机制:一级缓存和二级缓存,用于提升查询性能,减少数据库访问次数。
1. 一级缓存(Local Cache)
-
作用范围:一级缓存是 SqlSession 级别的缓存,默认开启。
-
生命周期:与 SqlSession 绑定,SqlSession 关闭或清空时,缓存失效。
-
工作机制:
-
同一 SqlSession 中,相同查询语句和参数的结果会被缓存。
-
执行更新操作(如 insert、update、delete)时,缓存会被清空,以保证数据一致性。
-
-
特点:
-
自动启用,无需额外配置。
-
仅对当前 SqlSession 有效,其他 SqlSession 无法共享。
-
2. 二级缓存(Global Cache)
-
作用范围:二级缓存是 Mapper 级别的缓存,多个 SqlSession 共享。
-
生命周期:与 Mapper 绑定,应用关闭或显式清空时,缓存失效。
-
工作机制:
-
需要手动配置启用。
-
多个 SqlSession 可以共享缓存数据。
-
执行更新操作时,缓存会被清空。
-