模型-视图-控制器(MVC)是一个众所周知的以设计界面应用程序为基础的设计思想。它主要通过分离模型、视图及控制器在应用程序中的角色将业务逻辑从界面中解耦。通常,模型负责封装应用程序数据在视图层展示。视图仅仅只是展示这些数据,不包含任何业务逻辑。控制器负责接收来自用户的请求,并调用后台服务(service或者dao)来处理业务逻辑。处理后,后台业务层可能会返回了一些数据在视图层展示。控制器收集这些数据及准备模型在视图层展示。MVC模式的核心思想是将业务逻辑从界面中分离出来,允许它们单独改变而不会相互影响。
Spring MVC框架也是一个基于请求驱动的Web框架,并且使用了前端控制器模式(是用来提供一个集中的请求处理机制,所有的请求都将由一个单一的处理程序处理来进行设计,再根据请求映射规则分发给相应的页面控制器(动作/处理器)进行处理。首先让我们整体看一下Spring MVC处理请求的流程:
HandlerAdapter根据请求的Handler适配并执行对应的Handler;HandlerAdapter(提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据配置,Spring将做一些额外的工作:
HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息。
数据转换:对请求消息进行数据转换。如String转换成Integer、Double等数据格式化:
数据格式化。 如将字符串转换成格式化数字或格式化日期等
数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中)
11、最终前端控制器将渲染后的页面响应给用户或客户端
对于SpringMvc 项目所有的请求入口(静态资源除外)这里都是从web.xml文件配置的前端控制器DispatcherServlet开始,
<!-- servlet请求分发器 --> <servlet> <servlet-name>springMvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:servlet-context.xml</param-value> </init-param> <!-- 表示启动容器时初始化该Servlet --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springMvc</servlet-name> <!-- 这是拦截请求, /代表拦截所有请求,拦截所有.do请求 --> <url-pattern>/</url-pattern> </servlet-mapping>
DispatcherServlet UML继承关系图如下:
这里关注蓝线部分继承结构:DispatcherServlet-->FrameworkServlet-->HttpServletBean-->HttpServlet-->GenericServlet-->Servlet,对于请求核心时序图如下:
对于web 请求的处理,大家都知道是通过继承HttpServlet重写其service方法,这里打开DispatcherServlet源码发现这里并没有看到我们要找的service方法,此时到父类FrameworkServlet 查找如下:可以看到父类重写HttpServlet service方法。
/** * Override the parent class implementation in order to intercept PATCH requests. */ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpMethod httpMethod = HttpMethod.resolve(request.getMethod()); if (httpMethod == HttpMethod.PATCH || httpMethod == null) { processRequest(request, response); } else { super.service(request, response); } }
从源码分析来看当请求方法为patch请求或者为null时执行processRequest 方法,其他情况则调用父类service 方法,大家都知道SpringMvc 请求大多请求是get|post请求为主,此时继续向上查看FrameworkServlet 父类HttpServletBean(抽象类继承HttpServlet 并未重写service方法 所以向上继续寻找)-->HttpServlet service 方法
@Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; if (!(req instanceof HttpServletRequest && res instanceof HttpServletResponse)) { throw new ServletException("non-HTTP request or response"); } request = (HttpServletRequest) req; response = (HttpServletResponse) res; service(request, response); } } protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } }
可以看到HttpServlet service 进行了重载,根据不同的请求类型然后调用不同处理方法,这里以get请求为例,当请求方法为get 请求时在重载service 方法中调用doGet 方法进行处理,这里需要特别注意的是:HttpServlet 存在doGet方法实现,然而在继承的子类中也存在doGet方法实现,到底调用哪个方法?很明显调用子类的doGet方法(面向对象多态思想!!!),从继承UML关系图上看,最外层子类实现doGet方法的为FrameworkServlet
@Override protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 系统计时开始时间 long startTime = System.currentTimeMillis(); Throwable failureCause = null; // 国际化 LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = buildLocaleContext(request); //构建ServletRequestAttributes对象 RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); //异步管理 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); //初始化ContextHolders initContextHolders(request, localeContext, requestAttributes); try { doService(request, response); } catch (ServletException | IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { //恢复原来的LocaleContext和ServiceRequestAttributes到LocaleContextHolder和RequestContextHolder,避免影响Servlet以外的处理,如Filter resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); } logResult(request, response, failureCause, asyncManager); //发布ServletRequestHandlerEvent消息,这个请求是否执行成功都会发布消息的 publishRequestHandledEvent(request, response, startTime, failureCause); } } // initContextHolders(request, localeContext, requestAttributes); private void initContextHolders(HttpServletRequest request, @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) { if (localeContext != null) { LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable); } if (requestAttributes != null) { RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable); } }
该方法大概做了这几件事:国际化的设置,创建ServletRequestAttributes对象,初始化上下文holders(即将Request对象放入到线程上下文中,如后续想要在方法中获取request,response对象此时可以通过调用LocaleContextHolder对应方法即可),然后调用doService方法。对于doService方法,FrameworkServlet 类并未提供实现,该方法由DispatcherServlet子类实现
DispatcherServlet里面执行处理的入口方法是doService,由于这个类继承于FrameworkServlet类,重写了doService()方法
@Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { logRequest(request); // Keep a snapshot of the request attributes in case of an include, // to be able to restore the original attributes after the include. Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } //Spring上下文 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); //国际化解析器 request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); //主题解析器 request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); //主题 request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); //重定向的数据 if (this.flashMapManager != null) { FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); } try { //request设置完相关的属性做真正的请求处理 doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } }
整个方法看下来处理的操作有:处理include标签的请求,将上下文放到request的属性中,将国际化解析器放到request的属性中,将主题解析器放到request属性中,将主题放到request的属性中,处理重定向的请求数据最后调用doDispatch这个核心的方法对请求进行处理。
DispatcherServlet#doDispatch
该方法是在doService方法中调用的,从底层设计了整个请求的处理流程:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { // 校验是否为上传请求 是上传请求执行解析 否则返回request processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // 根据访问的Handler 返回指定对应的HandlerExecutionChain对象 这里从HandlerMapping 集合中查找 HandlerExecutionChain 对象包含Handler与拦截器HandlerInterceptor列表 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // 根据得到的Handler 获取对应的HandlerAdaptor对象 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 处理GET、HEAD请求的Last-Modified String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); //当数据没有更改时,就直接返回上次的数据,提高效率 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } //执行Interceptor的preHandle if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 执行Handler 返回ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); //如果需要异步处理,直接返回 if (asyncManager.isConcurrentHandlingStarted()) { return; } //当view为空时,根据request设置默认view,如Handler返回值为void applyDefaultViewName(processedRequest, mv); //执行相应Interceptor的postHandle mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } //处理返回结果,包括处理异常、渲染页面,发出完成通知触发Interceptor的afterCompletion processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
1. doDispatcher首先检查是不是上传请求,如果是则将request转换为MultipartHttpServletRequest,并将multipartRequestParsed标志设置为true
通过getHandler获取Handler处理器链HandlerExecutionChain
3. 处理GET、HEAD请求的Last-Modified,这里主要判断Last-Modified值是否被修改来处理决定是否采用缓存数据。
4. 接下来依次调用相应的Interceptor的preHandle。执行拦截器拦截操作
5. 拦截器preHandle方法执行后,此时开始通过HandlerAdapter 适配对应的Handler 执行(这里才是真正要执行的Controller方法), Handler处理完请求后,如果需要异步处理则直接返回,如果不需要异步处理,当view为空时,设置默认view,然后执行相应的Interceptor的postHandle。
processDispatchResult方法主要用来处理前面返回的结果,其中包括处理异常、渲染页面、触发Interceptor的afterCompletion方法三部分内容,处理的异常是在处理请求doDispatch方的过程中产生。
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { boolean errorView = false; // 如果请求过程中有异常抛出则处理异常 if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } //执行页面渲染操作 if (mv != null && !mv.wasCleared()) { render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isTraceEnabled()) { logger.trace("No view rendering, null ModelAndView returned."); } } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; } // Handler请求处理完,触发Interceptor的afterCompletion if (mappedHandler != null) { // Exception (if any) is already handled.. mappedHandler.triggerAfterCompletion(request, response, null); } }
render 视图渲染
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine locale for request and apply it to the response. Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); response.setLocale(locale); View view; String viewName = mv.getViewName(); if (viewName != null) { // We need to resolve the view name. view = resolveViewName(viewName, mv.getModelInternal(), locale, request); if (view == null) { throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } else { // No need to lookup: the ModelAndView object contains the actual View object. view = mv.getView(); if (view == null) { throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } } if (logger.isTraceEnabled()) { logger.trace("Rendering view [" + view + "] "); } try { if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } // 渲染页面处理 view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "]", ex); } throw ex; } }
客户端请求被 DispatcherServlet(前端控制器)接收。 ->根据 HandlerMapping 映射到 Handler。 ->生成 Handler 和 HandlerInterceptor(如果有则生成)。 ->Handler 和 HandlerInterceptor 以 HandlerExecutionChain 的形式一并返回给 DispatcherServlet。 ->DispatcherServlet 通过 HandlerAdapter 调用 Handler 的方法做业务逻辑处理。 ->返回一个 ModelAndView 对象给 DispatcherServlet。 ->DispatcherServlet 将获取的 ModelAndView 对象传给 ViewResolver 视图解析器,将逻辑视图解析成物理视图 View。 ->ViewResolver 返回一个 View 给 DispatcherServlet。 ->DispatcherServlet 根据 View 进行视图渲染(将模型数据填充到视图中)。 ->DispatcherServlet 将渲染后的视图响应给客户端。
HTTP 请求是通过注解找到对应的 Controller 对象…; Controller 的 Method 也是通过注解与 HTTP 请求映射的; 使用map 当做 ioC 容器,完成储存所有参数与业务的class;
初始化工作完成,接下来处理 HTTP 请求,业务流程如下: DispatcherServlet 接收请求,通过映射从 IoC 容器中获取对应的 Controller 对象; 根据映射获取 Controller 对象对应的 Method; 调用 Method,获取返回值; 将返回值传给视图解析器,返回物理视图; 完成页面跳转。
@MyController
@MyRequestMapping
/** * 自定义 @RequestMapping 注解 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface MyRequestMapping { String value() default ""; } /** * @auther SyntacticSugar * @data 2018/11/13 0013下午 9:15 */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface MyController { String value() default ""; }
/** * @auther SyntacticSugar * @data 2018/11/13 0013下午 9:20 * <p> * 创建控制器 */ public class MyDispatcherServlet extends HttpServlet { //创建ioC 创建 handler存放容器 private HashMap<String, Object> ioC = new HashMap<>(); private HashMap<String, Method> handlerMapping = new HashMap<>(); //自定义视图解析 private MyViewResolver myViewResolver; @Override public void init(ServletConfig config) throws ServletException { // 把controller放到ioC中 scanController(config); //初始化handler 映射 initHandlerMapping(); //加载视图解析器 loadViewResolver(config); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String handlerUri = req.getRequestURI().split("/")[2]; String methodUri = req.getRequestURI().split("/")[3]; // Object o = ioC.get(handlerUri); Method method = handlerMapping.get(methodUri); // 使用反射机制,调用执行 业务 try { String value = (String) method.invoke(o); // 将逻辑视图 转化为 物理视图,交给 view渲染返回前端 String result = myViewResolver.jspMapping(value); req.getRequestDispatcher(result).forward(req,resp ); } catch (Exception e) { e.printStackTrace(); } } /** * saxReader 解析springmvc.xml * @param config */ private void scanController(ServletConfig config) { SAXReader saxReader = new SAXReader(); try { String path = config.getServletContext().getRealPath("") + "//WEB-INF//classes//" + config.getInitParameter("contextConfigLocation"); Document document = saxReader.read(path); // 获取根元素 Element rootElement = document.getRootElement(); Iterator iterator = rootElement.elementIterator(); // 遍历 nodes 、sax解析每一行xml while (iterator.hasNext()) { Element next = (Element) iterator.next(); // 把每一个元素的name和 component-scan 比较,获取base-package值 if (next.getName().equals("component-scan")) { String packageName = next.attributeValue("base-package"); // 获取包下 每个子包 List<String> classNames = getClassNames(packageName); for (String className : classNames) { /** 通过反射获取clazz ,判断class上是否存在MyController注解 * 若存在 、获取MyRequestMapping 的 value , 并将其 装入 自定义的ioC */ Class<?> clazz = Class.forName(className); if (clazz.isAnnotationPresent(MyController.class)) { MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class); String value = annotation.value().substring(1); // 放入ioC ioC.put(value,clazz.newInstance() ); } } } } } catch (Exception e) { e.printStackTrace(); } } /** * 获取 <context:component-scan base-package="com.baidu"/> 所有class 全路径名 * @param packageName * @return */ private List<String> getClassNames(String packageName) { List<String> classNameList = new ArrayList<String>(); String path = packageName.replace(".", "/"); //已知存在包路径,获取每一级路径下的file、 获取类加载器, ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); URL url = classLoader.getResource(path); //非空判断 if (url != null) { File[] files = new File(url.getPath()).listFiles(); //遍历取值 for (File childFile : files) { String className = packageName + "." + childFile.getName().replace(".class", ""); classNameList.add(className); } } // return return classNameList; } /** * 初始化 handler */ private void initHandlerMapping() { //从ioC中取出 MyController注解的 class for (String s : ioC.keySet()) { Class<?> clazz = ioC.get(s).getClass(); Method[] methods = clazz.getMethods(); //遍历 for (Method method : methods) { // 判断哪一个被 @MyRequestMapping 注解标识 if (method.isAnnotationPresent(MyRequestMapping.class)) { MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class); String value = annotation.value().substring(1); // 存入 handler handlerMapping.put(value,method ); } } } } /** * 加载自定义视图 saxReader 解析springmvc.xml * @param config */ private void loadViewResolver(ServletConfig config) { SAXReader reader = new SAXReader(); try { String path = config.getServletContext().getRealPath("")+"//WEB-INF//classes//"+config.getInitParameter("contextConfigLocation"); Document document = reader.read(path); Element root = document.getRootElement(); Iterator iter = root.elementIterator(); //遍历 while(iter.hasNext()){ Element ele = (Element) iter.next(); if(ele.getName().equals("bean")){ String className = ele.attributeValue("class"); Class clazz = Class.forName(className); Object obj = clazz.newInstance(); //获取 setter 方法 Method prefixMethod = clazz.getMethod("setPrefix", String.class); Method suffixMethod = clazz.getMethod("setSuffix", String.class); Iterator beanIter = ele.elementIterator(); //获取 property 值 Map<String,String> propertyMap = new HashMap<String,String>(); while(beanIter.hasNext()){ Element beanEle = (Element) beanIter.next(); String name = beanEle.attributeValue("name"); String value = beanEle.attributeValue("value"); propertyMap.put(name, value); } for(String str:propertyMap.keySet()){ //反射机制调用 setter 方法,完成赋值 if(str.equals("prefix")){ prefixMethod.invoke(obj, propertyMap.get(str)); } if(str.equals("suffix")){ suffixMethod.invoke(obj, propertyMap.get(str)); } } myViewResolver = (MyViewResolver) obj; } } } catch (Exception e) { e.printStackTrace(); } } }
/** * @auther SyntacticSugar * @data 2018/11/13 0013下午 9:31 * * 自定义视图解析器 MyViewResolver */ public class MyViewResolver { private String prefix; private String suffix; // 目标资源路径 public String jspMapping(String value){ return this.prefix+value+this.suffix; } //setter getter ...... }
/** * @auther SyntacticSugar * @data 2018/11/13 0013下午 10:47 */ @MyController @MyRequestMapping("/testController") public class TestController { @MyRequestMapping("/test") public String test(){ System.out.println("执行test相关业务"); return "index"; } }
<?xml version="1.0" encoding="UTF-8"?> <beans> <component-scan base-package="com.baidu"/> <!-- 配置视图解析器 ,拦截器 --> <bean class="com.baidu.view.MyViewResolver"> <property name="prefix" value="/"/> <property name="suffix" value=".jsp"/> </bean> </beans>