IOC 容器与 Controller:
- 在 Spring 框架中,所有的
Controller
都会被 Spring 的 IOC 容器管理。 - 当应用程序启动时,Spring 会扫描所有带有
@Controller
注解的类,并将它们作为 Bean 注册到 IOC 容器中。
方法扫描与 Dispatcher:
- Spring MVC 在启动时,会扫描所有
Controller
类中的方法。 - 根据方法上的注解(例如
@RequestMapping
,@GetMapping
,@PostMapping
等),Spring MVC 将这些方法与特定的 URL 模式映射,并将这些映射添加到一个中央的DispatcherServlet
中。
参数封装:
- 每个 Controller 方法的参数以及参数上的注解(如
@RequestParam
,@PathVariable
,@RequestBody
等)都会被封装成Param
对象。 - 这些
Param
对象包含了参数的类型、名称、默认值和注解类型等信息,并被存储在methodParameters
列表中。
请求处理:
- 当一个 HTTP 请求到达时,
DispatcherServlet
会根据请求的 URL 匹配到相应的处理器(Controller 方法)。 DispatcherServlet
调用匹配的处理器的process
方法来处理请求。- 在
process
方法中,根据请求的具体信息,调用对应的 Controller 方法。
返回结果:
- Controller 方法的返回值会被封装成一个
Result
对象,包含了处理是否成功以及处理的具体结果。 Result
对象会传递给视图解析器,将结果渲染成最终的视图(如 HTML、JSON 等),并返回给客户端。
实现一个简单的MVC
定义Dispatcher
static class Dispatcher {
final static Result NOT_PROCESSED = new Result(false, null);
Logger logger = LoggerFactory.getLogger(getClass());
boolean isRest;
boolean isResponseBody;
boolean isVoid;
Pattern urlPattern;
Object controller;
Method handlerMethod;
Param[] methodParameters;
public Dispatcher(String httpMethod, boolean isRest, Object controller, Method method, String urlPattern) throws ServletException {
this.isRest = isRest;
this.isResponseBody = method.getAnnotation(ResponseBody.class) != null;
this.isVoid = method.getReturnType() == void.class;
this.urlPattern = Pattern.compile(urlPattern);
this.controller = controller;
this.handlerMethod = method;
Parameter[] param = method.getParameters();
Annotation[][] paramAnnos = method.getParameterAnnotations();
this.methodParameters = new Param[param.length];
for (int i = 0; i < param.length; i++) {
methodParameters[i] = new Param(httpMethod, method, param[i], paramAnnos[i]);
}
}
定义Param
static class Param {
String name;
ParamType paramType;
Class<?> classType;
String defaultValue;
/**
* 对参数进行解析,并设置参数类型、参数名、参数默认值
*
* @param httpMethod
* @param method
* @param parameter
* @param annotations
* @throws ServletException
*/
public Param(String httpMethod, Method method, Parameter parameter, Annotation[] annotations) throws ServletException {
PathVariable pv = ClassUtils.getAnnotaion(annotations, PathVariable.class);
RequestParam rq = ClassUtils.getAnnotaion(annotations, RequestParam.class);
RequestBody rb = ClassUtils.getAnnotaion(annotations, RequestBody.class);
int total = (pv == null ? 0 : 1) + (rq == null ? 0 : 1) + (rb == null ? 0 : 1);
if (total > 1) {
throw new ServletException("Only one annotation can be used in a parameter." + method);
}
this.classType = parameter.getType();
if (pv != null) {
this.name = pv.value();
this.paramType = ParamType.PATH_VARIABLE;
} else if (rq != null) {
this.name = rq.value();
this.defaultValue = rq.defaultValue();
this.paramType = ParamType.REQUEST_PARAM;
} else if (rb != null) {
this.paramType = ParamType.REQUEST_BODY;
} else {
this.paramType = ParamType.SERVLET_VARIABLE;
if (this.classType != HttpServletRequest.class && this.classType != HttpServletResponse.class &&
this.classType != HttpSession.class && this.classType != ServletContext.class) {
throw new ServerErrorException("请给参数标记注解,不支持的参数类型 " + classType + " at method " + method);
}
}
}
定义处理结果
static record Result(boolean processed, Object returnObject) {
}
初始化
@Override
public void init(ServletConfig config) throws ServletException {
logger.info("init{}.", getClass().getName());
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) this.applicationContext;
List<BeanDefinition> beanDefinitions = configurableApplicationContext.findBeanDefinitions(Object.class);
for (BeanDefinition def : beanDefinitions) {
Class<?> beanClass = def.getBeanClass();
Object bean = def.getRequiredInstance();
Controller controller = beanClass.getAnnotation(Controller.class);
RestController restController = beanClass.getAnnotation(RestController.class);
if (controller != null && restController != null) {
throw new ServletException("Controller and RestController can not be used at the same time." + beanClass.getName());
}
if (controller != null) {
addController(false, def.getName(), bean);
}else{
addController(true, def.getName(), bean);
}
}
}
doService
void doService(String url, HttpServletRequest req, HttpServletResponse resp, List<Dispatcher> dispatchers) throws Exception {
for (Dispatcher dispatcher : dispatchers){
Result result = dispatcher.process(url, req, resp);
if(result.processed()){
Object r = result.returnObject();
if(dispatcher.isRest){
if(!resp.isCommitted()){
resp.setContentType("application/json;charset=UTF-8");
}
if(dispatcher.isResponseBody){
if(r instanceof String s){
PrintWriter pw = resp.getWriter();
pw.write(s);
pw.flush();
}else if(r instanceof byte[] data){
ServletOutputStream output = resp.getOutputStream();
output.write(data);
output.flush();
}else{
throw new ServerErrorException("Unsupported return type: " + r.getClass());
}
}
}else{
if( !resp.isCommitted()){
resp.setContentType("text/html");
}
if( r instanceof String s){
if (dispatcher.isResponseBody) {
// send as response body:
PrintWriter pw = resp.getWriter();
pw.write(s);
pw.flush();
} else if (s.startsWith("redirect:")) {
// send redirect:
resp.sendRedirect(s.substring(9));
} else {
// error:
throw new ServletException("Unable to process String result when handle url: " + url);
}
} else if (r instanceof byte[] data) {
if (dispatcher.isResponseBody) {
// send as response body:
ServletOutputStream output = resp.getOutputStream();
output.write(data);
output.flush();
} else {
// error:
throw new ServletException("Unable to process byte[] result when handle url: " + url);
}
}else if(r instanceof ModelAndView mv){
String view = mv.getViewName();
if (view.startsWith("redirect:")) {
// send redirect:
resp.sendRedirect(view.substring(9));
} else {
this.viewResolver.render(view, mv.getModel(), req, resp);
}
} else if (!dispatcher.isVoid && r != null) {
// error:
throw new ServletException("Unable to process " + r.getClass().getName() + " result when handle url: " + url);
}
}
}
return;
}
}
详细代码地址
https://github.com/laicoffee/Spring-Summer