Spring面试题
IOC(控制反转)
概念:将手动创建对象的控制权利,将对象之间的相互依赖关系交由Spring框架 / IOC容器管理。
-
控制:对象创建(实例化、管理)的权利
-
反转:控制权交给外部环境(Spring框架、IOC容器)
Spring Bean
Bean 指的就是被IOC容器所管理的对象。
@Component
:通用注解,可标注任意类为Spring
组件;场景:一个Bean不知道属于哪层。
@Repository
:持久层(Dao层);场景:数据库相关操作。
@Service
:服务层;场景:涉及复杂逻辑,需要用到Dao层。
@Controller
:Spring MVC控制层;场景:接受用户(前端)请求,并调用Service
层返回数据给前端页面。
@Repository
:Spring 用于标识 DAO 层,并提供异常转换功能。
@Mapper
:MyBatis 用于标识 Mapper 接口并生成实现。只使用
@Mapper
即可满足 MyBatis 的使用需求。如果项目需要一致的异常处理或其他 Spring 功能,可以在@Mapper
的基础上加上@Repository
。
@Component 和 @Bean的区别是什么?
-
@Component
:注解作用于类,而@Bean
注解作用于方法 -
@ComponentScan
注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中。 -
@Bean
通常是我们在标有该注解的方法中定义产生这个bean,@Bean
告诉Spring这是某个类的实例,当我需要用它的时候还给我。 -
@Bean
注解比@Component
注解的定义性更强,而且很多地方只能通过@Bean
注解来注册bean。
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
@Autowired 和@Resource 的区别是什么?
@Autowired
是Spring提供的注解,@Resource
是JDK提供的注解。- Autowired默认注入方式是
byType
(根据类型进行匹配),@Resource
默认注入方式是byName
(根据名称进行匹配) Autowired
可以通过@Qualifier
注解来显式指定名称,@Resource
可以通过name
属性来显式指定名称。@Autowired
支持在构造函数、方法、字段和参数上使用。@Resource
主要用于字段和方法上的注入,不支持在构造函数或参数上使用。
注入Bean的方式有哪些?
- 构造函数注入:通过类的构造函数来注入。
- Setter注入:通过类的Setter方法来注入。
- Field(字段)注入:直接坐在类的字段上使用注解(@Autowired 或 @Resource)
// 构造函数注入
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
推荐使用构造函数注入,原因如下:
依赖完整性:确保所有必需依赖在对象创建时就被注入,避免了空指针异常的风险。
不可变性:有助于创建不可变对象,提高了线程安全性。
初始化保证:组件在使用前已完全初始化,减少了潜在的错误。
测试便利性:在单元测试中,可以直接通过构造函数传入模拟的依赖项,而不必依赖 Spring 容器进行注入。
Bean的生命周期
- 首先是实例化 --> 属性赋值 --> 初始化 --> 销毁;
- 再是初始化的具体操作,有 Aware 接口的依赖注入、BeanPostProcessor 在初始化前后的处理以及 InitializingBean 和 init-method 的初始化操作;
- 销毁的具体操作,有注册相关销毁回调接口,最后通过DisposableBean 和 destory-method 进行销毁;
AOP(面向切面编程)
AOP能将与业务无关,但为业务模块所共同调用的逻辑(事务处理、日志管理、权限控制等)封装起来,便于减少系统重复代码,降低模块之间的耦合度,并且有利于未来项目的可拓展性和可维护性。
专业术语:
术语 | 含义 |
---|---|
目标(Target) | 被通知的对象 |
代理(Proxy) | 向目标对象应用通知之后创建的代理对象 |
连接点(JoinPoint) | 目标对象的所属类中,定义的所有方法均为连接点 |
切入点(Pointcut) | 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点) |
通知(Advice) | 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情 |
切面(Aspect) | 切入点(Pointcut)+通知(Advice) |
Weaving(织入) | 将通知应用到目标对象,进而生成代理对象的过程动作 |
Spring AOP 和 AspectJ AOP 有什么区别?
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理,而 AspectJ 基于字节码操作。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但Spring AOP 相对来说更简单,但是当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。
AOP常见的通知类型有哪些?
Before(前置通知):目标对象的方法调用之前触发
After (后置通知):目标对象的方法调用之后触发
AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
AfterThrowing(异常通知):目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
Around (环绕通知):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法。
多个切面的执行顺序如何控制?
- 通常使用
@Order
注解直接定义切面顺序。
// 值越小优先级越高
@Order(3)
@Component
@Aspect
public class LoggingAspect implements Ordered {
- 实现
Ordered
接口重写getOrder
方法。
@Component
@Aspect
public class LoggingAspect implements Ordered {
@Override
public int getOrder() {
// 返回值越小优先级越高
return 1;
}
}
Spring MVC
说说自己对于Spring MVC 了解
MVC是模型(Model)、视图(View)、控制器(Controller),核心思想就是将业务逻辑、数据、显示分离来组织代码。
Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。
Spring MVC工作原理
客户端(CS架构)、浏览器(BS架构)
流程:
- 客户端(浏览器)发送请求,
DispatcherServlet
拦截请求。 DispatcherServlet
根据请求信息调用HandlerMapping
。HandlerMapping
根据 URL 去匹配查找能处理的Handler
(也就是我们平常说的Controller
控制器) ,并会将请求涉及到的拦截器和Handler
一起封装。`DispatcherServlet
调用HandlerAdapter
适配器执行Handler
。Handler
完成对用户请求的处理后,会返回一个ModelAndView
对象给DispatcherServlet
,ModelAndView
顾名思义,包含了数据模型以及相应的视图的信息。Model
是返回的数据对象,View
是个逻辑上的View
。ViewResolver
会根据逻辑View
查找实际的View
。DispaterServlet
把返回的Model
传给View
(视图渲染)。- 把
View
返回给请求者(浏览器)
统一异常处理怎么做?
推荐使用注解的方式统一异常处理,具体会使用到 @ControllerAdvice
+ @ExceptionHandler
这两个注解 。
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(BaseException.class)
public ResponseEntity<?> handleAppException(BaseException ex, HttpServletRequest request) {
//......
}
@ExceptionHandler(value = ResourceNotFoundException.class)
public ResponseEntity<ErrorReponse> handleResourceNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
//......
}
}
这种异常处理方式下,会给所有或者指定的 Controller
织入异常处理的逻辑(AOP),当 Controller
中的方法抛出异常的时候,由被@ExceptionHandler
注解修饰的方法进行处理。
Spring框架中用到了哪些设计模式?
-
工厂设计模式 : Spring 使用工厂模式通过
BeanFactory
、ApplicationContext
创建 bean 对象。 -
代理设计模式 : Spring AOP 功能的实现。
-
单例设计模式 : Spring 中的 Bean 默认都是单例的。
-
模板方法模式 : Spring 中
jdbcTemplate
、hibernateTemplate
等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。 -
包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
-
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
-
适配器模式 : Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配
Controller
。
Spring循环依赖了解吗,如何解决?
循环依赖是指 Bean 对象循环引用,是两个或多个 Bean 之间相互持有对方的引用,例如 CircularDependencyA → CircularDependencyB → CircularDependencyA。
解决办法:三级缓存、懒加载
@Lazy解决循环依赖问题(先建立代理对象,保证对象初始化完成,等到真正调用的时候,才会调用实际方法)
举一个例子,比如说有两个 Bean,A 和 B,他们之间发生了循环依赖,那么 A 的构造器上添加 @Lazy
注解之后(延迟 Bean B 的实例化),加载的流程如下:
- 首先 Spring 会去创建 A 的 Bean,创建时需要注入 B 的属性;
- 由于在 A 上标注了
@Lazy
注解,因此 Spring 会去创建一个 B 的代理对象,将这个代理对象注入到 A 中的 B 属性; - 之后开始执行 B 的实例化、初始化,在注入 B 中的 A 属性时,此时 A 已经创建完毕了,就可以将 A 给注入进去。
从上面的加载流程可以看出: @Lazy
解决循环依赖的关键点在于代理对象的使用。
- 没有
@Lazy
的情况下:在 Spring 容器初始化A
时会立即尝试创建B
,而在创建B
的过程中又会尝试创建A
,最终导致循环依赖(即无限递归,最终抛出异常)。 - 使用
@Lazy
的情况下:Spring 不会立即创建B
,而是会注入一个B
的代理对象。由于此时B
仍未被真正初始化,A
的初始化可以顺利完成。等到A
实例实际调用B
的方法时,代理对象才会触发B
的真正初始化。
@Lazy
能够在一定程度上打破循环依赖链,允许 Spring 容器顺利地完成 Bean 的创建和注入。但这并不是一个根本性的解决方案,尤其是在构造函数注入、复杂的多级依赖等场景中,@Lazy
无法有效地解决问题。因此,最佳实践仍然是尽量避免设计上的循环依赖。
Spring 事务
Spring管理事务的方式有几种?
编程式事务:在代码中硬编码(在分布式系统中推荐使用) : 通过 TransactionTemplate
或者 TransactionManager
手动管理事务,事务范围过大会出现事务未提交导致超时,因此事务要比锁的粒度更小。
声明式事务:在 XML 配置文件中配置或者直接基于注解(单体应用或者简单业务系统推荐使用) : 实际是通过 AOP 实现(基于@Transactional
的全注解方式使用最多)
@Transactional(rollbackFor = Exception.class)了解吗
// 所有的异常都回滚事务
@Transactional(rollbackFor = Exception.class)
public void someMethod() {
// some business logic
}
// 特定异常不回滚事务
@Transactional(noRollbackFor = CustomException.class)
public void someMethod() {
// some business logic
Spring JPA
JPA审计功能是做什么的?有什么用?
审计功能主要是帮助我们记录数据库操作的具体行为比如某条记录是谁创建的、什么时间创建的、最后修改人是谁、最后修改时间是什么时候
@Data
@AllArgsConstructor
@NoArgsConstructor
@MappedSuperclass
@EntityListeners(value = AuditingEntityListener.class)
public abstract class AbstractAuditBase {
@CreatedDate
@Column(updatable = false)
@JsonIgnore
private Instant createdAt;
@LastModifiedDate
@JsonIgnore
private Instant updatedAt;
@CreatedBy
@Column(updatable = false)
@JsonIgnore
private String createdBy;
@LastModifiedBy
@JsonIgnore
private String updatedBy;
}
@CreatedDate
: 表示该字段为创建时间字段,在这个实体被 insert 的时候,会设置值
@CreatedBy
:表示该字段为创建人,在这个实体被 insert 的时候,会设置值
@LastModifiedDate
、@LastModifiedBy
同理。
Spring Security
有哪些控制请求访问权限的方法?
permitAll()
:无条件允许任何形式访问,不管你登录还是没有登录。anonymous()
:允许匿名访问,也就是没有登录才可以访问。denyAll()
:无条件决绝任何形式的访问。authenticated()
:只允许已认证的用户访问。fullyAuthenticated()
:只允许已经登录或者通过 remember-me 登录的用户访问。hasRole(String)
: 只允许指定的角色访问。hasAnyRole(String)
: 指定一个或者多个角色,满足其一的用户即可访问。hasAuthority(String)
:只允许具有指定权限的用户访问hasAnyAuthority(String)
:指定一个或者多个权限,满足其一的用户即可访问。hasIpAddress(String)
: 只允许指定 ip 的用户访问。
如何对密码进行加密
Spring Security 提供了多种加密算法的实现,开箱即用,非常方便。这些加密算法实现类的接口是 PasswordEncoder
(代理类) ,如果你想要自己实现一个加密算法的话,也需要实现 PasswordEncoder
接口。
PasswordEncoder
接口一共也就 3 个必须实现的方法
public interface PasswordEncoder {
// 加密也就是对原始密码进行编码
String encode(CharSequence var1);
// 比对原始密码和数据库中保存的密码
boolean matches(CharSequence var1, String var2);
// 判断加密密码是否需要再次进行加密,默认返回 false
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
官方推荐使用基于 bcrypt 强哈希函数的加密算法实现类。
hashPassword
方法:接收明文密码,通过 BCrypt.gensalt()
生成一个随机盐值,使用 BCrypt.hashpw
方法将密码加密后返回。
checkPassword
方法:用于验证明文密码是否与加密后的哈希密码匹配。
import org.mindrot.jbcrypt.BCrypt;
public class PasswordUtil {
// 加密密码的方法
public static String hashPassword(String plainTextPassword) {
// 使用 BCrypt 生成盐并进行加密
return BCrypt.hashpw(plainTextPassword, BCrypt.gensalt());
}
// 验证密码的方法
public static boolean checkPassword(String plainTextPassword, String hashedPassword) {
// 使用 BCrypt 验证密码是否匹配
return BCrypt.checkpw(plainTextPassword, hashedPassword);
}
public static void main(String[] args) {
// 示例:加密密码
String password = "mySecurePassword";
String hashedPassword = hashPassword(password);
System.out.println("Hashed Password: " + hashedPassword);
// 示例:验证密码
boolean isMatch = checkPassword(password, hashedPassword);
System.out.println("Password Match: " + isMatch);
}
}
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.4</version>
</dependency>
原文地址:https://blog.csdn.net/m0_74119287/article/details/143494088
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!