1. 谈谈你对SpringMVC的理解?
答: SpringMVC是基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架。它通过把Model,View,Controller分离,将Web层进行职责解耦。
-
M (Model):数据模型,用于封装数据。
-
V (View):视图,用于展示数据。
-
C (Controller):控制器,用于处理用户请求。
它主要通过DispatcherServlet(前端控制器)来分发请求,配合HandlerMapping(处理器映射器)、HandlerAdapter(处理器适配器)和ViewResolver(视图解析器)协同工作,极大地简化了Web开发。
2. 简述Mybatis的插件运行原理,以及如何编写一个插件?
答:
-
原理:Mybatis插件通过动态代理机制实现。Mybatis在创建Executor、StatementHandler、ParameterHandler、ResultSetHandler这四大对象时,会拦截这些对象的创建过程,通过JDK动态代理生成代理对象。当执行这些对象的方法时,会先经过插件(拦截器)逻辑(Interceptor Chain)。
-
编写步骤:
-
实现
Interceptor接口(包含intercept、plugin、setProperties方法)。 -
使用
@Intercepts注解指定要拦截的接口、方法和参数。 -
在Mybatis配置文件中配置该插件。
-
3. Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
答:
-
支持 :Mybatis支持关联对象(association)和集合关联对象(collection)的延迟加载。需要配置
lazyLoadingEnabled=true。 -
原理:基于动态代理(CGLIB或Javassist)。当主对象被加载时,关联属性并没有被立即查询,而是生成了一个代理对象。当程序第一次调用该关联属性的get方法时,代理对象会拦截调用,触发SQL查询加载真实数据,这叫做"按需加载"。
4. Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别?
答: 能。
-
实现方式:
-
单独发送SQL:先查主表,得到ID后再查从表(N+1问题)。
-
嵌套查询(Nested Queries) :在ResultMap中配置association或collection标签的
select属性。 -
嵌套结果(Nested Results):使用联表查询(JOIN),在ResultMap中配置映射规则。
-
-
区别:嵌套查询简单但可能产生N+1问题,性能较差;嵌套结果(联表)通常只发送一条SQL,性能较好,但ResultMap编写较复杂。
5. Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
答:
-
原理 :通过
ResultSetHandler处理JDBC返回的ResultSet。它利用反射机制,根据映射配置,将数据库列值赋给Java对象的属性。 -
映射形式:
-
自动映射:当列名与属性名一致(或开启驼峰转换)时自动封装。
-
ResultMap:通过XML明确指定列名与属性的对应关系。
-
注解映射 :使用
@Results和@Result注解。
-
6. Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?
答: B标签可以定义在A标签的后面。Mybatis解析XML时,是解析整个文档并加载到Configuration中,<sql>标签的解析和引用是在解析过程中通过ID查找的,顺序通常不影响引用。
7. MyBatis里面的动态Sql是怎么设定的?用什么语法?
答:
-
设定:在XML映射文件中使用特定的标签来编写逻辑判断。
-
语法/标签 :使用OGNL表达式。常见标签包括:
<if>,<choose>,<when>,<otherwise>,<trim>,<where>,<set>,<foreach>。
8. Mybatis都有哪些Executor执行器?它们之间的区别是什么?
答: 主要有三种:
-
SimpleExecutor(默认):每执行一次update或select,就开启一个Statement对象,用完立刻关闭。
-
ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完不关闭,可重复使用。
-
BatchExecutor:执行update(没有select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),缓存了多个Statement对象。
9. 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
答:
-
半自动:Mybatis在查询关联对象或进行复杂SQL操作时,需要手动编写SQL语句来完成映射。它负责将结果映射到对象,但不完全接管SQL的生成。
-
全自动(如Hibernate):框架根据对象模型自动生成SQL语句,开发者几乎不需要写SQL。
-
区别:Mybatis胜在灵活,容易优化SQL;Hibernate开发效率高,但优化复杂SQL较难。
10. 简单介绍下你对mybatis的理解?
答: Mybatis是一个优秀的持久层框架,它对JDBC进行了封装,屏蔽了JDBC API底层访问细节(如注册驱动、创建Statement、处理ResultSet)。它支持定制化SQL、存储过程以及高级映射。Mybatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。通过XML或注解配置,将Java POJO映射成数据库记录。
11. 介绍一下Spring的事物管理?
答: Spring提供了编程式事务管理和声明式事务管理。
-
编程式 :在代码中显式调用
beginTransaction()、commit()、rollback(),灵活性高但侵入性强。 -
声明式 (推荐):基于AOP实现。通过
@Transactional注解或XML配置,将事务管理逻辑与业务逻辑分离。Spring支持多种事务管理器(如DataSourceTransactionManager, HibernateTransactionManager),并定义了事务的传播行为(Propagation)和隔离级别(Isolation)。
12. SSM优缺点、使用场景?
答:
-
优点:
-
Spring:轻量级、IOC容器解耦、AOP支持、生态丰富。
-
SpringMVC:分层清晰、注解驱动开发简单、扩展性强。
-
Mybatis:SQL灵活、可控性高、性能优化方便。
-
-
缺点:配置相对繁琐(相比SpringBoot),Mybatis编写SQL工作量大。
-
场景:适合中大型互联网应用,特别是对SQL性能要求高、业务逻辑复杂的系统。
13. SpringMVC的工作流程?
答:
-
用户发送请求至DispatcherServlet。
-
DispatcherServlet收到请求调用HandlerMapping。
-
HandlerMapping找到具体的处理器(XML或注解),生成处理器对象及拦截器,返回给DispatcherServlet。
-
DispatcherServlet调用HandlerAdapter。
-
HandlerAdapter经过适配调用具体的Controller (后端控制器)。
-
Controller执行完成返回ModelAndView。
-
HandlerAdapter将结果返回给DispatcherServlet。
-
DispatcherServlet将ModelAndView传给ViewResolver进行解析。
-
ViewResolver解析后返回具体的View。
-
DispatcherServlet对View进行渲染视图(填充数据)。
-
响应用户。
14. 如果你也用过struts2.简单介绍下springMVC和struts2的区别有哪些?
答:
-
入口不同:SpringMVC入口是Servlet(DispatcherServlet),Struts2入口是Filter(StrutsPrepareAndExecuteFilter)。
-
开发方式:SpringMVC是基于方法开发的,Controller默认单例;Struts2是基于类开发的,Action是多例的(每次请求创建一个实例)。
-
参数传递:SpringMVC通过方法参数传递,Struts2通过类属性传递。
-
整合:SpringMVC是Spring家族产品,整合更无缝。
15. 怎么样把数据放入Session里面?
答: 在SpringMVC中:
-
在Controller方法参数中注入
HttpSession对象,调用session.setAttribute("key", value)。 -
使用
HttpServletRequest对象:request.getSession().setAttribute("key", value)。 -
使用
@SessionAttributes注解将Model中的属性暂存到Session中。
16. 讲下SpringMvc的执行流程?
答: (参考第13题,通常面试中流程类问题会重复确认熟悉度)简述核心点:请求 -> 前端控制器 -> 映射器 -> 适配器 -> 控制器 -> 视图解析器 -> 视图渲染。
17. MyBatis(IBatis)的好处是什么?
答:
-
解耦:将SQL代码从Java代码中分离出来,放入XML,便于管理和维护。
-
灵活:支持动态SQL,能够灵活应对复杂查询。
-
简化:自动处理JDBC繁琐的连接获取、参数设置、结果集封装。
-
性能:支持连接池、缓存机制。
18. Bean 工厂和 Application contexts 有什么区别?
答:
-
BeanFactory :是Spring最底层的接口,提供IOC容器最基本的功能。它采用懒加载(Lazy Load),即只有当getBean时才实例化Bean。启动快,内存占用小。
-
ApplicationContext :是BeanFactory的子接口,提供更多企业级功能(如AOP、国际化、事件发布)。它默认采用预加载(Eager Load),容器启动时就实例化所有单例Bean。启动慢,但响应快,能通过配置检查错误。
19. 解释Spring支持的几种bean的作用域?
答:
-
singleton(默认):单例,整个容器只有一个实例。
-
prototype:多例,每次获取Bean都创建一个新实例。
-
request:一次HTTP请求创建一个实例(仅Web环境)。
-
session:一个HTTP Session创建一个实例(仅Web环境)。
-
application/global-session:全局Web上下文(仅Web环境)。
20. 什么是bean的自动装配?
答: 自动装配(Autowiring)是指Spring容器根据Bean之间的依赖关系,自动将协作Bean注入到当前Bean中,而无需在XML中显式使用<property>或<constructor-arg>标签。可以通过byName、byType、constructor等模式实现。
21. 什么是基于Java的Spring注解配置? 给一些注解的例子?
答: 是指完全脱离XML,使用Java类和注解来定义Bean和配置Spring容器。
-
例子:
-
@Configuration:标记该类为配置类。 -
@Bean:标记方法返回一个Bean。 -
@ComponentScan:扫描组件。 -
@Import:导入其他配置类。
-
22. 使用Spring通过什么方式访问Hibernate?
答:
-
HibernateTemplate:Spring封装的模板类(老式做法)。
-
SessionFactory:直接注入SessionFactory,获取CurrentSession(推荐)。
-
JPA:Spring Data JPA,通过EntityManager操作。
23. 如何通过HibernateDaoSupport将Spring和Hibernate结合起来?
答: 让DAO类继承HibernateDaoSupport类,然后在Spring配置文件中,将SessionFactory注入给该DAO。DAO内部即可通过getHibernateTemplate()进行数据库操作。(注:这是较老的集成方式)。
24. Spring框架的事务管理有哪些优点?
答:
-
统一API:为不同的持久化技术(JDBC, Hibernate, JPA)提供一致的编程模型。
-
声明式事务:通过配置/注解即可管理事务,无需侵入业务代码。
-
灵活控制:易于配置传播行为和隔离级别。
25. 在Spring AOP 中,连接点和切入点的区别是什么?
答:
-
连接点 (Joinpoint) :程序执行过程中所有可以插入增强逻辑的点(如所有的方法调用、异常抛出等)。
-
切入点 (Pointcut) :实际被选定要插入增强逻辑的连接点。切入点是连接点的子集。
26. AOP作用是什么,底层如何实现在哪些地方会用到,分别简述切面,切入点和通知?
答:
-
作用:解耦业务逻辑和系统服务(如日志、事务、权限),提高代码复用性。
-
底层:JDK动态代理(针对接口)和CGLIB动态代理(针对类)。
-
应用:事务管理、日志记录、权限控制、性能监控。
-
概念:
-
切面 (Aspect):关注点模块化(切入点+通知)。
-
切入点 (Pointcut):定义在哪里执行。
-
通知 (Advice):定义执行什么逻辑(前置、后置、环绕等)。
-
27. Spring中AutoWired和Resource之间区别是什么?
答:
-
@Autowired :Spring提供的注解。默认按类型 (byType) 装配。如果想按名称,需配合
@Qualifier。 -
@Resource :JDK提供的注解(JSR-250)。默认按名称 (byName) 装配。如果找不到名称,再按类型。
28. TCP协议有哪些协议字段?如何保障可靠传输的?滑动窗口解决了什么问题?
答:
-
字段:源端口、目的端口、序列号(Seq)、确认号(Ack)、数据偏移、标志位(SYN, ACK, FIN等)、窗口大小、校验和等。
-
可靠性保障:通过校验和、序列号(去重、排序)、确认应答(ACK)、超时重传、流量控制、拥塞控制。
-
滑动窗口:解决了流量控制问题,允许发送方在未收到确认前发送多个数据包,提高了传输效率,同时防止接收方缓冲区溢出。
29. 什么是三次握手,什么是四次挥手。
答:
-
三次握手 (建立连接):
-
Client发SYN包,进入SYN_SEND状态。
-
Server收SYN,发SYN+ACK包,进入SYN_RECV状态。
-
Client收SYN+ACK,发ACK包,双方进入ESTABLISHED状态。
-
-
四次挥手 (断开连接):
-
Client发FIN,进入FIN_WAIT_1。
-
Server收FIN,发ACK,进入CLOSE_WAIT(此时Server仍可发送数据)。
-
Server数据发完,发FIN,进入LAST_ACK。
-
Client收FIN,发ACK,进入TIME_WAIT,Server收到ACK后关闭。
-
30. 什么是深拷贝?什么是浅拷贝?
答:
-
浅拷贝 (Shallow Copy) :创建一个新对象,属性是基本类型则拷贝值,是引用类型则拷贝内存地址。修改新对象的引用属性会影响原对象。
-
深拷贝 (Deep Copy):创建一个新对象,属性中引用的其他对象也会被递归创建新的实例。修改新对象完全不影响原对象。
31. 访问器如何保障线程安全?举例说明。
答: 访问器(Getter/Setter)本身不安全。要保障安全需同步:
-
使用
synchronized关键字修饰方法。 -
使用
ReentrantLock加锁。 -
使用
Atomic原子类。- 例 :
public synchronized void setVal(int v) { this.val = v; }
- 例 :
32. 抽象类和接口的不同点和相同点有哪些?
答:
-
相同:都不能实例化;都包含抽象方法。
-
不同:
-
定义 :抽象类用
abstract class,接口用interface。 -
继承:类只能继承一个抽象类,但可实现多个接口。
-
构造器:抽象类有构造器,接口没有。
-
成员 :接口成员默认是
public static final和public abstract(Java8后有default方法),抽象类可包含普通变量和非抽象方法。
-
33. final关键字能否防止指令重排序?能否代替volatile?
答:
-
final有自己的重排序规则(如构造函数内的写入不会重排序到构造函数外),在一定程度上保证对象的安全发布(初始化安全性),但它不能 完全代替volatile。 -
volatile主要保证可见性和禁止指令重排序,用于多线程间的变量修改同步;final主要用于定义不可变常量。
34. Static修饰的模块什么时候开始初始化?
答: 在类加载 (Class Loading)的初始化(Initialization)阶段。当类被初次主动使用(如new、访问静态字段/方法)时执行,且只执行一次。
35. 什么是对象锁,什么是类锁?
答:
-
对象锁 :锁的是当前对象实例(
synchronized(this)或非静态同步方法)。不同实例的锁互不干扰。 -
类锁 :锁的是Class对象(
synchronized(MyClass.class)或静态同步方法)。所有该类的实例共用这一把锁。
36. 进程和线程的区别是什么?进程间的通信方式有哪些?线程间的通信方式有哪些?
答:
-
区别:进程是资源分配的最小单位,线程是CPU调度的最小单位。进程间内存隔离,线程间共享进程内存。
-
进程通信 (IPC):管道、消息队列、共享内存、信号量、Socket。
-
线程通信:共享内存(volatile/锁)、wait/notify、Join、CountDownLatch、CyclicBarrier、ThreadLocal。
37. 类锁和对象锁是否是互斥的?
答: 不是。它们锁住的是不同的对象(一个是实例对象,一个是Class对象)。一个线程持有对象锁时,另一个线程可以获取类锁,互不阻塞。
38. 什么是注解,注解可以作用类的哪些部分?
答: 注解(Annotation)是Java代码里的特殊标记,为代码提供元数据。
- 作用范围 :类、接口、方法、字段、参数、构造器、包、局部变量等(由
@Target决定)。
39. 内部类有哪几种?分别适用于那些场景?
答:
-
成员内部类:依附于外部类实例,需访问外部类私有属性时。
-
静态内部类:不依赖外部类实例,仅逻辑相关,常用作Builder模式或辅助类。
-
局部内部类:定义在方法内,作用域仅限于该方法。
-
匿名内部类 :一次性使用,常用于回调函数、事件监听(如
new Runnable(){...})。
40. finally的作用,一般用于什么场景?
答: finally块中的代码无论是否发生异常(try块执行后)都会被执行。
- 场景:资源释放(关闭IO流、关闭数据库连接、释放锁)。
41. 什么是栈溢出?什么是堆溢出?什么是堆外内存?
答:
-
栈溢出 (StackOverflowError):方法调用层次太深(如死递归),超过了栈内存限制。
-
堆溢出 (OutOfMemoryError: Java heap space):创建的对象太多,垃圾回收无法回收,撑爆了堆内存。
-
堆外内存:JVM堆之外的直接内存(Direct Memory),受操作系统内存限制,NIO常使用它提高IO性能。
42. Java的集合类有哪些?
答:
-
Collection接口:
-
List: ArrayList, LinkedList, Vector.
-
Set: HashSet, TreeSet, LinkedHashSet.
-
Queue: PriorityQueue, ArrayDeque.
-
-
Map接口: HashMap, TreeMap, Hashtable, LinkedHashMap, ConcurrentHashMap.
43. 什么是泛型,请写一个泛型队列或者泛型栈。
答: 泛型是JDK 1.5引入的特性,允许在定义类、接口、方法时使用类型参数,提高了代码复用率和安全性。
- 泛型栈示例:
Java
java
public class MyStack<T> {
private LinkedList<T> storage = new LinkedList<>();
public void push(T v) { storage.addFirst(v); }
public T pop() { return storage.removeFirst(); }
public boolean isEmpty() { return storage.isEmpty(); }
}
44. 反射能否获取私有信息,如果能,需要注意什么?
答: 能。通过Class对象获取Field或Method后,调用setAccessible(true)即可强制访问私有成员。
- 注意:破坏了封装性;性能开销较大;可能受到安全管理器(SecurityManager)的限制。
45. 线程的状态有哪些?
答: NEW(新建)、RUNNABLE(运行/可运行)、BLOCKED(阻塞)、WAITING(无限等待)、TIMED_WAITING(计时等待)、TERMINATED(终止)。
46. 线程.start()是进入什么状态?
答: 调用start()后,线程从NEW变为RUNNABLE(就绪状态),等待CPU调度执行。
47. 等待队列和阻塞队列的区别是什么?
答:
-
等待队列 (Wait Set) :调用
wait()后进入,属于逻辑等待,需要被notify()唤醒重新竞争锁。 -
阻塞队列 (Entry List/Blocking Queue):
-
synchronized锁被占用时,线程进入的同步阻塞队列。
-
JUC包中的
BlockingQueue数据结构,用于生产者-消费者模型。
-
48. sleep()和wait()的区别是什么?
答:
-
来源 :sleep是
Thread类的静态方法;wait是Object类的方法。 -
锁处理 :sleep不释放锁 ;wait释放锁。
-
唤醒 :sleep时间到自动苏醒;wait需要
notify/notifyAll(除非是wait(time))。
49. sleep(1)什么作用,都用于哪些场景?
答: 让当前线程暂停1毫秒,进入TIMED_WAITING状态,让出CPU时间片给其他线程。
- 场景:模拟网络延迟、避免死循环占用100% CPU、简单的轮询间隔。
50. 什么是中断机制?举一个中断响应的例子。
答: 中断是一种协作机制,通过调用thread.interrupt()设置中断标志位,通知线程"你应该停止了"。线程内部需检测该标志。
- 例子 :线程正在
sleep(),此时外部调用interrupt(),sleep会抛出InterruptedException,线程捕获异常后可选择退出或处理。
51. 守护线程是什么?什么是协程?
答:
-
守护线程 (Daemon Thread):在后台运行为其他线程服务的线程(如GC线程)。当所有非守护线程结束时,JVM会直接退出,守护线程也会立即终止。
-
协程 (Coroutine):用户态的轻量级线程。由程序自己管理调度,不需要操作系统介入,上下文切换开销极小(Java目前通过Project Loom/Virtual Threads支持)。
52. 形成死锁的条件是什么?如何避免死锁?
答:
-
条件:1. 互斥条件;2. 请求与保持;3. 不剥夺条件;4. 循环等待。
-
避免:破坏其中一个条件。例如:按顺序加锁(破坏循环等待)、设置超时时间、一次性申请所有资源。
53. 基本类型的数据存在jvm的哪个区域?
答:
-
如果基本类型是局部变量 :存储在栈(Stack)帧的局部变量表中。
-
如果基本类型是成员变量 (对象的属性):存储在堆(Heap)中,随对象存在。
54. 多线程的使用场景有哪些?
答: 1. 异步处理(如发送短信、日志记录);2. 并行计算(利用多核CPU);3. 处理高并发请求(Web服务器);4. 复杂的后台任务(定时任务)。
55. Throw和Throws的区别?
答:
-
Throw :用在方法内部,手动抛出一个异常对象。
-
Throws :用在方法声明上,声明该方法可能抛出的异常类型,交给调用者处理。
56. Object类的方法有哪些?分别有什么作用?
答: getClass()(获取类信息)、hashCode()、equals()、clone()(复制)、toString()、notify()/notifyAll()(唤醒)、wait()(等待)、finalize()(垃圾回收前调用)。
57. 总线锁的优缺点是什么?
答: (指CPU层面的LOCK#信号或缓存一致性协议)
-
优点:保证原子性操作,解决多核CPU可见性问题。
-
缺点:早期的总线锁会锁住CPU和内存之间的总线,导致其他CPU无法访问内存,性能开销极大(现在的CPU多用MESI缓存一致性协议优化)。
58. 写三个线程t1、t2、t3,t1线程只输出A,t2线程只输出B,t3线程只输出C,总体输出顺序控制在ABABCABABC...循环模块"ABABC"
答:
Java
java
import java.util.concurrent.locks.*;
public class ThreadPrint {
private int count = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void print(String name, int targetRem) {
for (int i = 0; i < 10; ) { // 循环次数
lock.lock();
try {
// 逻辑:0->A, 1->B, 2->A, 3->B, 4->C
while (count % 5 != targetRem && !((name.equals("A") && count % 5 == 2))) {
// 特殊处理A:余数为0或2都打印A
if(name.equals("A") && (count % 5 == 0 || count % 5 == 2)) break;
condition.await();
}
System.out.print(name);
count++;
condition.signalAll();
if(name.equals("C")) i++; // 这里简单控制总轮次
} catch (Exception e) { e.printStackTrace();
} finally { lock.unlock(); }
}
}
// 实际调用需分别启动线程,传入目标余数:T1(0), T2(1), T3(4) 且T1,T2需处理双重逻辑
}
(注:简单实现逻辑:使用一个计数器 count。T1负责 count%5==0 和 2;T2负责 1 和 3;T3负责 4。)
59. 默写懒汉式单例模式,以及代码解释
答:
Java
java
public class Singleton {
// volatile禁止指令重排,保证可见性
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查,减少加锁开销
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查,防止重复创建
instance = new Singleton();
}
}
}
return instance;
}
}
解释:这是双重检查锁(DCL)模式。不仅实现了懒加载(用时再创建),还保证了线程安全和高性能。
60. 推理:Tomcat如何访问到Controller的,内部实现原理是什么?不低于500字描述
答:
这是一个从Servlet容器(Tomcat)到Spring MVC框架的完整调用链路过程。
- Tomcat启动与连接建立
Tomcat启动时,会初始化Connector组件,监听指定端口(如8080)。当客户端发起HTTP请求时,Connector接收请求,解析TCP报文,封装成Request和Response对象。
- 容器管道处理
请求被交给Engine(引擎),经过Host(主机)、Context(应用上下文)等容器层层传递(管道模式Valve)。最终,根据web.xml或Servlet 3.0注解配置,请求找到了映射路径为/或/*的Servlet,即Spring MVC的DispatcherServlet。
- DispatcherServlet接管 (核心)
Tomcat调用DispatcherServlet.service()方法。
service()调用doDispatch()方法,这是Spring MVC的核心调度中心。
- 寻找处理器 (HandlerMapping)
doDispatch根据请求的URL(如/user/login),遍历所有注册的HandlerMapping(如RequestMappingHandlerMapping)。它查找是否有对应的方法标注了@RequestMapping("/user/login")。如果找到,返回一个HandlerExecutionChain(包含Handler对象和拦截器链)。
- 适配器执行 (HandlerAdapter)
DispatcherServlet不能直接执行Handler(因为Handler可能是各种形式,如Controller类、方法等),它需要一个HandlerAdapter。
-
适配器(如RequestMappingHandlerAdapter)负责解析HTTP请求中的参数,利用反射机制调用Controller中的具体方法。
-
在此过程中,会进行数据绑定 (DataBinding)和参数校验,将request中的参数赋值给Controller方法的形参。
- 执行Controller逻辑
反射调用Controller的业务方法,执行业务逻辑(Service -> DAO -> Database)。
方法执行完毕,返回结果(可能是String视图名,也可能是@ResponseBody的数据)。
- 视图渲染与返回
HandlerAdapter将结果封装为ModelAndView返回给DispatcherServlet。
-
如果是JSP/Thymeleaf,DispatcherServlet调用ViewResolver解析视图,进行渲染,将HTML写入Response。
-
如果是RESTful(@ResponseBody),通过HttpMessageConverter直接将对象转为JSON写入Response流。
最终,Tomcat将填充好的Response对象转换为HTTP协议报文,通过Socket返回给客户端,完成一次访问。