java测试题(ssm框架)
一、Spring MVC
Q: 谈谈你对Spring MVC的理解?
A: Spring MVC是基于Java的轻量级Web框架,是Spring框架的一个模块。它采用了经典的MVC (Model-View-Controller) 设计模式,实现了业务逻辑、数据和视图的清晰解耦。其核心是围绕
DispatcherServlet
(前端控制器)构建的请求驱动模型。通过HandlerMapping
(处理器映射)、HandlerAdapter
(处理器适配器)、ViewResolver
(视图解析器)等核心组件协作处理HTTP请求。它支持强大的功能,如灵活的视图技术(JSP, Thymeleaf, FreeMarker等)、全局异常处理、文件上传、数据绑定和验证等。主要优势:
松耦合: 深度依赖Spring的IoC (控制反转) / DI (依赖注入) 容器管理组件。
灵活性: 支持多种视图技术,易于扩展(如自定义拦截器
HandlerInterceptor
)。现代性: 深度支持RESTful 风格开发,注解驱动(如
@Controller
,@RequestMapping
)配置简洁。集成性: 与Spring Boot无缝集成,开箱即用。
高性能: 相比Struts2等框架,设计更简洁高效。
扩展性: 提供
DeferredResult
/Callable
支持异步请求处理,并通过Spring WebFlux支持响应式编程模型。
设计哲学: 体现了“约定优于配置”的思想,简化开发。凭借其成熟性、灵活性和强大的Spring生态整合能力,Spring MVC已成为企业级Web应用开发的主流选择。
Q: 介绍一下Spring MVC的工作流程?
A: Spring MVC的核心工作流程如下:
DispatcherServlet 接收请求: 用户发起HTTP请求,由配置的前端控制器
DispatcherServlet
捕获。HandlerMapping 查找处理器:
DispatcherServlet
查询注册的HandlerMapping
,根据请求URL找到对应的Handler
(通常是@Controller
注解标记类中的一个具体方法)。HandlerAdapter 执行处理器:
DispatcherServlet
通过HandlerAdapter
适配器来执行找到的Handler
方法。适配器负责处理方法参数绑定(将请求参数、路径变量、Session属性、Model对象等注入到方法参数中)和返回值处理。Controller 执行业务逻辑:
Handler
方法(即Controller方法)执行业务逻辑,通常会访问Service层,并最终返回一个结果(可能是视图名称String
、ModelAndView
对象、@ResponseBody
注解的Java对象等)。处理返回结果:
如果返回视图名称或
ModelAndView
,DispatcherServlet
会调用ViewResolver
视图解析器将逻辑视图名解析为具体的View
对象(如JSP, Thymeleaf模板)。如果方法有
@ResponseBody
注解或返回类型是ResponseEntity
等,则使用HttpMessageConverter
将返回值直接写入HTTP响应体(如JSON/XML)。
View 渲染:
View
对象负责渲染模型数据(由Controller放入Model
或ModelMap
中的数据),生成最终的响应内容(如HTML)。响应返回: 将渲染结果返回给客户端。
Q: 如果你也用过Struts2,简单介绍下Spring MVC和Struts2的区别有哪些?
A: Spring MVC与Struts2的核心区别在于架构设计和开发模式:
前端控制器: Spring MVC基于Servlet (
DispatcherServlet
),Struts2基于Filter (StrutsPrepareAndExecuteFilter
/ 早期的FilterDispatcher
)。映射方式: Spring MVC支持方法级映射(
@RequestMapping
注解在方法上),更灵活,天然适合RESTful。Struts2是Action类级映射(通常一个URL对应一个Action类),需要在XML或注解中指定方法名。参数绑定: Spring MVC提供灵活强大的参数绑定机制(支持路径变量
@PathVariable
、请求参数@RequestParam
、模型属性@ModelAttribute
等),处理简单直观。Struts2主要依赖OGNL表达式和Setter/Getter进行参数注入。IoC容器: Spring MVC深度集成Spring IoC容器,Controller及其他组件都是Spring Bean,享受依赖注入、AOP等所有Spring特性。Struts2有自身的IoC容器,但功能不如Spring强大,且与Spring整合需要额外配置。
设计理念: Spring MVC更轻量、简洁,更符合现代Java Web开发习惯。Struts2相对重量级,配置较繁琐。
性能: 普遍认为Spring MVC的性能优于Struts2,尤其在请求处理速度和内存占用方面。
线程模型: Spring MVC默认Controller是单例的(线程安全需开发者注意)。Struts2默认每次请求创建一个新的Action实例。
视图技术: 两者都支持多种视图技术,但Spring MVC与新兴模板引擎(Thymeleaf)的整合通常更流畅。
社区与趋势: Spring MVC(及其在Spring Boot中的形态)是目前绝对的主流,社区活跃,生态丰富。Struts2因历史安全漏洞、架构相对陈旧等原因,已逐渐被淘汰,主要用于维护遗留系统。
Q: 怎么样把数据放入Session里面?
A: 在Spring MVC的Controller方法中,可以通过以下方式将数据放入HttpSession:
注入
HttpServletRequest
:java
@RequestMapping("/setSession") public String setSessionData(HttpServletRequest request) {request.getSession().setAttribute("key", "value"); // 使用request获取sessionreturn "view"; }
直接注入
HttpSession
:java
@RequestMapping("/setSession") public String setSessionData(HttpSession session) { // 直接注入sessionsession.setAttribute("key", "value");return "view"; }
使用
@SessionAttributes
注解 (Controller类级别): 该注解主要用于在请求之间将Model
中的特定属性临时存储在Session中(通常用于表单对象跨请求),并非通用的Session存取方式。通用数据存取推荐前两种方式。
二、MyBatis
Q: 简述Mybatis的插件运行原理,以及如何编写一个插件?
A:
原理: MyBatis插件的核心原理是 责任链模式 + 动态代理。MyBatis允许在四大核心对象 (
Executor
,StatementHandler
,ParameterHandler
,ResultSetHandler
) 的方法执行前后进行拦截。当配置了插件后,MyBatis会使用JDK动态代理(如果对象实现了接口)或CGLIB动态代理(如果对象没实现接口)为这些核心对象创建代理对象。当调用代理对象的方法时,会先经过配置的所有插件的intercept()
方法。在intercept()
方法内部,开发者可以编写自定义逻辑,然后通过Invocation.proceed()
方法将调用传递给责任链中的下一个插件或最终的目标方法。编写插件:
实现MyBatis提供的
Interceptor
接口。重写
intercept(Invocation invocation)
方法,在此方法中编写拦截逻辑。可以通过invocation.getTarget()
获取被拦截的目标对象,invocation.getMethod()
获取被拦截的方法,invocation.getArgs()
获取方法参数。调用invocation.proceed()
执行原方法。重写
plugin(Object target)
方法,通常使用Plugin.wrap(target, this)
来方便地创建代理对象。重写
setProperties(Properties properties)
方法,用于接收插件配置参数(如果在xml中配置了<property>
)。使用
@Intercepts
和@Signature
注解(或在XML中配置<plugins>
)指定要拦截的类、方法以及方法参数类型。在MyBatis的核心配置文件(
mybatis-config.xml
)中注册该插件。
Q: Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
A:
支持。 MyBatis支持关联对象(一对一
<association>
、一对多<collection>
)的延迟加载(懒加载)。原理: 核心是 动态代理 + 拦截机制。
当执行查询主对象的SQL时,MyBatis会将查询到的结果映射到主对象。
对于配置了延迟加载的关联对象属性,MyBatis不会立即执行关联SQL去查询数据库,而是为该属性创建一个动态代理对象(如
Proxy
或CglibProxyFactory
生成的代理)。当程序首次调用这个代理对象的任何方法(如
getXXX()
)时,代理对象会拦截这次调用。拦截器触发关联SQL查询的执行,从数据库中加载关联对象的真实数据。
加载完成后,MyBatis会用加载到的真实对象替换掉原来的代理对象(或者通过代理对象持有真实对象的引用)。
后续再调用该属性的方法时,将直接操作已经加载完成的真实对象数据,不再触发SQL查询。
关键配置: 在MyBatis全局配置(
mybatis-config.xml
)或映射文件中,通过lazyLoadingEnabled=true
开启全局延迟加载,aggressiveLazyLoading=false
(推荐)防止“侵入性”加载(访问一个延迟属性就加载所有延迟属性)。
Q: Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别?
A:
能。 MyBatis非常擅长处理对象关联映射。
实现方式: 主要有两种:
嵌套查询 (Nested Select):
定义:执行一条主查询SQL获取主对象结果集。然后,对于结果集中的每一行(即每一个主对象),根据配置的
<association>
或<collection>
标签中的select
属性指定的另一个Mapper语句的ID,再发起额外的SQL查询去加载其关联对象。特点:
会产生 N+1 查询问题(1条主查询 + N条关联查询,N是主查询结果集行数)。
SQL语句相对简单。
适用于关联对象数据量较小,或者关联查询的条件比较复杂(不适合用JOIN写)的场景。
可以配合延迟加载使用。
嵌套结果 (Nested Results):
定义:编写一条复杂的联合查询SQL (JOIN),一次性查询出主对象及其关联对象的所有字段。然后,在ResultMap中使用
<association>
或<collection>
标签的resultMap
属性(或内联<result>
)来手动定义如何从联合查询的大结果集中拆分出关联对象的字段并进行映射。特点:
只有一条SQL,避免了N+1问题,性能通常更好。
SQL语句相对复杂(需要写JOIN)。
适用于主查询结果集数据量较大,且关联对象字段较少、结构相对简单的场景。
无法直接使用延迟加载(因为数据是一次性查出来的)。
主要区别总结:
特性 嵌套查询 (Nested Select) 嵌套结果 (Nested Results) SQL数量 1 (主) + N (关联) 1 (联合查询) 性能 可能存在 N+1 问题,效率较低 效率较高 (单次查询) SQL复杂度 主SQL和关联SQL相对简单 SQL较复杂 (需JOIN) 适用场景 关联数据少/查询条件复杂 主数据量大/关联字段少且简单 延迟加载 支持 不支持
Q: Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
A:
封装过程: 主要由
ResultSetHandler
组件完成。执行SQL后,获取JDBC
ResultSet
结果集。读取
ResultSet
的元数据(ResultSetMetaData
),获取列名、列类型等信息。根据Mapper中配置的结果映射规则(
resultType
或resultMap
),确定目标Java类型。通过反射创建目标Java对象的实例。
遍历结果集的每一行:
根据列名(或
resultMap
中指定的column
)查找目标对象中对应的属性名(或resultMap
中指定的property
)。利用反射找到属性的Setter方法(或直接访问字段,如果配置允许)。
将
ResultSet
中当前行对应列的值取出,进行必要的类型转换(MyBatis内置了大量TypeHandler处理基础类型、日期等)。调用Setter方法(或直接设置字段值)将转换后的值注入到目标对象中。
将填充好数据的对象添加到返回的集合(
List
)或作为单个对象返回。
映射形式:
resultType
(自动映射):指定一个简单的Java类型(如基本类型包装类、String)或POJO类的全限定名。
MyBatis会基于列名与属性名的匹配规则(开启
mapUnderscoreToCamelCase
可启用下划线转驼峰)自动进行映射。要求数据库列名(或别名)与POJO属性名严格匹配(忽略大小写?通常不忽略,取决于DB驱动)。无法处理复杂关系(如关联对象)。
resultMap
(自定义映射):定义一个
<resultMap>
标签,在其中显式、精细地指定数据库列(column
)与Java对象属性(property
)之间的映射关系。功能最强大,可以处理:
列名与属性名不一致。
复杂类型属性(如关联对象
<association>
,<collection>
)。构造函数映射(
<constructor>
)。鉴别器(
<discriminator>
)实现继承映射。使用
<id>
标签标识主键提升性能。
构造函数映射 (
<constructor>
within<resultMap>
):是
resultMap
的一部分。用于当目标对象没有无参构造器,或者你想通过构造器注入结果值而非Setter注入时。
在
<resultMap>
中使用<constructor>
标签,内部通过<idArg>
(主键参数)和<arg>
(普通参数)标签指定构造器参数对应的数据库列。
Q: Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?
A: B标签可以定义在A标签的前面或后面。 因为MyBatis在解析整个XML映射文件时,会先将所有的
<sql>
片段(B标签)和<select>/<insert>/<update>/<delete>
等语句(A标签)一次性加载到内存中,构建一个完整的DOM树或内部数据结构。之后,在处理<include>
标签引用<sql>
片段时,它只需要根据refid
在这个已经加载好的全局结构中找到对应的片段进行替换即可。所以定义顺序不影响解析和引用。
Q: MyBatis里面的动态Sql是怎么设定的?用什么语法?
A:
设定: 动态SQL主要在Mapper XML映射文件中定义(也可以在注解中使用
@SelectProvider
等实现,但XML方式更强大常用)。通过使用MyBatis提供的一系列特殊标签和OGNL (Object-Graph Navigation Language) 表达式来实现。核心标签:
<if>
: 条件判断。test
属性写OGNL表达式,表达式结果为true
时,包含其中的SQL片段才会被拼接。xml
<if test="title != null"> AND title = #{title} </if>
<choose> / <when> / <otherwise>
: 类似Java的switch-case-default
,实现多分支选择。xml
<choose><when test="state == 'ACTIVE'"> ... </when><when test="state == 'INACTIVE'"> ... </when><otherwise> ... </otherwise> </choose>
<trim> / <where> / <set>
:<where>
: 智能处理WHERE
子句。它会自动移除其内部片段开头多余的AND
或OR
,并且只在至少有一个子条件成立时才插入WHERE
关键字。避免WHERE
后直接跟AND
的错误。<set>
: 智能处理UPDATE
语句中的SET
子句。它会自动移除结尾的逗号,
,并且只在至少有一个子条件成立时才插入SET
关键字。<trim>
: 更通用的字符串修剪标签。通过prefix
(添加前缀)、suffix
(添加后缀)、prefixOverrides
(移除前缀中匹配的字符串)、suffixOverrides
(移除后缀中匹配的字符串)属性实现更灵活的修剪。<where>
和<set>
是其特定场景的便捷实现。
<foreach>
: 遍历集合(如List, Set, Array, Map),常用于IN
条件或批量操作。xml
<foreach item="item" index="index" collection="list"open="(" separator="," close=")">#{item} </foreach>
<bind>
: 创建一个变量并将其绑定到当前上下文,常用于模糊查询拼接或复杂表达式复用。xml
<bind name="pattern" value="'%' + name + '%'" /> AND username LIKE #{pattern}
语法 (OGNL 表达式): 在
test
属性中编写逻辑判断表达式。常用:判断对象/属性是否为
null
:param != null
,name == null
判断字符串是否为空/非空:
name != null and name != ''
(或使用@org.apache.commons.lang3.StringUtils@isNotBlank(name)
如果导入了库)判断集合/数组是否为空/非空:
list != null and !list.isEmpty()
,array.length > 0
比较操作符:
==
,!=
,<
,>
,<=
,>=
逻辑操作符:
and
(或&&
),or
(或||
),not
(或!
)调用JavaBean方法:
user.isAdmin()
Q: Mybatis都有哪些Executor执行器?它们之间的区别是什么?
A: MyBatis有三种基本的
Executor
执行器:SimpleExecutor
(简单执行器,默认):行为: 每次执行
update
或query
操作时,都会创建一个新的PreparedStatement
对象。特点: 执行完SQL后,立即关闭该
PreparedStatement
对象(以及对应的ResultSet
)。优点: 实现简单。
缺点: 没有
Statement
复用,效率较低。
ReuseExecutor
(重用执行器):行为: 重用预处理语句(
PreparedStatement
)。在执行SQL时,会先根据SQL语句本身作为Key在一个Map缓存中查找是否存在可用的PreparedStatement
。如果存在则直接使用;如果不存在,则创建新的PreparedStatement
并放入缓存。特点: 同一个SQL语句(严格匹配)在同一个
SqlSession
内只会预编译一次。优点: 减少了数据库对相同SQL的预编译开销,提高了效率(尤其对于频繁执行相同SQL的场景)。
缺点: 缓存作用域是
SqlSession
级别。缓存的是PreparedStatement
,不是Connection
。
BatchExecutor
(批量执行器):行为: 专门用于批量更新操作 (
insert
,update
,delete
)。特点: 将所有更新操作都添加到一个批处理队列中。等待显式调用
commit()
、flushStatements()
或执行一个查询操作时,才一次性将所有队列中的更新操作发送到数据库执行。优点: 大幅提升批量操作的性能,减少网络IO次数。
缺点: 不适用于查询(
select
)。使用时需要注意事务管理和内存占用(队列大小)。在SqlSession
关闭或commit
时,未执行的批处理会被丢弃。
CachingExecutor
(缓存执行器): 严格来说这不是一个“基本”执行器,而是一个装饰器。它包裹在上述三种执行器之上,提供二级缓存功能。先查询二级缓存,缓存未命中再委托给底层的Executor
去数据库查询,并将查询结果存入二级缓存。区别总结:
执行器 核心行为 适用场景 主要优点 主要缺点 SimpleExecutor 每次执行创建新Statement,用完即关 通用 简单 效率低 (无复用) ReuseExecutor 复用相同SQL的PreparedStatement 频繁执行相同SQL 减少预编译开销 作用域仅限SqlSession BatchExecutor 批量收集更新操作,最后一次性执行 批量插入/更新/删除 大幅提升批量性能 不适用查询;需注意提交和内存 CachingExecutor 装饰器,提供二级缓存 需要利用二级缓存 减少数据库访问 需要管理缓存一致性
Q: 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
A:
半自动ORM: MyBatis被称为半自动ORM映射工具,核心在于它只自动化了SQL执行和结果集映射到对象的过程。开发者需要手动编写具体的SQL语句(或通过Mapper接口方法+注解定义)。开发者对SQL拥有完全的掌控权,可以精细地优化SQL性能,处理复杂查询和数据库特性。
全自动ORM (如 Hibernate, JPA): 全自动ORM框架的目标是让开发者完全面向对象编程,不需要手动编写SQL。框架根据定义的实体类(Entity)和映射关系(Annotations/XML),在运行时自动生成所需的SQL语句(CRUD操作、关联查询等)。开发者操作的是对象,框架负责对象与数据库表记录的转换和SQL生成。
核心区别:
特性 MyBatis (半自动) Hibernate/JPA (全自动) SQL控制 开发者手动编写、控制SQL 框架自动生成SQL 灵活性 极高,可编写任意复杂、优化SQL 受限,依赖于框架生成的SQL 学习曲线 相对较低,需熟悉SQL 相对较高,需理解ORM抽象和HQL/JQL 性能调优 直接优化SQL,调优更直接 需理解框架行为、缓存、抓取策略等 数据库移植性 较差,SQL可能依赖特定数据库语法 较好,框架负责方言转换 开发效率 简单CRUD需写SQL,效率可能稍低 简单CRUD效率高,复杂查询需HQL 适用场景 需要精细SQL控制、复杂查询、高性能优化、遗留数据库 快速开发、数据库无关性强、对象模型复杂
Q: 简单介绍下你对Mybatis的理解?
A: MyBatis是一款优秀的半自动化的持久层框架。它通过XML配置文件或注解将SQL语句与Java对象(POJO)的映射关系解耦。MyBatis的核心价值在于它封装了JDBC底层的繁琐操作(如手动创建/关闭
Connection
,Statement
,ResultSet
,手动设置参数,手动处理结果集映射),让开发者只需关注SQL本身和业务对象。它提供了强大的特性:动态SQL: 灵活构建复杂SQL。
灵活的映射: 支持自动映射(
resultType
)和高度自定义映射(resultMap
),处理复杂对象关系。插件机制: 可扩展框架行为。
缓存: 提供一级缓存(SqlSession级别)和二级缓存(Namespace/Mapper级别)提升性能。
与Spring集成: 无缝整合Spring IoC和事务管理。
它平衡了SQL控制力与开发效率,尤其适合需要对SQL进行深度优化和控制的场景,是Java持久层框架中的重要选择。
三、Spring Core (IoC, Bean, AOP)
Q: 介绍一下Spring的事物管理?
A: Spring提供了声明式和编程式两种事务管理方式,旨在简化事务管理代码,提供一致的编程模型,并支持多种事务API(JDBC, JPA, Hibernate, JTA等)。
核心接口:
PlatformTransactionManager
(平台事务管理器) 是核心抽象。针对不同持久化技术有具体实现(如DataSourceTransactionManager
用于JDBC/MyBatis,HibernateTransactionManager
,JpaTransactionManager
,JtaTransactionManager
)。声明式事务 (主流): 基于AOP (面向切面编程) 实现。开发者通过配置(XML或
@Transactional
注解)来定义事务的属性(传播行为、隔离级别、超时、只读、回滚规则),而无需在业务代码中编写事务管理代码(如begin
,commit
,rollback
)。Spring容器在运行时为配置了事务属性的Bean创建代理对象,在目标方法执行前后加入事务管理的逻辑。优点: 非侵入性,业务代码清晰,配置灵活。编程式事务: 在代码中显式调用Spring提供的事务管理API(如
TransactionTemplate
或直接使用PlatformTransactionManager
)来控制事务边界(开始、提交、回滚)。优点: 更精细的控制。缺点: 侵入性强,代码冗余,通常只在需要非常精细控制或声明式无法满足的复杂场景中使用。关键配置属性 (
@Transactional
):传播行为 (Propagation): 定义事务方法在调用另一个事务方法时的行为(7种,如
REQUIRED
-默认,存在则加入,不存在则新建;REQUIRES_NEW
-总是新建事务;NESTED
-嵌套事务等)。隔离级别 (Isolation): 定义事务的隔离程度(解决脏读、不可重复读、幻读问题),如
DEFAULT
(使用数据库默认)、READ_COMMITTED
、REPEATABLE_READ
、SERIALIZABLE
。超时 (timeout): 事务超时时间(秒),超过自动回滚。
只读 (readOnly): 提示数据库优化(设置为
true
)。回滚规则 (rollbackFor / noRollbackFor): 指定哪些异常触发回滚或哪些异常不触发回滚。
优势: 统一API、支持声明式(减少样板代码)、灵活配置传播隔离等、与Spring生态无缝集成。
Q: Bean 工厂和 Application contexts 有什么区别?
A:
BeanFactory
和ApplicationContext
都是Spring IoC容器的核心接口,用于管理Bean的生命周期、依赖注入。ApplicationContext
是BeanFactory
的一个功能更强大的子接口。BeanFactory
(基础容器):提供最基础的IoC功能:Bean的实例化、配置、组装、生命周期管理(调用初始化
init
和销毁destroy
方法)。采用懒加载 (Lazy-loading) 策略:Bean在第一次被请求时才创建和初始化。
功能相对有限,不支持AOP、事件、资源访问、国际化等企业级特性。
适用于资源受限的环境(如移动设备、Applet)。
ApplicationContext
(高级容器,主流):继承了
BeanFactory
的所有功能,是它的超集。提供更丰富的企业级服务:
继承
MessageSource
,支持国际化 (i18n)。继承
ResourceLoader
,提供更便捷的资源文件访问方式 (如classpath:
,file:
,url:
)。支持事件发布/订阅机制 (
ApplicationEventPublisher
)。集成AOP功能。
更方便的访问特定环境信息 (
Environment
)。自动注册
BeanPostProcessor
和BeanFactoryPostProcessor
。
默认采用预实例化 (Eager-loading) 策略:容器启动时,单例作用域(Singleton)的Bean默认会被立即创建和初始化(除非显式配置为懒加载)。这有助于在应用启动时尽早发现配置错误。
是Spring应用的主要入口点,代表完整的应用上下文。
总结:
ApplicationContext
在BeanFactory
的基础上增加了大量面向应用框架的特性,是绝大多数Spring应用(尤其是Web应用)使用的容器。BeanFactory
是更底层、更轻量的容器。通常直接使用ApplicationContext
的实现类(如ClassPathXmlApplicationContext
,AnnotationConfigApplicationContext
,FileSystemXmlApplicationContext
,WebApplicationContext
)。
Q: 解释Spring支持的几种bean的作用域?
A: Spring Bean的作用域定义了Bean实例的生命周期和在容器中的可见范围。常用作用域如下:
作用域 描述 适用场景 singleton (默认) 在每个Spring IoC容器中,该Bean定义只存在一个共享的实例。所有对该Bean的依赖引用都指向同一个对象。 无状态的服务、工具类、DAO层组件、配置类。 prototype 每次请求(如通过 applicationContext.getBean()
或注入点)该Bean时,容器都会创建一个新的实例。有状态的Bean(如用户会话、购物车),需要每次使用时都是新实例的场景。 request 在每个HTTP请求的生命周期内,该Bean定义只存在一个实例。仅适用于Web应用。 存储与单个HTTP请求相关的数据(如表单绑定对象、请求处理器)。 session 在每个用户HTTP Session的生命周期内,该Bean定义只存在一个实例。仅适用于Web应用。 存储用户会话状态(如登录用户信息、用户偏好设置)。 application 在整个Web应用(ServletContext)的生命周期内,该Bean定义只存在一个实例。作用域比 singleton
更广,是跨多个Servlet容器的singleton
。仅适用于Web应用。应用级别的缓存、共享资源(类似ServletContext属性)。 websocket 在每个WebSocket会话的生命周期内,该Bean定义只存在一个实例。仅适用于WebSocket应用。 存储与单个WebSocket会话相关的状态。 注意: 后四种作用域(
request
,session
,application
,websocket
)都依赖于WebApplicationContext
(如Spring MVC使用的容器)。标准ApplicationContext
只支持singleton
和prototype
。
Q: 什么是bean的自动装配?
A: Bean的自动装配 (Autowiring) 是Spring容器提供的一种机制,用于自动解析和注入一个Bean所依赖的其他Bean,而无需开发者在配置文件中(XML或Java Config)显式地通过
<property>
或<constructor-arg>
标签(或@Autowired
注解对应的配置方式)指定依赖关系。Spring容器会根据类型匹配 (byType) 或 名称匹配 (byName) 等规则,在上下文中查找符合条件的Bean并自动注入。主要方式 (XML配置中
<bean>
的autowire
属性):no
: (默认) 不自动装配,必须手动指定依赖。byName
: 根据属性名在容器中查找同名的Bean进行注入。byType
: 根据属性类型在容器中查找类型匹配的Bean进行注入。如果找到多个同类型Bean,会抛出异常。需要确保该类型只有一个Bean定义。constructor
: 类似于byType
,但是应用于构造器参数。如果容器中有多个与构造器参数类型匹配的Bean,会尝试根据参数名匹配Bean名,否则报错。
注解驱动 (主流): 使用
@Autowired
注解(或@Inject
(JSR-330),@Resource
(JSR-250))标注在字段、Setter方法或构造器上,Spring会自动查找匹配的Bean进行注入。@Autowired
默认按类型 (byType) 装配。如果需要按名称,可以结合@Qualifier("beanName")
注解使用。@Resource
默认按名称 (byName) 装配,找不到再按类型。优点: 减少显式配置,简化XML或Java Config,使代码更简洁。
缺点: 过度使用可能导致配置不够清晰,依赖关系不直观,且自动装配失败(如
byType
找到多个候选者)可能带来运行时错误。需谨慎使用,特别是在大型项目中。显式配置通常更可控。
Q: 什么是基于Java的Spring注解配置? 给一些注解的例子?
A: 基于Java的Spring注解配置是一种完全替代传统XML配置文件的Spring容器配置方式。开发者通过编写Java类(使用
@Configuration
注解标记),并在其中使用各种Spring注解(如@Bean
,@ComponentScan
,@Import
,@PropertySource
等)来定义Bean、Bean之间的依赖关系、组件扫描路径、属性源、导入其他配置类等。核心注解举例:
@Configuration
: 标记在一个类上,表明该类是一个Spring配置类,其作用类似于一个XML配置文件。Spring容器会处理其中定义的@Bean
方法。@Bean
: 标记在@Configuration
类中的方法上。该方法负责创建、配置并返回一个需要由Spring IoC容器管理的对象。方法名默认作为Bean的名称,也可以通过name
属性指定。java
@Configuration public class AppConfig {@Beanpublic DataSource dataSource() { ... } // Bean名 'dataSource'@Bean(name = "myService")public MyService myService() { // Bean名 'myService'return new MyServiceImpl(dataSource());} }
@ComponentScan
: 标记在@Configuration
类上,指示Spring去哪些包路径下扫描被@Component
及其衍生注解(@Service
,@Repository
,@Controller
)标记的类,并自动将它们注册为Spring Bean。 可指定basePackages
或basePackageClasses
。java
@Configuration @ComponentScan("com.example.service") // 扫描com.example.service包 @ComponentScan(basePackageClasses = {MyService.class}) // 扫描MyService类所在的包 public class AppConfig { ... }
@Import
: 标记在@Configuration
类上,用于导入其他配置类(可以是@Configuration
类或实现了ImportSelector
/ImportBeanDefinitionRegistrar
的类)。允许模块化配置。java
@Configuration @Import({DatabaseConfig.class, SecurityConfig.class}) public class AppConfig { ... }
@PropertySource
: 标记在@Configuration
类上,用于加载外部的属性文件(如.properties
)到Spring的Environment
中。然后可以通过@Value("${prop.key}")
注入或Environment.getProperty("key")
访问。java
@Configuration @PropertySource("classpath:app.properties") public class AppConfig {@Value("${jdbc.url}")private String url;// 或者通过Environment@Autowiredprivate Environment env; }
@Profile
: 标记在@Configuration
类或@Bean
方法上,只有当指定的Profile被激活时,该配置类或Bean定义才会生效。 用于环境隔离(如dev, test, prod)。java
@Configuration @Profile("development") public class DevConfig { ... }
@Lazy
: 标记在@Component
类或@Bean
方法上,指示该Bean应延迟初始化(在第一次被请求时才创建),而不是在容器启动时立即创建。适用于开销大的Bean或不常用的Bean。@Scope
: 标记在@Component
类或@Bean
方法上,显式指定Bean的作用域(如@Scope("prototype")
,@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
)。
优点: 类型安全(编译器检查)、重构友好、配置更集中(在代码中)、减少XML配置的繁琐、支持条件化配置(如
@Profile
,@Conditional
)。
Q: Spring中@Autowired和@Resource之间区别是什么?
A:
@Autowired
(Spring特有) 和@Resource
(JSR-250标准) 都是用于依赖注入的注解,主要区别在于注入规则和来源:特性 @Autowired
(Spring)@Resource
(JSR-250)所属规范 Spring框架 特有 Java EE (JSR-250) 标准注解 默认注入规则 按类型 (byType) 进行装配。 按名称 (byName) 进行装配。 处理者 AutowiredAnnotationBeanPostProcessor
(Spring)CommonAnnotationBeanPostProcessor
(Spring处理JSR注解)指定名称 需要结合 @Qualifier("beanName")
注解直接使用 name
属性 (@Resource(name = "myBean")
)处理找不到的情况 默认情况下,如果找不到匹配的Bean,会抛出 NoSuchBeanDefinitionException
。可通过设置required=false
允许为null
(@Autowired(required = false)
)。如果未指定 name
且按名称找不到,则回退到按类型 (byType) 查找。如果按类型也找不到或不唯一,则抛出异常。适用范围 可标注在构造器、字段、Setter方法、普通方法上。 可标注在字段、Setter方法上。不能标注在构造器或普通方法上。 简单总结: 优先使用
@Autowired
,它是Spring生态首选,功能更灵活(支持构造器注入)。当需要明确按名称注入时,使用@Resource(name="xxx")
更直接。如果项目需要兼容非Spring容器(如其他JSR-250实现容器),@Resource
是更好的选择。
Q: 在Spring AOP 中,连接点和切入点的区别是什么?
A: 这是AOP中的两个核心概念:
连接点 (Join Point): 指在程序执行过程中的一个具体点,这个点可以被AOP框架拦截并在其上应用通知(Advice)。在Spring AOP中,连接点特指方法的执行(即当某个方法被调用时)。例如:
UserService.saveUser()
方法的调用就是一个连接点。切入点 (Pointcut): 是一个谓词(表达式),用于匹配/筛选哪些连接点(Join Point)会被通知(Advice)所作用。它定义了通知应该应用在哪些连接点上。切入点表达式描述了“在什么地方执行通知”。例如:一个切入点表达式
execution(* com.example.service.*.*(..))
匹配了com.example.service
包下所有类的所有方法的执行。只有被切入点匹配到的连接点,才会被关联的Advice拦截处理。关键区别:
连接点是程序中客观存在的点(方法执行)。
切入点是开发者定义的规则/条件,用来选择特定的连接点。
一个切入点可以匹配多个连接点。
通知 (Advice) 是与切入点关联的,它定义了在匹配到的连接点上做什么以及何时做。
Q: AOP作用是什么,底层如何实现?在哪些地方会用到?分别简述切面,切入点和通知?
A:
作用 (Why AOP?):
代码复用与模块化: 将散布在应用多处的横切关注点(Cross-Cutting Concerns)代码(如日志、事务管理、安全、性能监控、异常处理等)抽取出来,封装到独立的模块(切面)中。一处定义,多处使用。
业务逻辑纯净: 业务模块(核心关注点)的代码不再混杂横切关注点代码,更清晰、更聚焦核心业务。
提高可维护性: 当需要修改横切关注点逻辑(如更改日志格式、事务策略)时,只需修改切面,无需在所有相关业务代码中搜索修改。
提高可扩展性: 可以方便地添加或移除横切功能。
底层实现 (How? - Spring AOP): Spring AOP主要使用动态代理技术实现。
代理创建: 当Spring容器创建Bean时,如果发现该Bean匹配了某个切入点(即有通知需要应用到它),容器会为目标Bean创建一个代理对象。
如果目标对象实现了接口,默认使用JDK动态代理(基于接口)。
如果目标对象没有实现接口,则使用CGLIB库生成目标类的子类作为代理(基于类)。
代理拦截: 当客户端调用代理对象的方法时:
代理对象会检查该方法是否匹配配置的切入点。
如果匹配,则执行与该切入点关联的通知链(Before, After, Around等)。
在通知链中,最终会通过反射调用目标对象(原始Bean)的实际方法(在
Around
通知中由ProceedingJoinPoint.proceed()
触发)。
应用场景 (Where?):
日志记录: 统一记录方法入参、出参、执行时间、异常等。
事务管理: 声明式事务的核心实现(
@Transactional
)。安全控制: 方法调用前的权限检查(如
@PreAuthorize
)。性能监控: 统计方法执行耗时。
异常处理: 统一异常捕获和转换。
缓存: 方法结果缓存(
@Cacheable
)。数据校验: 方法执行前的参数校验。
核心概念 (What?):
切面 (Aspect): 横切关注点的模块化体现。它封装了通知和切入点。在Spring AOP中,切面通常是一个用
@Aspect
注解标记的Java类。它包含多个通知和切入点定义。切入点 (Pointcut): 一个表达式,用于匹配连接点(在Spring AOP中就是方法执行)。它定义了“在哪里执行通知”。例如:
@Pointcut("execution(* transfer(..))")
定义了一个匹配所有名为transfer
的方法执行的切入点。通知 (Advice): 切面在特定的连接点(由切入点匹配)上执行的动作。它定义了“做什么以及何时做”。Spring AOP支持的通知类型:
Before: 在目标方法执行之前执行通知。使用
@Before
注解。After returning: 在目标方法成功执行完成(正常返回)之后执行通知。使用
@AfterReturning
注解。After throwing: 在目标方法抛出异常退出之后执行通知。使用
@AfterThrowing
注解。After (finally): 在目标方法执行结束之后(无论正常返回还是异常退出)执行通知(类似
finally
块)。使用@After
注解。Around: 环绕目标方法的执行。这是功能最强大的通知类型,可以在目标方法执行前、后执行自定义行为,并控制是否执行目标方法、何时执行、以及如何处理返回值或异常。使用
@Around
注解。它接收一个ProceedingJoinPoint
参数,通过调用其proceed()
方法来执行目标方法。
四、SSM整合与对比
Q: SSM优缺点、使用场景?
A:
Spring Framework:
优点:
强大的IoC/DI容器: 解耦组件依赖,管理对象生命周期,提高可测试性和可维护性。
AOP支持: 模块化横切关注点(事务、日志、安全等)。
声明式事务管理: 简化数据库事务操作,提高开发效率。
强大的整合能力: 提供模板类简化各种技术的使用(JDBC, JMS, JPA等)。
丰富的生态系统: Spring Boot, Spring Cloud, Spring Security, Spring Data等。
高度模块化: 可按需引入模块。
缺点:
学习曲线: 概念众多(IoC, DI, AOP, Bean生命周期等),对新手有一定门槛。
配置复杂性: 虽然Spring Boot极大简化了配置,但深入理解和定制仍需处理较复杂的配置(尤其在大型项目)。
运行时性能开销: AOP、动态代理等机制会带来轻微性能损耗(通常可忽略)。
使用场景: 几乎适用于任何规模的Java/Java EE应用开发,是现代Java开发的基础框架。特别适合需要松耦合、可测试、可扩展、需要整合多种技术的企业级应用。
Spring MVC:
优点:
清晰的MVC分层: 分离关注点,代码结构清晰。
灵活强大的配置: 注解驱动,支持RESTful,视图技术灵活。
与Spring无缝集成: 享受Spring IoC、AOP等所有特性。
高性能: 相比Struts2等更轻量高效。
强大的数据绑定和验证: 简化请求参数处理。
强大的测试支持: 易于进行Web层单元测试和集成测试。
缺点:
配置相对较多: 虽然注解简化了很多,但完全理解DispatcherServlet、HandlerMapping等配置仍需时间(Spring Boot极大缓解)。
原生对高并发IO支持有限: 传统Servlet模型是阻塞IO。应对超高并发需结合异步(
DeferredResult
/Callable
)或转向响应式(WebFlux)。
使用场景: 构建基于Servlet API的Web应用程序,特别是需要RESTful API、需要与Spring深度集成、追求性能的Web项目。
MyBatis:
优点:
SQL完全可控: 开发者直接编写和优化SQL,性能调优直接有效。
灵活的映射:
resultMap
功能强大,能处理复杂对象关系映射。动态SQL: 灵活构建复杂查询条件。
轻量级: 学习曲线相对平缓,依赖少。
与Spring集成好: 通过
SqlSessionFactoryBean
,MapperScannerConfigurer
等轻松整合。性能优化灵活: 一级/二级缓存、插件机制、执行器选择。
缺点:
需要手动编写SQL: 简单CRUD也需写SQL,相比JPA开发效率略低。
数据库移植性差: SQL依赖特定数据库方言。
关联查询配置相对复杂: 尤其是深层次的复杂关联。
XML依赖 (可选但常用): 虽然支持注解,但复杂SQL和映射通常还是用XML。
使用场景:
需要精细控制和优化SQL性能的项目(如金融、交易系统)。
遗留系统维护或数据库Schema复杂、SQL优化空间大的系统。
开发者更熟悉SQL,或者团队有DBA需要介入SQL优化。
中小型项目,或者大型项目中需要直接操作复杂SQL的部分模块。
对数据库移植性要求不高的项目。
SSM组合使用场景: SSM组合特别适合于需要平衡SQL控制力、Web开发效率、框架灵活性和成熟度的中大型企业级Java Web应用开发。是传统Java EE三层架构(Web层-Spring MVC, Service层-Spring, Dao层-MyBatis)的经典实现。
五、整合 Hibernate (可选,但题目包含)
Q: 使用Spring通过什么方式访问Hibernate?
A: Spring 提供了多种方式来整合和访问 Hibernate:
原生 Hibernate API (使用
SessionFactory
和Session
):配置: 在Spring配置中定义一个
LocalSessionFactoryBean
,用于创建Hibernate的SessionFactory
。它需要配置数据源、实体类扫描路径、Hibernate属性(如方言hibernate.dialect
)。访问: 在DAO层,可以通过
HibernateTemplate
(Spring提供的模板类,简化Session
操作,处理异常和资源管理)或者直接注入SessionFactory
,然后通过SessionFactory.getCurrentSession()
(通常绑定到Spring事务)或openSession()
获取Session
进行数据库操作。HibernateTemplate
在早期流行,现在更推荐直接使用SessionFactory
配合Spring的声明式事务。特点: 相对底层,控制力强。
基于 JPA 标准 (使用
EntityManager
):配置: 在Spring配置中定义一个
LocalContainerEntityManagerFactoryBean
,它负责创建JPA标准的EntityManagerFactory
,并指定Hibernate作为其底层JPA实现提供者(HibernateJpaVendorAdapter
)。同样需要配置数据源、实体扫描、JPA/Hibernate属性。访问: 在DAO层,可以通过
@PersistenceContext
注解注入EntityManager
。通过EntityManager
进行符合JPA标准的持久化操作(persist()
,merge()
,find()
,createQuery()
等)。特点: 使用标准JPA API,代码更具移植性(理论上可切换JPA实现),Spring对JPA支持完善(如
@Transactional
)。这是目前更主流的方式。
Spring Data JPA:
配置: 继承方式2(配置
LocalContainerEntityManagerFactoryBean
),然后定义一个继承JpaRepository<T, ID>
的接口。访问: Spring Data JPA会自动为这个接口生成实现。开发者只需定义接口,就可以直接使用常见的CRUD方法(
save()
,findById()
,findAll()
,delete()
等)以及通过方法名约定或@Query
注解定义查询,几乎无需编写实现代码。特点: 极大简化DAO层开发,减少样板代码。非常适合标准CRUD和简单查询场景。复杂查询仍需结合
@Query
或JPA Criteria API。底层依然是Hibernate作为JPA提供者。
事务管理: 无论哪种方式,都推荐使用Spring的声明式事务管理 (
@Transactional
),并配置HibernateTransactionManager
(对于方式1) 或JpaTransactionManager
(对于方式2和3)。
Q: 如何通过HibernateDaoSupport将Spring和Hibernate结合起来?
A:
HibernateDaoSupport
是Spring提供的一个便利的DAO基类(现在已不常用,更推荐直接注入SessionFactory
或使用Spring Data JPA),用于简化整合。步骤如下:配置 SessionFactory: 在Spring的ApplicationContext配置文件中,定义一个
LocalSessionFactoryBean
Bean,配置数据源、映射资源/实体类、Hibernate属性等。创建 DAO 类: 编写具体的DAO实现类,继承
org.springframework.orm.hibernate5.support.HibernateDaoSupport
(注意版本,如hibernate3/4/5
)。注入 SessionFactory: 在DAO类中,需要通过Setter注入或构造器注入的方式,将配置好的
SessionFactory
Bean注入。HibernateDaoSupport
类内部有一个setSessionFactory(SessionFactory sessionFactory)
方法。通常通过Spring依赖注入完成:XML:
<bean id="myDao" class="com.example.dao.MyDaoImpl"><property name="sessionFactory" ref="sessionFactoryBean"/></bean>
注解: 在DAO类上使用
@Repository
,并添加一个@Autowired
的Setter方法或字段(但基类要求Setter)。
访问数据库: 在DAO方法中,可以通过
getHibernateTemplate()
方法获取HibernateTemplate
实例,然后调用其提供的方法进行数据库操作,如:getHibernateTemplate().save(Object entity)
getHibernateTemplate().get(Class entityClass, Serializable id)
getHibernateTemplate().find(String queryString, Object... values)
getHibernateTemplate().update(Object entity)
getHibernateTemplate().delete(Object entity)
HibernateTemplate
内部负责处理Session
的获取/释放、异常转换(将Hibernate专有异常转换为Spring的DataAccessException
层次结构)、事务同步等。
事务管理: 在Service层使用Spring的声明式事务管理 (
@Transactional
),并配置HibernateTransactionManager
。
注意:
HibernateTemplate
和HibernateDaoSupport
在较新的Spring/Hibernate版本中已逐渐被视为过时,因为它们:引入了额外的抽象层。
限制了直接使用原生
Session
API的灵活性。现代实践更倾向于直接注入
SessionFactory
(或EntityManager
)并使用@Transactional
管理事务