首页 > 其他分享 >Spring Mvc

Spring Mvc

时间:2023-12-11 20:01:55浏览次数:30  
标签:return String Mvc Spring 视图 handler public 请求

SpringMvc

SpringMvc应用(常规使用)

MVC架构

MVC全名 Model View Controller ,是模型(model)一个试图(view)一个控制器(controller)的缩写.是一种用于创建Web应用程序表现层的模式

  • Model(模型):
  • 包含业务模型和数据模型,数据模型用于封装数据,业务模型用于处理业务
  • View(视图)
  • 通常指的就是我们的jsp或者html。作用一般就是展示数据的,通常视图是依据模型数据创建的
  • Controller(控制器)
  • 应用程序中处理用户交互的部分,作用一般就是处理程序逻辑的。

MVC提倡:每一层只编写自己的东西,不编写任何其他的代码;分层是为了解耦,解耦是为例维护方便和分工协作

SpringMvc是什么

  • 是springframework产品之一
  • 为了解决表现层问题的web框架
  • 本质可以认为是对servlet的封装,简化servlet开发
流程图

Spring Mvc_数据

Spring Web工作流
案例

前段访问本地接口 http://localhost:8080/demo/handle01, 前段页面显示后台服务器的时间

开发过程

  • 配置DispatcherServlet前段控制器
  • 开发处理具体业务逻辑的Handler (@Controller,@RequestMapping)
  • xml配置文件配置controller扫描,配置springmvc三大件
  • 将xml文件路径告诉springMvc(DispatcherServlet)
Spring Mvc请求处理流程

Spring Mvc_spring_02

流程数名
  • 用户发送请求至前段控制器DispatcherServlet
  • DispatcherServlet 收到请求后调用HandlerMapping处理器映射器
  • 处理器映射器根据请求URL找到具体的Handler(后端控制器),生成处理器对象以及处理器拦截器(如果有则生成)一并放回DispatcherServlet
  • DispatcherServlet调用HandlerAdapter处理器适配器去调用Handler
  • 处理器适配器执行Handler
  • Handler执行完成给处理器适配器返回ModelAndView
  • 处理器适配器向前段控制器返回ModelAndView,ModelAndView是SpringMvc框架的一个底层对象,包括Model 和View
  • 前端控制器请求视图解析器去进行视图解析,根据逻辑视图名来解析真正的视图
  • 视图解析器向端段控制器返回View
  • 前端控制器进行视图渲染,就是将数据模型(在ModelAndView对象中)填充到request域中
  • 前端控制器向用户响应结果
SpringMvc九大组件
  • HandlerMapping
  • HandlerMapping用来查找Handler的,也就是处理器,具体的表现形式可以是类,也可以是方法,比如,标注了@RequestMapping的每个方法可以看成是一个Handler。Handler负责具体实际的请求处理,在请求到达后,HandlerMapping的作用便是找到请求对应的处理器Handler和Intercepor
  • HandlerAdapter(处理器适配器)
  • HandlerAdapter是一个适配器。因为SpringMvc中Handler可以是任意形式的,只要能处理请求即可,但是把请求交给servlet 的时候,由于servlet的方法结构都是doservice(HTTPServletRequest req,HttpServletResponse resp)形式的,要让固定的servlet处理方法调用Handler来进行处理,便是HandlerAdapter的职责
  • HandlerExceptionResolver
  • HandlerExceptionResolver用于处理Handler产生的异常情况,它的作用是根据异常设置ModelAndView,之后交给渲染方法进行渲染,渲染方法会将ModelAndView渲染成页面。
  • ViewResolver
  • ViewResolver即视图解析器,用于将String类型的视图名和Locale解析成View类型的视图,只有一个resolveViewName()方法。从方法的定义可以看出,Controller层返回的String类型视图名的参数和数据填入模板中,生成html。ViewResoler 在这个过程中主要完成两件事:
  • ViewResolver找到渲染所用的模板
  • 找到所用的视图类型如:jsp,默认情况下mvc会自动为我们配置一个InternalResourceViewResolver,是针对Jsp类型视图的
  • LocaleResolver
  • ViewResolver 组件的ResolveViewName方法需要两个参数,一个视图名,一个是Locale.LocaleResolver用于从请求中解析出Local,比如中国Locale是zh-CN,用来表示一个区域。这个组件也是i18n的基础
  • ThemeResolver
  • ThemeResolver组件是用来解析主题的
  • MultipartResolver
  • MultipartResolver 用于上传请求,通过将普通的请求包装成MultipartHttpServletRequest来实现。MultipartHttpServletRequest可以通过getFile()方法直接获得文件。如果上传多个文件,还可以调用getFileMap()方法得到Map<FileName,File>这样的结构,MultipartResolver的作用就是封装普通的请求,使其拥有文件上传的功能。
  • FlashMapManager
  • FlashMap 用于重定向时的参数传递,比如在处理用户订单时候,为了避免重复提交,可以处理完post请求之后重定向得到一个get请求,这个get请求可以用来显示订单详情之类的信息,这样做虽然可以避免用户重新提交订单的问题,但是在页面上要显示订单的信息,这些数据从哪里来获得呢?因为重定向时没有传递参数的功能,如果不把参数写进URL,那么就可以通过FlashMap来传递只需要在重定向之前将要传递的数据写入请求(可以通过ServletRequestAttributes.getRequest()方法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE中,这样重定向之后的Handler中Spring就会自动将其设置到Model中,在显示订单信息的页面上就可以直接从Model中获取数据。FlashMapManager就是用来管理FlashMap的
请求参数绑定
  • servlet 原生接收一个整型参数
String ageStr = request.getParameter("age");
  • mvc 只需要加上requestMapping注解,和方法中声明形参即可
@reuqestMapping
public String handle(Integer age){
    System.out.println(age)
}
  • 如果mvc的接口需要用到HTTPServletRequest、HTTPServletResponse、HttpSession等原生servlet对象时,直接在方法的形参上声明即可
  • 参数类型的绑定(形参和传递的参数要一致)要用包装类型
  • 如果参数名称不一致,可以在参数的前面加上**@RequestParam("传递的参数名")**进行映射
  • 如果是pojo类型的话,接口形参类型为pojo类型就行,但是传递的参数要和pojo中的属性名称一致
自动以转换器
  • 日期类型的格式转换
/**
 * 自定义类型转换器
 * S:source,源类型
 * T:target:目标类型
 */
public class DateConverter implements Converter<String, Date> {
    @Override
    public Date convert(String source) {
        // 完成字符串向日期的转换
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        try {
            Date parse = simpleDateFormat.parse(source);
            return parse;
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}
  • 注册自定义转换器
<!--注册自定义类型转换器-->
    <bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.lagou.edu.converter.DateConverter"></bean>
            </set>
        </property>
    </bean>
RESTFUL

RESTful是个请求的风格,基于这种风格设计请求的URl

原URL:http://localhost:8080/user/queryUser.action?id=3

RESTful:http://localhost:8080/user/queryUser.action/3

根据请求的方式不同做不同的操作

get查询获取资源

post增加,新建资源

put 更新

delete 删除资源

RESTful风格URL:互联网所有的事物都是资源,要求URL中只有表示资源的名称,没有动词。

RESTful风格资源操作:使用HTTP请求中农的method方法put,delete,post,get来操作资源。分别对应添加、删除、修改、查询。不过一般使用时还是post和get

  • 前段代码
<div>
        <h2>SpringMVC对Restful风格url的支持</h2>
        <fieldset>
            <p>测试用例:SpringMVC对Restful风格url的支持</p>
            <a href="/demo/handle/15">rest_get测试</a>
            <form method="post" action="/demo/handle">
                <input type="text" name="username"/>
                <input type="submit" value="提交rest_post请求"/>
            </form>
            <form method="post" action="/demo/handle/15/lisi">
                <input type="hidden" name="_method" value="put"/>
                <input type="submit" value="提交rest_put请求"/>
            </form>
            <form method="post" action="/demo/handle/15">
                <input type="hidden" name="_method" value="delete"/>
                <input type="submit" value="提交rest_delete请求"/>
            </form>
        </fieldset>
    </div>
  • 后台代码
/*
     * restful  get   /demo/handle/15
     */
    @RequestMapping(value = "/handle/{id}",method = {RequestMethod.GET})
    public ModelAndView handleGet(@PathVariable("id") Integer id) {

        Date date = new Date();
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("date",date);
        modelAndView.setViewName("success");
        return modelAndView;
    }

    /*
     * restful  post  /demo/handle
     */
    @RequestMapping(value = "/handle",method = {RequestMethod.POST})
    public ModelAndView handlePost(String username) {

        Date date = new Date();
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("date",date);
        modelAndView.setViewName("success");
        return modelAndView;
    }

    /*
     * restful  put  /demo/handle/15/lisi
     */
    @RequestMapping(value = "/handle/{id}/{name}",method = {RequestMethod.PUT})
    public ModelAndView handlePut(@PathVariable("id") Integer id,@PathVariable("name") String username) {

        Date date = new Date();
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("date",date);
        modelAndView.setViewName("success");
        return modelAndView;
    }

    /*
     * restful  delete  /demo/handle/15
     */
    @RequestMapping(value = "/handle/{id}",method = {RequestMethod.DELETE})
    public ModelAndView handleDelete(@PathVariable("id") Integer id) {

        Date date = new Date();
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("date",date);
        modelAndView.setViewName("success");
        return modelAndView;
    }
  • web.xml中配置请求方式过滤器(将特定的post请求转换成put和delete请求)
<!--配置springmvc请求方式转换过滤器,会检查请求参数中是否有_method参数,如果有就按照指定的请求方式进行转换-->
  <filter>
    <filter-name>hiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  </filter>
Ajax Json

交互:两个方向

  • 前端到后台:前端ajax发送json格式字符串,后台直接接收pojo参数,使用注解@RequestBody
  • 后台到前端:后台直接返回pojo对象,前端直接接收json对象或者字符串,使用注解@ResponseBody
  • @ReponseBody注解
    @ReponseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换成指定的格式之后,写入到Reponse对象的body区,通常用来返回Json数据或者是xml数据,注意:在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,他的效果等同于通过Response对象输出指定格式的数据。
@RequestMapping("/handle07")
    // 添加@ResponseBody之后,不再走视图解析器那个流程,而是等同于response直接输出数据
    public @ResponseBody User handle07(@RequestBody User user) {
        // 业务逻辑处理,修改name为张三丰
        user.setName("张三丰");
        return user;
    }

SpringMvc高级技术(拦截器、异常处理器)

拦截器(inteceptor)使用

监听器、过滤器和拦截器对比
  • servlet:处理Request请求和Response响应
  • 过滤器(Filter):对Request请求起到过滤作用,作用在servlet之前,如果配置为*/可以对所有的资源访问(sevlet、jsp/css静态资源)
  • 监听器(Listener):实现了javax.servlet.ServletContextListener接口的服务器端组件,他随着Web应用的启动而启动,只初始化一次,然后会一直运行监视,随着Web应用的停止而销毁
  • 作用一: 做一些初始化工作,Web应用中spring容器启动contextLoaderListener
  • 作用二:监听Web中的特定事件,比如HttpSession,ServletRequest的创建和销毁;变量的创建、销毁和修改等。可以在某些动作前后增加处理,实现监控,比如统计在线人数,利用HttpSessionListener等
  • 拦截器(Interceptor):是SpringMVC、Struts等表现层框架自己的,不会拦截jsp/html/image的访问等,只会拦截访问的控制器方法(Handler)
    从配置对的角度也能够总结发现:
    servlet,filter,listener是配置在web.xml中的,而interceptor是配置在表现层框架自己的配置文件中的
  • 在Handler业务逻辑执行之前拦截一次
  • 在Handler逻辑执行完毕但未跳转页面之前拦截一次
  • 在跳转页面之后拦截一次

Spring Mvc_数据_03

拦截器执行流程

在运行程序时,拦截器的执行是有一定顺序的,该顺序与配置文件中所定义的拦截器的顺序相关,单个拦截器在程序中的执行流程如下图所示:

Spring Mvc_拦截器_04

  • 程序先执行preHandle()方法,如果该方法的返回值为True,则程序会继续向下执行处理器中的方法,否则将不再向下执行
  • 在业务处理器(即控制器Controller类)处理完请求后,会执行postHandler()方法,然后会通过DispatcherServlet向客户端返回响应
  • 在DispatcherServlet处理完请求后,才会执行afterCompletion()方法
多个拦截器执行流程

多个拦截器(假设有两个拦截器Interceptor1和interceptor2,并且在配置文件中,Interceptor1拦截器配置在前),程序执行流程如下图:

Spring Mvc_spring_05

从图中可以看出,当有多个拦截器同时工作时,他们的preHandle()方法会按照配置文件中拦截的配置顺序执行,而他们的postHandler()方法和afterCompletion()方法则会按照配置顺序的反序执行

  • 自定义SpringMvc拦截器
/**
 * 自定义springmvc拦截器
 */
public class MyIntercepter01 implements HandlerInterceptor {
    
    /**
     * 会在handler方法业务逻辑执行之前执行
     * 往往在这里完成权限校验工作
     *
     * @param request
     * @param response
     * @param handler
     * @return 返回值boolean代表是否放行,true代表放行,false代表中止
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyIntercepter01 preHandle......");
        return true;
    }
    
    /**
     * 会在handler方法业务逻辑执行之后尚未跳转页面时执行
     *
     * @param request
     * @param response
     * @param handler
     * @param modelAndView 封装了视图和数据,此时尚未跳转页面呢,你可以在这里针对返回的数据和视图信息进行修改
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyIntercepter01 postHandle......");
    }

    /**
     * 页面已经跳转渲染完毕之后执行
     *
     * @param request
     * @param response
     * @param handler
     * @param ex       可以在这里捕获异常
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyIntercepter01 afterCompletion......");
    }
}
  • 注册拦截器
<mvc:interceptors>
        <!--拦截所有handler-->
        <!--<bean class="com.lagou.edu.interceptor.MyIntercepter01"/>-->
        
        <mvc:interceptor>
            <!--配置当前拦截器的url拦截规则,**代表当前目录下及其子目录下的所有url-->
            <mvc:mapping path="/**"/>
            <!--exclude-mapping可以在mapping的基础上排除一些url拦截-->
            <!--<mvc:exclude-mapping path="/demo/**"/>-->
            <bean class="com.lagou.edu.interceptor.MyIntercepter01"/>
        </mvc:interceptor>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.lagou.edu.interceptor.MyIntercepter02"/>
        </mvc:interceptor>
        
    </mvc:interceptors>
处理multipart形式的数据

文件上传

原生servlet处理上传的文件数据的,springmvc是对servlet的封装

所需jar包

<!--文件上传所需坐标-->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>
  • 配置文件上传解析器
<!--多元素解析器id固定为multipartResolver-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--设置上传文件大小上限,单位是字节,-1代表没有限制也是默认的-->
        <property name="maxUploadSize" value="5000000"/>
    </bean>

后台处理

/**
     * 文件上传
     * @return
     */
    @RequestMapping(value = "/upload")
    public ModelAndView upload(MultipartFile uploadFile,HttpSession session) throws IOException {

        // 处理上传文件
        // 重命名,原名123.jpg ,获取后缀
        String originalFilename = uploadFile.getOriginalFilename();// 原始名称
        // 扩展名  jpg
        String ext = originalFilename.substring(originalFilename.lastIndexOf(".") + 1, originalFilename.length());
        String newName = UUID.randomUUID().toString() + "." + ext;

        // 存储,要存储到指定的文件夹,/uploads/yyyy-MM-dd,考虑文件过多的情况按照日期,生成一个子文件夹
        String realPath = session.getServletContext().getRealPath("/uploads");
        String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        File folder = new File(realPath + "/" + datePath);

        if(!folder.exists()) {
            folder.mkdirs();
        }
        // 存储文件到目录
        uploadFile.transferTo(new File(folder,newName));


        // TODO 文件磁盘路径要更新到数据库字段

        Date date = new Date();
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("date",date);
        modelAndView.setViewName("success");
        return modelAndView;
    }
  • 在控制器中处理异常
// 可以让我们优雅的捕获所有Controller对象handler方法抛出的异常
@ControllerAdvice
public class GlobalExceptionResolver {

    @ExceptionHandler(ArithmeticException.class)
    public ModelAndView handleException(ArithmeticException exception, HttpServletResponse response) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg",exception.getMessage());
        modelAndView.setViewName("error");
        return modelAndView;
    }
}

注意注解和异常的控制级别

基于Flas属性的跨重定向请求数据传递

重定向请求参数会丢失,我们往往需要重新携带请求参数,我们可以进行手动参数拼接如下

return "redirect:handle01?name="+name;

但是上述拼接参数的方法属于get请求,携带参数长度有限制,参数安全性也不高,此时,我们可以使用SpringMVC提供的flash属性机制,向上下文中添加flash属性,框架会在session中记录该属性值,当跳转到也难之后会自动删除flash属性,不需要我们手动删除,通过这种方式进行重定向参数传递,参数长度和安全性都得到了保障,代码如下:

/**
     * SpringMVC 重定向时参数传递的问题
     * 转发:A 找 B 借钱400,B没有钱但是悄悄的找到C借了400块钱给A
     *      url不会变,参数也不会丢失,一个请求
     * 重定向:A 找 B 借钱400,B 说我没有钱,你找别人借去,那么A 又带着400块的借钱需求找到C
     *      url会变,参数会丢失需要重新携带参数,两个请求
     */

    @RequestMapping("/handleRedirect")
    public String handleRedirect(String name,RedirectAttributes redirectAttributes) {

        //return "redirect:handle01?name=" + name;  // 拼接参数安全性、参数长度都有局限
        // addFlashAttribute方法设置了一个flash类型属性,该属性会被暂存到session中,在跳转到页面之后该属性销毁
        redirectAttributes.addFlashAttribute("name",name);
        return "redirect:handle01";

    }

手写MVC框架(自定义MVC框架)

  • 回顾springmvc执行的大致原理,后续根据这个模仿手写自己的mvc框架

Spring Mvc_拦截器_06

手写MVC框架
  • 注解开发
  • Controller注解
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LagouController {
    String value() default "";
}
  • Service注解开发
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LagouService {
    String value() default "";
}
  • Autowired注解开发
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LagouAutowired {
    String value() default "";
}
  • RequestMapping注解开发
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LagouRequestMapping {
    String value() default "";
}
  • pojo Handler类开发,用于存储方法相关信息
/**
 * 封装handler方法相关的信息
 */
public class Handler {

    private Object controller; // method.invoke(obj,) 在动态代理执行的时候需要该类

    private Method method;

    private Pattern pattern; // spring中url是支持正则的

    private Map<String,Integer> paramIndexMapping; // 参数顺序,是为了进行参数绑定,key是参数名,value代表是第几个参数 <name,2>


    public Handler(Object controller, Method method, Pattern pattern) {
        this.controller = controller;
        this.method = method;
        this.pattern = pattern;
        this.paramIndexMapping = new HashMap<>();
    }

    public Object getController() {
        return controller;
    }

    public void setController(Object controller) {
        this.controller = controller;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public Pattern getPattern() {
        return pattern;
    }

    public void setPattern(Pattern pattern) {
        this.pattern = pattern;
    }

    public Map<String, Integer> getParamIndexMapping() {
        return paramIndexMapping;
    }

    public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) {
        this.paramIndexMapping = paramIndexMapping;
    }
  • web.xml配置
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>lgoumvc</servlet-name>
    <servlet-class>com.lagou.edu.mvcframework.servlet.LgDispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
        <!--这个配置文件里面配置了注解扫描的路径-->
      <param-value>springmvc.properties</param-value>
    </init-param>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>lgoumvc</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>
  • LgDispatcherServlet开发
public class LgDispatcherServlet extends HttpServlet {
    
    private Properties properties = new Properties();

    private List<String> classNames = new ArrayList<>(); // 缓存扫描到的类的全限定类名

    // ioc容器
    private Map<String, Object> ioc = new HashMap<String, Object>();


    // handlerMapping
    //private Map<String,Method> handlerMapping = now HashMap<>(); // 存储url和Method之间的映射关系
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1 加载配置文件 springmvc.properties
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);

        // 2 扫描相关的类,扫描注解
        doScan(properties.getProperty("scanPackage"));
        
        // 3 初始化bean对象(实现ioc容器,基于注解)
        doInstance();

        // 4 实现依赖注入
        doAutoWired();

        // 5 构造一个HandlerMapping处理器映射器,将配置好的url和Method建立映射关系
        initHandlerMapping();

        System.out.println("lagou mvc 初始化完成....");

        // 等待请求进入,处理请求
    }

    /*
        构造一个HandlerMapping处理器映射器
        最关键的环节
        目的:将url和method建立关联
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            // 获取ioc中当前遍历的对象的class类型
            Class<?> aClass = entry.getValue().getClass();
            if (!aClass.isAnnotationPresent(LagouController.class)) {
                continue;
            }
            String baseUrl = "";
            if (aClass.isAnnotationPresent(LagouRequestMapping.class)) {
                LagouRequestMapping annotation = aClass.getAnnotation(LagouRequestMapping.class);
                baseUrl = annotation.value(); // 等同于/demo
            }
            // 获取方法
            Method[] methods = aClass.getMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];

                //  方法没有标识LagouRequestMapping,就不处理
                if (!method.isAnnotationPresent(LagouRequestMapping.class)) {
                    continue;
                }
                // 如果标识,就处理
                LagouRequestMapping annotation = method.getAnnotation(LagouRequestMapping.class);
                String methodUrl = annotation.value();  // /query
                String url = baseUrl + methodUrl;    // 计算出来的url /demo/query
                // 把method所有信息及url封装为一个Handler
                Handler handler = new Handler(entry.getValue(), method, Pattern.compile(url));
                
                // 计算方法的参数位置信息  // query(HttpServletRequest request, HttpServletResponse response,String name)
                Parameter[] parameters = method.getParameters();
                for (int j = 0; j < parameters.length; j++) {
                    Parameter parameter = parameters[j];

                    if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                        // 如果是request和response对象,那么参数名称写HttpServletRequest和HttpServletResponse
                        handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), j);
                    } else {
                        handler.getParamIndexMapping().put(parameter.getName(), j);  // <name,2>
                    }
                }
                // 建立url和method之间的映射关系(map缓存起来)
                handlerMapping.add(handler);
            }
        }
    }

    //  实现依赖注入
    private void doAutoWired() {
        if (ioc.isEmpty()) {
            return;
        }
        // 有对象,再进行依赖注入处理
        // 遍历ioc中所有对象,查看对象中的字段,是否有@LagouAutowired注解,如果有需要维护依赖注入关系
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            // 获取bean对象中的字段信息
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            // 遍历判断处理
            for (int i = 0; i < declaredFields.length; i++) {
                Field declaredField = declaredFields[i];   //  @LagouAutowired  private IDemoService demoService;
                if (!declaredField.isAnnotationPresent(LagouAutowired.class)) {
                    continue;
                }
                // 有该注解
                LagouAutowired annotation = declaredField.getAnnotation(LagouAutowired.class);
                String beanName = annotation.value();  // 需要注入的bean的id
                if ("".equals(beanName.trim())) {
                    // 没有配置具体的bean id,那就需要根据当前字段类型注入(接口注入)  IDemoService
                    beanName = declaredField.getType().getName();
                }
                // 开启赋值
                declaredField.setAccessible(true);
                try {
                    declaredField.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    // ioc容器
    // 基于classNames缓存的类的全限定类名,以及反射技术,完成对象创建和管理
    private void doInstance() {
        if (classNames.size() == 0) return;
        try {

            for (int i = 0; i < classNames.size(); i++) {
                String className = classNames.get(i);  // com.lagou.demo.controller.DemoController
                // 反射
                Class<?> aClass = Class.forName(className);
                // 区分controller,区分service'
                if (aClass.isAnnotationPresent(LagouController.class)) {
                    // controller的id此处不做过多处理,不取value了,就拿类的首字母小写作为id,保存到ioc中
                    String simpleName = aClass.getSimpleName();// DemoController
                    String lowerFirstSimpleName = lowerFirst(simpleName); // demoController
                    Object o = aClass.newInstance();
                    ioc.put(lowerFirstSimpleName, o);
                } else if (aClass.isAnnotationPresent(LagouService.class)) {
                    LagouService annotation = aClass.getAnnotation(LagouService.class);
                    //获取注解value值
                    String beanName = annotation.value();
                    // 如果指定了id,就以指定的为准
                    if (!"".equals(beanName.trim())) {
                        ioc.put(beanName, aClass.newInstance());
                    } else {
                        // 如果没有指定,就以类名首字母小写
                        beanName = lowerFirst(aClass.getSimpleName());
                        ioc.put(beanName, aClass.newInstance());
                    }
                    // service层往往是有接口的,面向接口开发,此时再以接口名为id,放入一份对象到ioc中,便于后期根据接口类型注入
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (int j = 0; j < interfaces.length; j++) {
                        Class<?> anInterface = interfaces[j];
                        // 以接口的全限定类名作为id放入
                        ioc.put(anInterface.getName(), aClass.newInstance());
                    }
                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    // 首字母小写方法
    public String lowerFirst(String str) {
        char[] chars = str.toCharArray();
        if ('A' <= chars[0] && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }


    // 扫描类
    // scanPackage: com.lagou.demo  package---->  磁盘上的文件夹(File)  com/lagou/demo
    private void doScan(String scanPackage) {
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File pack = new File(scanPackagePath);
        File[] files = pack.listFiles();
        for (File file : files) {
            if (file.isDirectory()) { // 子package
                // 递归
                doScan(scanPackage + "." + file.getName());  // com.lagou.demo.controller
            } else if (file.getName().endsWith(".class")) {
                String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                classNames.add(className);
            }
        }
    }

    // 加载配置文件
    private void doLoadConfig(String contextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }


    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理请求:根据url,找到对应的Method方法,进行调用
        // 获取uri
//        String requestURI = req.getRequestURI();
//        Method method = handlerMapping.get(requestURI);// 获取到一个反射的方法
        // 反射调用,需要传入对象,需要传入参数,此处无法完成调用,没有把对象缓存起来,也没有参数!!!!改造initHandlerMapping();
//        method.invoke() //


        // 根据uri获取到能够处理当前请求的hanlder(从handlermapping中(list))
        Handler handler = getHandler(req);

        if (handler == null) {
            resp.getWriter().write("404 not found");
            return;
        }
        // 参数绑定
        // 获取所有参数类型数组,这个数组的长度就是我们最后要传入的args数组的长度
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        // 根据上述数组长度创建一个新的数组(参数数组,是要传入反射调用的)
        Object[] paraValues = new Object[parameterTypes.length];
        // 以下就是为了向参数数组中塞值,而且还得保证参数的顺序和方法中形参顺序一致
        Map<String, String[]> parameterMap = req.getParameterMap();
        // 遍历request中所有参数  (填充除了request,response之外的参数)
        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            // name=1&name=2   name [1,2]
            String value = StringUtils.join(param.getValue(), ",");  // 如同 1,2
            // 如果参数和方法中的参数匹配上了,填充数据
            if (!handler.getParamIndexMapping().containsKey(param.getKey())) {
                continue;
            }
            // 方法形参确实有该参数,找到它的索引位置,对应的把参数值放入paraValues
            Integer index = handler.getParamIndexMapping().get(param.getKey());//name在第 2 个位置
            paraValues[index] = value;  // 把前台传递过来的参数值填充到对应的位置去
        }
        int requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName()); // 0
        paraValues[requestIndex] = req;
        int responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName()); // 1
        paraValues[responseIndex] = resp;
        // 最终调用handler的method属性
        try {
            handler.getMethod().invoke(handler.getController(), paraValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()) {
            return null;
        }
        String url = req.getRequestURI();
        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(url);
            if (!matcher.matches()) {
                continue;
            }
            return handler;
        }
        return null;
    }
}
  • 控制层代码
@LagouController
@LagouRequestMapping("/demo")
public class DemoController {
    @LagouAutowired
    private IDemoService demoService;
    /**
     * URL: /demo/query?name=lisi
     * @param request
     * @param response
     * @param name
     * @return
     */
    @LagouRequestMapping("/query")
    public String query(HttpServletRequest request, HttpServletResponse response,String name) {
        return demoService.get(name);
    }
}

SpringMvc源码分析

前端控制器DispatcherServlet继承结构

Spring Mvc_拦截器_07

重要时机点分析
  • Handler方法的执行时机

观察调用栈

Spring Mvc_springMvc_08

doDispatcher方法中的ha.handle方法完成handler的调用

  • 页面渲染时机

Spring Mvc_springMvc_09

  • springmvc处理请求的流程即为

org.springframework.web.servlet.DispatcherServlet#doDispatcher方法的执行流程,其中步骤2、3、4、5是核心步骤

  • 调用getHandler()获取到能够处理当前请求的执行链HandlerExecutionChain(Handler+拦截器)具体如何调用,后面分析
  • 调用getHandlerAdapter()获取能够执行上面的适配器
  • 适配器调用Handler执行ha.handle,返回一个ModelAndView对象
  • 调用processDispatchResult()方法完成视图渲染跳转
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 {
				//1.检查是否是文件上传的请求
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				/**
				 * 2.获取处理当前请求的Controller,这里也称为Handler,即处理器
				 * 这里并不是直接返回Controller,而是返回HandlerExecutionChain请求处理链对象
				 * 该对象封装了Handler和Inteceptor
				 */
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					//如果Handler为空,则返回404
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				//3.获取处理请求的处理器适配器HandlerAdapter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				//处理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;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				//4.实际处理器处理请求,返回结果视图对象
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				//
				applyDefaultViewName(processedRequest, mv);
				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);
			}
			//跳转页面,渲染视图
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			//最终会调用HandlerInterceptor的afterCompletion方法
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			//最终会调用HandlerInterceptor的afterCompletion方法
			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);
				}
			}
		}
	}

核心步骤getHandler方法剖析

遍历两个HandlerMapping ,视图获取能够处理当前请求的执行链

Spring Mvc_数据_10

核心步骤getHandlerAdpter方法剖析

遍历各个HandlerAdapter看哪个Adapter支持处理当前handler

Spring Mvc_数据_11

核心步骤ha.handle

入口

Spring Mvc_拦截器_12

断点从入口进入

Spring Mvc_拦截器_13

Spring Mvc_数据_14

核心步骤processDispatcherResult方法剖析

render方法完成渲染

Spring Mvc_spring_15

视图解析器解析出View视图对象

Spring Mvc_拦截器_16

在解析出View视图对象的过程中会判断是否重定向、是否转发等,不同情况封装的是不同的View实现

Spring Mvc_数据_17

解析出View视图对象的过程中,要讲逻辑视图名解析为物理视图名

Spring Mvc_springMvc_18

封装View视图对象之后,调用view对象的render方法

Spring Mvc_数据_19

渲染数据

Spring Mvc_springMvc_20

把modelMap中的数据暴露到request域中,这也是为什么后台model.add之后jsp中可以从请求域中取出来的原因

Spring Mvc_数据_21

将数据设置到请求域中

Spring Mvc_拦截器_22

SpringMvc九大组件初始化
  • 在DispatcherServlet中定义了9个属性,每个属性对应一种组件
/** MultipartResolver used by this servlet. 
	 * 多部件解析器
	 * */
	@Nullable
	private MultipartResolver multipartResolver;

	/** LocaleResolver used by this servlet. */
	//区域化国际化解析器
	@Nullable
	private LocaleResolver localeResolver;

	/** ThemeResolver used by this servlet. */
	//主题解析器
	@Nullable
	private ThemeResolver themeResolver;

	/** List of HandlerMappings used by this servlet. */
	//处理器映射器组件
	@Nullable
	private List<HandlerMapping> handlerMappings;

	/** List of HandlerAdapters used by this servlet. */
	//处理器适配器组件
	@Nullable
	private List<HandlerAdapter> handlerAdapters;

	/** List of HandlerExceptionResolvers used by this servlet. */
	//异常解析器组件
	@Nullable
	private List<HandlerExceptionResolver> handlerExceptionResolvers;

	/** RequestToViewNameTranslator used by this servlet. */
	//默认视图名转换器组件
	@Nullable
	private RequestToViewNameTranslator viewNameTranslator;

	/** FlashMapManager used by this servlet. */
	//flash熟悉管理组件
	@Nullable
	private FlashMapManager flashMapManager;

	/** List of ViewResolvers used by this servlet. */
	//视图解析器
	@Nullable
	private List<ViewResolver> viewResolvers;

九大组件都是定义了接口,接口其实就是定义了该组件的规范,比如ViewResolver、HandlerAdapter等都是接口

九大组件的初始化时机

  • DispatcherServlet中的onRefresh(),该方法中初始化了九大组件

Spring Mvc_spring_23

  • initStrategies方法

Spring Mvc_spring_24

  • 观察其中的一个组件initHanderlerMapping(context)

Spring Mvc_拦截器_25

  • 如果按照类型和按照固定id从IOC容器中找不到对应组件,则会按照默认策略进行注册初始化,默认策略在DispatcherServlet.properties文件中配置

Spring Mvc_数据_26

  • DispatcherServlet.properties

Spring Mvc_spring_27

  • 注意:多部件解析器的初始化必须按照id注册对象(multipartResolver)

Spring Mvc_springMvc_28

SSM整合

SSM = Spring + SpringMVC + Mybatis = (Spring +Mybatis) +SpringMVC

Mybatis整合Spring

  • 整合目标

数据库连接池以及事务管理都交给Spring容器来完成

SqlSessionFactory对象应该放到Spring容器中作为单例对象管理

Mapper动态代理对象交给Spring管理,我们从spring容器中直接获得Mapper的动态对象

整合后的Pom坐标

<!--junit-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!--mybatis-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.5</version>
    </dependency>
    <!--spring相关-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.9</version>
    </dependency>
    <!--mybatis与spring的整合包-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.3</version>
    </dependency>
    <!--数据库驱动jar-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.46</version>
    </dependency>
    <!--druid连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.21</version>
    </dependency>


    <!--SpringMVC-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.1.12.RELEASE</version>
    </dependency>
    <!--jsp-api&servlet-api-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <!--页面使用jstl表达式-->
    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>

    <!--json数据交互所需jar,start-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.9.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.9.0</version>
    </dependency>
    <!--json数据交互所需jar,end-->

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/bank
jdbc.username=root
jdbc.password=123456

applicationContext-dao.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
">

    <!--包扫描-->
    <context:component-scan base-package="com.lagou.edu.mapper"/>


    <!--数据库连接池以及事务管理都交给Spring容器来完成-->

        <!--引入外部资源文件-->
        <context:property-placeholder location="classpath:jdbc.properties"/>

        <!--第三方jar中的bean定义在xml中-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>



    <!--SqlSessionFactory对象应该放到Spring容器中作为单例对象管理

        原来mybaits中sqlSessionFactory的构建是需要素材的:SqlMapConfig.xml中的内容
    -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--别名映射扫描-->
        <property name="typeAliasesPackage" value="com.lagou.edu.pojo"/>
        <!--数据源dataSource-->
        <property name="dataSource" ref="dataSource"/>
    </bean>



    <!--Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对象-->
    <!--扫描mapper接口,生成代理对象,生成的代理对象会存储在ioc容器中-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--mapper接口包路径配置-->
        <property name="basePackage" value="com.lagou.edu.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

</beans>

applicationContext-service.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
">

    <!--包扫描-->
    <context:component-scan base-package="com.lagou.edu.service"/>

    <!--事务管理-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--事务管理注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>
  • springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd
">

    <!--扫描controller-->
    <context:component-scan base-package="com.lagou.edu.controller"/>

    <!--配置springmvc注解驱动,自动注册合适的组件handlerMapping和handlerAdapter-->
    <mvc:annotation-driven/>

</beans>
  • AccountMapper.xml
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lagou.edu.mapper.AccountMapper">

    <select id="queryAccountList" resultType="com.lagou.edu.pojo.Account">
        select * from account
    </select>

</mapper>
  • web.xml
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:applicationContext*.xml</param-value>
  </context-param>
  <!--spring框架启动-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!--springmvc启动-->
  <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*:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>
  • 乱码问题解决
  • Post请求,web.xml中加入过滤器
<filter>
    <filter-name>encoding</filter-name>
    <filter-class>
    org.springframework.web.filter.CharacterEncodingFilter
    </filter-class>
    <!-- 设置编码参数是UTF8 -->
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encoding</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
  • Get请求乱码(Get请求乱码需要修改tomcat下server.xml)
<Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080"
protocol="HTTP/1.1" redirectPort="8443"/>

标签:return,String,Mvc,Spring,视图,handler,public,请求
From: https://blog.51cto.com/u_16426198/8777304

相关文章

  • SpringBoot+Vue实现大文件分块上传
    1.项目背景由于用户需求,需要上传大量图片,只能通过上传压缩包的形式上传,可是压缩包过大时,又会出现上传超时的情况,故需要将压缩包分块上传,然后解压缩图片、若图片过大则再对图片进行压缩。2.分块上传分块上传我在用的时候发现有两种:第一种:分块合并接口全由后端接口生成;第二种:前端......
  • .NET Core MVC基础之返回文件类型
    .NETCoreMVC基础之返回文件类型......
  • springboot下添加全局异常处理和自定义异常处理
    前言在spring项目中,优雅处理异常,好处是可以将系统产生的全部异常统一捕获处理,自定义的异常也由全局异常来捕获,如果涉及到validator参数校验器使用全局异常捕获也是较为方便。相关代码:GlobalExceptionHandler类:@Slf4j@RestControllerAdvicepublicclassGlobalExceptionHandl......
  • 简述一下Spring的两大特性(概念,使用范围。作用等方面简单的阐述
    简述一下Spring的两大特性(概念,使用范围。作用等方面简单的阐述1.控制反转(IoC,InversionofControl):概念:控制反转是Spring框架的核心概念,它指的是将对象的创建、组装和管理的控制权从应用程序代码中反转到Spring容器中。在传统的开发模式中,程序员负责直接创建和管理对象,而在......
  • SpringAOP的组成包含哪些
    SpringAOP的组成包含哪些SpringAOP(面向切面编程)的主要组成部分包括以下几个关键概念:切面(Aspect):切面是横切关注点的模块化单元。它封装了横切关注点的具体实现,包括通知和切点。通常,切面是一个类,其中包含了定义在何处以及如何执行横切关注点的代码。通知(Advice):通知是切面......
  • 列举不少于5个springMVC的常用的注解,并说明注解的作用
    列举不少于5个springMVC的常用的注解,并说明注解的作用;SpringMVC中有许多注解用于简化和增强控制器、请求映射、数据绑定等方面的操作。以下是不少于5个常用的SpringMVC注解及其作用:@Controller:作用:用于标识一个类是SpringMVC中的控制器,它处理客户端的请求。被注解为@......
  • spring中bean是什么
    spring中bean是什么在Spring框架中,"Bean"是一个概念,它表示由SpringIoC容器管理的对象。Bean是Spring框架的基本构建块,它可以是一个Java对象、一个组件、一个服务,或者是你的应用程序中的任何一个对象。具体来说,Spring的Bean具有以下特征:由容器管理:Bean由SpringIoC容器负责......
  • 简述springMVC工作流程
    简述springMVC工作流程SpringMVC是一个基于Java的开源MVC框架,用于构建Web应用程序。它通过将应用程序分解为模型(Model)、视图(View)和控制器(Controller)三个部分,以提高应用程序的可维护性和可扩展性。以下是SpringMVC的基本工作流程:客户端发送请求:客户端通过浏览器或其他客户端......
  • 简述SpringMVC数据输出的方式
    简述SpringMVC数据输出的方式SpringMVC提供多种方式来输出数据,主要包括以下几种:ModelAndView:ModelAndView是SpringMVC中用于封装模型数据和视图信息的对象。Controller方法可以返回一个ModelAndView对象,其中包含了要传递给视图的模型数据和视图的逻辑名称。@Controllerpubl......
  • 一文浅入Springboot+mybatis-plus+actuator+Prometheus+Grafana+Swagger2.9.2开发运维
    Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTFUL风格的Web服务,是非常流行的API表达工具。Swagger能够自动生成完善的RESTFULAP文档,,同时并根据后台代码的修改同步更新,同时提供完整的测试页面来调试API。Prometheus是一个开源的服务监控系统和时序数据库......