SpringMVC 高级框架

第一部分 Spring MVC 应用

1、简介

1.1 三层架构

我们的开发架构一般都是基于两种形式,一种是 C/S 架构,也就是客户端/服务器;另一种是 B/S 架构 ,也就是浏览器服务器。在 JavaEE 开发中,几乎全都是基于 B/S 架构的开发。那么在 B/S 架构中,系 统标准的三层架构包括:表现层、业务层、持久层。三层架构在我们的实际开发中使用的非常多,所以 我们课程中的案例也都是基于三层架构设计的。

  • 表现层:
    也就是我们常说的web 层。它负责接收客户端请求,向客户端响应结果,通常客户端使用http 协 议请求web 层,web 需要接收 http 请求,完成 http 响应。表现层包括展示层和控制层:控制层负责接收请求,展示层负责结果的展示。 表现层依赖业务层,接收到客户端请求一般会调用业务层进行业务处理,并将处理结果响应给客户端。 表现层的设计一般都使用 MVC 模型。(MVC 是表现层的设计模型,和其他层没有关系)

  • 业务层
    也就是我们常说的 service 层。它负责业务逻辑处理,和我们开发项目的需求息息相关。web 层依赖业
    务层,但是业务层不依赖 web 层。业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务一致性。(也就是我们说
    的, 事务应该放到业务层来控制)

  • 持久层
    也就是我们是常说的 dao 层。负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进 行持久化的载体,数据访问层是业务层和持久层交互的接口,业务层需要通过数据访问层将数据持久化 到数据库中。通俗的讲,持久层就是和数据库交互,对数据库表进行增删改查的。

1.2 MVC设计模式

MVC 全名是 Model View Controller,是 模型(model)-视图(view)-控制器(controller) 的缩写, 是一种用于设计创建 Web 应用程序表现层的模式。MVC 中每个部分各司其职: Model(模型):模型包含业务模型和数据模型,数据模型用于封装数据,业务模型用于处理业务。

  • View(视图): 通常指的就是我们的 jsp 或者 html。作用一般就是展示数据的。通常视图是依据 模型数据创建的。
  • Controller(控制器): 是应用程序中处理用户交互的部分。作用一般就是处理程序逻辑的。
  • MVC提倡:每一层只编写自己的东⻄,不编写任何其他的代码;分层是为了解耦,解耦是为了维护方便和分工协作。
1.3 SpringMVC

全名叫 Spring Web MVC

是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级Web 框架,属于 SpringFrameWork 的后续产品。为了解决表现层问题 的web框架,基于 MVC 设计模式的。而这些表现层框架的主要职责就是处理前端HTTP请求。本质可以认为是对servlet的封装,简化了我们serlvet的开发

作用:
1)接收请求
2)返回响应,跳转⻚面

在这里插入图片描述

2、Spring Web MVC 工作流程

2.1 Spring MVC 请求处理流程

在这里插入图片描述
流程说明

第一步: 用户发送请求至前端控制器DispatcherServlet

第二步: DispatcherServlet收到请求调用HandlerMapping处理器映射器

第三步: 处理器映射器根据请求Url找到具体的Handler(后端控制器),生成处理器对象及处理器拦截 器(如果 有则生成)一并返回DispatcherServlet

第四步: DispatcherServlet调用HandlerAdapter处理器适配器去调用Handler

第五步: 处理器适配器执行Handler

第六步: Handler执行完成给处理器适配器返回ModelAndView

第七步: 处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的一个 底层对 象,包括 Model 和 View

第八步: 前端控制器请求视图解析器去进行视图解析,根据逻辑视图名来解析真正的视图。

第九步: 视图解析器向前端控制器返回View

第十步: 前端控制器进行视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域

第十一步:前端控制器向用户响应结果

开发过程

1)配置DispatcherServlet前端控制器

2)开发处理具体业务逻辑的Handler(@Controller、@RequestMapping)

3)xml配置文件配置controller扫描,配置springmvc三大件

4)将xml文件路径告诉springmvc(DispatcherServlet)

2.2 Spring MVC 九大组件
  1. HandlerMapping(处理器映射器)
    HandlerMapping 是用来查找 Handler 的,也就是处理器,具体的表现形式可以是类,也可以是 方法。比如,标注了@RequestMapping的每个方法都可以看成是一个Handler。Handler负责具 体实际的请求处理,在请求到达后,HandlerMapping 的作用便是找到请求相应的处理器 Handler 和 Interceptor.

  2. HandlerAdapter(处理器适配器)
    HandlerAdapter 是一个适配器。因为 Spring MVC 中 Handler 可以是任意形式的,只要能处理请 求即可。但是把请求交给 Servlet 的时候,由于 Servlet 的方法结构都是 doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的 Servlet 处理 方法调用 Handler 来进行处理,便是 HandlerAdapter 的职责。

  3. HandlerExceptionResolver
    HandlerExceptionResolver 用于处理 Handler 产生的异常情况。它的作用是根据异常设置
    ModelAndView,之后交给渲染方法进行渲染,渲染方法会将 ModelAndView 渲染成⻚面。

  4. ViewResolver
    ViewResolver即视图解析器,用于将String类型的视图名和Locale解析为View类型的视图,只有一 个resolveViewName()方法。从方法的定义可以看出,Controller层返回的String类型视图名 viewName 最终会在这里被解析成为View。View是用来渲染⻚面的,也就是说,它会将程序返回 的参数和数据填入模板中,生成html文件。ViewResolver 在这个过程主要完成两件事情: ViewResolver 找到渲染所用的模板(第一件大事)和所用的技术(第二件大事,其实也就是找到 视图的类型,如JSP)并填入参数。默认情况下,Spring MVC会自动为我们配置一个 InternalResourceViewResolver,是针对 JSP 类型视图的。

  5. RequestToViewNameTranslator
    RequestToViewNameTranslator 组件的作用是从请求中获取 ViewName.因为 ViewResolver 根据 ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName, 便要通过这个组件从请求中查找 ViewName。

  6. LocaleResolver
    ViewResolver 组件的 resolveViewName 方法需要两个参数,一个是视图名,一个是 Locale。 LocaleResolver 用于从请求中解析出 Locale,比如中国 Locale 是 zh-CN,用来表示一个区域。这 个组件也是 i18n 的基础。

  7. ThemeResolver
    ThemeResolver 组件是用来解析主题的。主题是样式、图片及它们所形成的显示效果的集合。 Spring MVC 中一套主题对应一个 properties文件,里面存放着与当前主题相关的所有资源,如图 片、CSS样式等。创建主题非常简单,只需准备好资源,然后新建一个“主题名.properties”并将资 源设置进去,放在classpath下,之后便可以在⻚面中使用了。SpringMVC中与主题相关的类有 ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名, ThemeSource根据主题名找到具体的主题,其抽象也就是Theme,可以通过Theme来获取主题和 具体的资源。

  8. MultipartResolver
    MultipartResolver 用于上传请求,通过将普通的请求包装成 MultipartHttpServletRequest 来实 现。MultipartHttpServletRequest 可以通过 getFile() 方法 直接获得文件。如果上传多个文件,还 可以调用 getFileMap()方法得到Map<FileName,File>这样的结构,MultipartResolver 的作用就 是封装普通的请求,使其拥有文件上传的功能。

  9. FlashMapManager
    FlashMap 用于重定向时的参数传递,比如在处理用户订单时候,为了避免重复提交,可以处理完 post请求之后重定向到一个get请求,这个get请求可以用来显示订单详情之类的信息。这样做虽然 可以规避用户重新提交订单的问题,但是在这个⻚面上要显示订单的信息,这些数据从哪里来获得 呢?因为重定向时么有传递参数这一功能的,如果不想把参数写进URL(不推荐),那么就可以通 过FlashMap来传递。只需要在重定向之前将要传递的数据写入请求(可以通过ServletRequestAttributes.getRequest()方法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE 中,这样在重定向之后的Handler中Spring就会自动将其设置到Model中,在显示订单信息的⻚面 上就可以直接从Model中获取数据。FlashMapManager 就是用来管理 FalshMap 的。

springmvc.xml

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

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


    <!--配置springmvc的视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>


    <!--
        自动注册最合适的处理器映射器,处理器适配器(调用handler方法)
    -->
    <mvc:annotation-driven conversion-service="conversionServiceBean"/>


    <!--注册自定义类型转换器-->
    <bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.lagou.edu.converter.DateConverter"></bean>
            </set>
        </property>
    </bean>


    <!--静态资源配置,方案一 --> 
    <!--
        原理:添加该标签配置之后,会在SpringMVC上下文中定义一个DefaultServletHttpRequestHandler对象
             这个对象如同一个检查人员,对进入DispatcherServlet的url请求进行过滤筛查,如果发现是一个静态资源请求
             那么会把请求转由web应用服务器(tomcat)默认的DefaultServlet来处理,如果不是静态资源请求,那么继续由
             SpringMVC框架处理
    -->
    <!--<mvc:default-servlet-handler/>-->

    <!--静态资源配置,方案二 --> 
	<!--
		SpringMVC框架自己处理静态资源
        mapping: 约定的静态资源的url规则
        location: 指定的静态资源的存放位置
     -->
    <mvc:resources location="classpath:/"  mapping="/resources/**"/>
    <mvc:resources location="/WEB-INF/js/" mapping="/js/**"/>


    <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>


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

</beans>

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>

  <!--springmvc提供的针对post请求的编码过滤器-->
  <filter>
    <filter-name>encoding</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encoding</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>


  <!--配置springmvc请求方式转换过滤器,会检查请求参数中是否有_method参数,如果有就按照指定的请求方式进行转换-->
  <filter>
    <filter-name>hiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>hiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>


  <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>
  </servlet>
  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <!--
      方式一:带后缀,比如*.action  *.do *.aaa
             该种方式比较精确、方便,在以前和现在企业中都有很大的使用比例
      方式二:/ 不会拦截 .jsp,但是会拦截.html等静态资源(静态资源:除了servlet和jsp之外的js、css、png等)

            为什么配置为/ 会拦截静态资源???
                因为tomcat容器中有一个web.xml(父),你的项目中也有一个web.xml(子),是一个继承关系
                      父web.xml中有一个DefaultServlet,  url-pattern 是一个 /
                      此时我们自己的web.xml中也配置了一个 / ,覆写了父web.xml的配置
            为什么不拦截.jsp呢?
                因为父web.xml中有一个JspServlet,这个servlet拦截.jsp文件,而我们并没有覆写这个配置,
                所以springmvc此时不拦截jsp,jsp的处理交给了tomcat

            如何解决/拦截静态资源这件事?
            springmvd.xml配置文件中 两种方式处理
            
      方式三:/* 拦截所有,包括.jsp
    -->
    <!--拦截匹配规则的url请求,进入springmvc框架处理-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

MyIntercepter01.java DateConverter.java

/**
 * 自定义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......");
    }
}


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;
    }
}

3、请求参数绑定

3.1 默认支持 Servlet API 作为方法参数
	/**
	*
	* SpringMVC 对原生servlet api的支持 url:/demo/handle?id=1
	* 如果要在SpringMVC中使用servlet原生对象,
	* 比如HttpServletRequest\HttpServletResponse\HttpSession,
	* 直接在Handler方法形参中声 明使用即可
	*/
	@RequestMapping("/handle")
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response,HttpSession session) {
		String id = request.getParameter("id");
		Date date = new Date();
		ModelAndView modelAndView = new ModelAndView(); 
		modelAndView.addObject("date",date); 
		modelAndView.setViewName("success");
		return modelAndView;
	}
3.2 简单类型参数

简单数据类型: 八种基本数据类型及其包装类型
参数类型推荐使用包装数据类型,因为基础数据类型不可以为null
整型:Integer、int
字符串:String
单精度:Float、float
双精度:Double、double
布尔型:Boolean、boolean 说明:对于布尔类型的参数,请求的参数值为true或false。或者1或0
注意:绑定简单数据类型参数,只需要直接声明形参即可(形参参数名和传递的参数名要保持一 致,建议使用包装类型,当形参参数名和传递参数名不一致时可以使用@RequestParam注解进行手动映射)

/*
* SpringMVC 接收简单数据类型参数 url:/demo/handle?ids=1
* 注意: 接收简单数据类型参数,直接在handler方法的形参中声明即可,框架会取出参数值然后绑定到对应参数上
* 要求: 传递的参数名和声明的形参名称保持一致 
*/
@RequestMapping("/handle")
public ModelAndView handle(@RequestParam("ids") Integer id, Boolean flag) {
	Date date = new Date();
	ModelAndView modelAndView = new ModelAndView(); 
	modelAndView.addObject("date",date); 
	modelAndView.setViewName("success");
	return modelAndView;
}
3.3 Pojo类型参数
public class User {

    private Integer id;
    private String name;
    
    //setter getter
}
 
/*
 * SpringMVC接收pojo类型参数 url:/demo/handle?id=1&username=zhangsan 
 * 接收pojo类型参数,直接形参声明即可,类型就是Pojo的类型,形参名无所谓
 * 但是要求传递的参数名必须和Pojo的属性名保持一致
*/
@RequestMapping("/handle")
public ModelAndView handle(User user) {
	Date date = new Date();
	ModelAndView modelAndView = new ModelAndView(); 
	modelAndView.addObject("date",date); 
	modelAndView.setViewName("success");
	return modelAndView;
}
3.4 Pojo包装对象参数

包装类型 QueryVo

public class QueryVo {

    private String mail;
    private String phone;

    // 嵌套了另外的Pojo对象
    private User user;
    
     //setter getter
}

	/*
     * SpringMVC接收pojo包装类型参数  url:/demo/handle?user.id=1&user.username=zhangsan
     * 不管包装Pojo与否,它首先是一个pojo,那么就可以按照上述pojo的要求来
     *  1、绑定时候直接形参声明即可
     *  2、传参参数名和pojo属性保持一致,如果不能够定位数据项,那么通过属性名 + "." 的方式进一步锁定数据
     */
    @RequestMapping("/handle")
    public ModelAndView handle(QueryVo queryVo) {
        Date date = new Date();
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("date",date);
        modelAndView.setViewName("success");
        return modelAndView;
    }

3.5 绑定日期类型参数

需要配置自定义类型转换器

	/**
	 *  自定义类型转换器
	 *  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;
	    }
	}
	
	/**
     * 绑定日期类型参数   url:/demo/handle?birthday=2019-10-08
     * 定义一个SpringMVC的类型转换器接口,扩展实现接口接口,注册我们的实现
     * @param birthday
     * @return
     */
    @RequestMapping("/handle")
    public ModelAndView handle(Date birthday) {
        Date date = new Date();
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("date",date);
        modelAndView.setViewName("success");
        return modelAndView;
    }
	<!--
        自动注册最合适的处理器映射器,处理器适配器(调用handler方法)
    -->
    <mvc:annotation-driven conversion-service="conversionServiceBean"/>


    <!--注册自定义类型转换器-->
    <bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.lagou.edu.converter.DateConverter"></bean>
            </set>
        </property>
    </bean>

4、Restful ⻛格请求支持

Restful 是一种 web 软件架构⻛格,它不是标准也不是协议,它倡导的是一个资源定位及资源操作的⻛格。

REST(英文:Representational State Transfer,简称 REST)

表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层 (Representation)。比 如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进 制格式。

状态转化(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。

HTTP 协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器, 必须通过某种手段,让服务器端发生“状态转化”(State Transfer)。而这种转化是建立在表现层 之上的,所以就是 “ 表现层状态转化” 。具体说, 就是 HTTP 协议里面,四个表示操作方式的动词: GET 、POST 、PUT 、DELETE 。
1、GET 用来获取资源, 查询
2、POST 用来新建资源, 修改
3、PUT 用来更新资源, 添加
4、DELETE 用来删除资源。 删除
只是一种协议规定,不是说一个动词只能实现这一种功能,例如 POST 也可以用作查询
一般使用时还是 post 和 get。put 和 delete几乎不使用

	/*
     * restful  /demo/handle/123
     */
    @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 /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/123/test
     */
    @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/123
     */
    @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

<!--配置springmvc请求方式转换过滤器,会检查请求参数中是否有_method参数,如果有就按照指定的请求方式进行转换-->
  <filter>
    <filter-name>hiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>hiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

5、Ajax Json交互

交互: 两个方向
1)前端到后台:前端ajax发送json格式字符串,后台直接接收为pojo参数,使用注解@RequstBody
2)后台到前端:后台直接返回pojo对象,前端直接接收为json对象或者字符串,使用注解 @ResponseBody

@responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之 后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据。 注意:在使用此注解之 后不会再走视图处理器,而是直接将数据写入到输入流中,他的效果等同于通过response对象输出指定 格式的数据。

pom.xml

	<!--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-->
  
    @RequestMapping("/handle")
    // 添加@ResponseBody之后,不再走视图解析器那个流程,而是等同于response直接输出数据
    public @ResponseBody User handle(@RequestBody User user) {
        // 业务逻辑处理,修改name为张三丰
        user.setName("张三丰");
        return user;
    }

第二部分 Spring MVC 高级技术

1、拦截器(Interceptor)

1.1、监听器、过滤器和拦截器对比
  • Servlet: 处理Request请求和Response响应

  • 过滤器(Filter): 对Request请求起到过滤的作用,作用在Servlet之前,如果配置为/*可以对所 有的资源访问(servlet、js/css静态资源等)进行过滤处理

  • 监听器(Listener): 实现了javax.servlet.ServletContextListener 接口的服务器端组件,它随 Web应用的启动而启动,只初始化一次,然后会一直运行监视,随Web应用的停止而销毁

    作用一: 做一些初始化工作,web应用中spring容器启动ContextLoaderListener;

    作用二: 监听web中的特定事件,比如HttpSession,ServletRequest的创建和销毁;变量的创建、 销毁和修改等。可以在某些动作前后增加处理,实现监控,比如统计在线人数,利用 HttpSessionLisener等。

  • 拦截器(Interceptor): 是SpringMVC、Struts等表现层框架自己的,不会拦截 jsp/html/css/image的访问等,只会拦截访问的控制器方法(Handler)。

从配置的⻆度: servlet、filter、listener是配置在web.xml中的,而interceptor是配置在表现层框架自己的配置文件中的,在Handler业务逻辑执行之前拦截一次,在Handler逻辑执行完毕但未跳转⻚面之前拦截一次,在跳转⻚面之后拦截一次。
在这里插入图片描述

1.2、单拦截器的执行流程

在运行程序时,拦截器的执行是有一定顺序的,该顺序与配置文件中所定义的拦截器的顺序相关。 单个
拦截器,在程序中的执行流程如下图所示:
在这里插入图片描述
1)程序先执行preHandle(),如果该方法的返回值为true,则程序会继续向下执行处理器中的方法,否则将不再向下执行。

2)在业务处理器(Controller)处理完请求后,会执行postHandle(),然后会通过DispatcherServlet向客户端返回响应。

3)在DispatcherServlet处理完请求后,才会执行afterCompletion()方法。

1.3、多个拦截器的执行流程

多个拦截器(假设有两个拦截器Interceptor1和Interceptor2,并且在配置文件中, Interceptor1拦截器配置在前),在程序中的执行流程如下图所示:
在这里插入图片描述
从图可以看出,当有多个拦截器同时工作时,它们的preHandle()方法会按照配置文件中拦截器的配置 顺序执行,而它们的postHandle()方法和afterCompletion()方法则会按照配置顺序的反序执行。

自定义拦截器

/**
 * 自定义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......");
    }
}

// MyIntercepter02 与 MyIntercepter01一样

注册SpringMVC拦截器

	<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>

运行结果
在这里插入图片描述

2、处理multipart形式的数据

pom.xml

<!--文件上传所需jar--> 
<dependency>
	<groupId>commons-fileupload</groupId> 
	<artifactId>commons-fileupload</artifactId> 
	<version>1.3.1</version>
</dependency>

springmvc.xml

<!--配置文件上传解析器,id是固定的multipartResolver-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--设置上传大小,单位字节-->
<property name="maxUploadSize" value="1000000000"/> </bean>

前端From表单

<form method="post" enctype="multipart/form-data" action="/demo/upload">
	<input type="file" name="uploadFile"/>
	<input type="submit" value="上传"/> 
</form>

业务逻辑java

	/**
     * 文件上传
     * @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;
    }

3、控制器中处理异常

import org.springframework.web.bind.annotation.ControllerAdvice; 
import org.springframework.web.bind.annotation.ExceptionHandler; 
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletResponse; 
import java.io.IOException;

// 可以让我们优雅的捕获所有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;
	} 
}

4、基于Flash属性的跨重定向请求数据传递

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

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 框架

在这里插入图片描述

LgDispatcherServlet.java

package *;

import com.lagou.demo.service.IDemoService;
import com.lagou.edu.mvcframework.annotations.LagouAutowired;
import com.lagou.edu.mvcframework.annotations.LagouController;
import com.lagou.edu.mvcframework.annotations.LagouRequestMapping;
import com.lagou.edu.mvcframework.annotations.LagouService;
import com.lagou.edu.mvcframework.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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
            String value = StringUtils.join(param.getValue(), ","); 
            // 如果参数和方法中的参数匹配上了,填充数据
            if(!handler.getParamIndexMapping().containsKey(param.getKey())) {
            	continue;
            }
            // 方法形参确实有该参数,找到它的索引位置,对应的把参数值放入paraValues
            Integer index = handler.getParamIndexMapping().get(param.getKey());
            paraValues[index] = value;  // 把前台传递过来的参数值填充到对应的位置去
        }

        int requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        paraValues[requestIndex] = req;
        int responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        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;
    }
}

Handler.java

package *;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * 封装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;
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.lagou.edu</groupId>
  <artifactId>mvc</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>mvc Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>


    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.9</version>
    </dependency>
  </dependencies>



  <build>
    <plugins>
      <!--编译插件定义编译细节-->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>11</source>
          <target>11</target>
          <encoding>utf-8</encoding>
          <!--告诉编译器,编译的时候记录下形参的真实名称-->
          <compilerArgs>
            <arg>-parameters</arg>
          </compilerArgs>
        </configuration>
      </plugin>


      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <port>8080</port>
          <path>/</path>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

springmvc.properties

scanPackage=com.lagou.demo

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>classpath*:springmvc.properties</param-value>
    </init-param>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>lgoumvc</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

注解代码及Controller,Service省略

第四部分 Spring MVC 源码深度剖析

1、前端控制器 DispatcherServlet 继承结构

在这里插入图片描述

2、重要时机点分析

2、1 Handler方法的执行时机

在这里插入图片描述

2、2 ⻚面渲染时机(打断点并观察调用栈)

在这里插入图片描述
SpringMVC处理请求的流程 (org.springframework.web.servlet.DispatcherServlet#doDispatch方法的执行过程)

  1. 调用getHandler()获取到能够处理当前请求的执行链 HandlerExecutionChain(Handler+拦截器)
  2. 调用HandlerExecutionChain.getHandlerAdapter(); 获取能够执行1中Handler的适配器 HandlerAdapter
  3. HandlerAdapter适配器调用执行ha.handle() , 总会返回一个ModelAndView对象
  4. 调用processDispatchResult()方法完成视图渲染跳转

3、核心步骤getHandler方法剖析

在这里插入图片描述
在这里插入图片描述

4、核心步骤getHandlerAdapter方法剖析

在这里插入图片描述

5、核心步骤ha.handle方法剖析

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6、核心步骤processDispatchResult方法剖析在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
解析出View视图对象的过程中,要将逻辑视图名解析为物理视图名
在这里插入图片描述
在这里插入图片描述
把modelMap中的数据暴露到request域中,这也是为什么后台model.add之后在jsp中可以从请求 域取出来的根本原因
在这里插入图片描述
将数据设置到请求域中
在这里插入图片描述

7、SpringMVC九大组件初始化

7、1 DispatcherServlet中定义了九个属性

每一个属性都对应一种组件, 九大组件都是定义了接口,接口其实就是定义了该组件的规范
比如ViewResolver、HandlerAdapter 等都是接口

/** 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;
7、2 九大组件的初始化时机

Spring容器在启动时org.springframework.context.support.AbstractApplicationContext#onfresh()中调用了onRefresh(), 该方法由子类DispatcherServlet实现(初始化九大件)
在这里插入图片描述
在这里插入图片描述
如果按照类型和按照固定id从ioc容器中找不到对应组件,则会按照默认策略进行注册初始化,默 认策略在DispatcherServlet.properties文件中配置
在这里插入图片描述
在这里插入图片描述

第五部分 SSM 整合

1、Mybatis整合Spring

1、1 整合目标

数据库连接池以及事务管理都交给Spring容器来完成 SqlSessionFactory对象应该放到Spring容器中作为单例对象管理 Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对象

1、2 整合所需 Jar 分析
  • Junit测试jar(4.12版本)
  • Mybatis的jar(3.4.5) Spring相关jar(spring-context、spring-test、spring-jdbc、spring-tx、spring-aop、 aspectjweaver)
  • Mybatis/Spring整合包jar(mybatis-spring-xx.jar)
  • Mysql数据库驱动jar
  • Druid数据库连接池的jar

pom.xml

 <dependencies>
    <!--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>

jdbc.properties

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

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>

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>

AccountMapper接口

 
package com.lagou.edu.mapper; 
import com.lagou.edu.pojo.Account; 
import java.util.List;
public interface AccountMapper {
// 定义dao层接口方法--> 查询account表所有数据 
	List<Account> queryAccountList() throws Exception;
}

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>

测试程序

import com.lagou.edu.pojo.Account;
import com.lagou.edu.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:application*.xml"})
public class MybatisSpringTest {

    // 希望测试ioc容器中的哪个对象你注入即可。
    @Autowired
    private AccountService accountService;

    @Test
    public void testMybatisSpring() throws Exception {
        List<Account> accounts = accountService.queryAccountList();
        for (int i = 0; i < accounts.size(); i++) {
            Account account =  accounts.get(i);
            System.out.println(account);
        }
    }
}

2、 整合SpringMVC

pom.xml

<!--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-->

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>

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>

  <!-- 解决post乱码问题 -->
  <filter>
    <filter-name>encoding</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <!-- 设置编码参是UTF8 -->
      <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"/>
    -->
    

  <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>

Controller类

package com.lagou.edu.controller;

import com.lagou.edu.pojo.Account;
import com.lagou.edu.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

@Controller
@RequestMapping("/account")
public class AccountController {

    /**
     * Spring容器和SpringMVC容器是有层次的(父子容器)
     * Spring容器:service对象+dao对象
     * SpringMVC容器:controller对象,,,,可以引用到Spring容器中的对象
     */


    @Autowired
    private AccountService accountService;

    @RequestMapping("/queryAll")
    @ResponseBody
    public List<Account>  queryAll() throws Exception {
        return accountService.queryAccountList();
    }

}

附录 Spring MVC 必备设计模式

1、策略模式 Strategy

就是一个问题有多种解决方案,选择其中的一种使用,这种情况下我们 使用策略模式来实现灵活地选择,也能够方便地增加新的解决方案。比如做数学题,一个问题的 解法可能有多种;再比如商场的打折促销活动,打折方案也有很多种,有些商品是不参与折扣活 动要按照原价销售,有些商品打8.5折,有些打6折,有些是返现5元等。

  • 结构

      策略(Strategy)
      定义所有支持算法的公共接口。 Context 使用这个接口来调用某 ConcreteStrategy 定义的算法。 
    
      策略实现(ConcreteStrategy)
      实现了Strategy 接口的具体算法
      
      上下文(Context)
      维护一个 Strategy 对象的引用
      用一个 ConcreteStrategy 对象来装配
      可定义一个接口方法让 Strategy 访问它的数据
    
  • 示例
    假如现在有一个商场优惠活动,有的商品原价售卖,有的商品打8.5折,有的商品打6折,有的返现 5元

 
package designpattern.strategy.old;
import java.text.MessageFormat;

public class BuyGoods {
    private String goods;
    private double price;
	private double finalPrice;
    private String desc;
    
	public BuyGoods(String goods, double price) { 
		this.goods = goods;
		this.price = price;
	}
	
	public double calculate(String discountType) { 
		if ("discount85".equals(discountType)) {
			finalPrice = price * 0.85;
			desc = "该商品可享受8.5折优惠";
		} else if ("discount6".equals(discountType)) {
			finalPrice = price * 0.6;
			desc = "该商品可享受6折优惠";
		} else if ("return5".equals(discountType)) {
			finalPrice = price >= 5 ? price - 5 : 0;
			desc = "该商品可返现5元"; } else {
			finalPrice = price;
			desc = "对不起,该商品不参与优惠活动"; 
		}
		System.out.println(MessageFormat.format("您购买的商品为:{0},原价为: {1},{2},最终售卖价格为:{3}", 
		goods, price, desc, finalPrice));
		
        return finalPrice;
    }
}
public class Test {
    public static void main(String[] args) {
		BuyGoods buyGoods1 = new BuyGoods("Java编程思想", 99.00); 
		buyGoods1.calculate("discount85");
		
		BuyGoods buyGoods2 = new BuyGoods("罗技鼠标", 66 ); 
		buyGoods2.calculate("discount6");
		
		BuyGoods buyGoods3 = new BuyGoods("苹果笔记本", 15000.00); 
		buyGoods3.calculate("return5");
		
		BuyGoods buyGoods4 = new BuyGoods("佳能相机", 1900);
		buyGoods4.calculate(null); 
	}
}

上述代码可以解决问题,但是从代码设计的⻆度还是存在一些问题

  • 增加或者修改打折方案时必须修改 BuyGoods 类源代码,违反了面向对象设计的 “开闭原 则”,代码的灵活性和扩展性较差。
  • 打折方案代码聚合在一起,如果其他项目需要重用某个打折方案的代码,只能复制粘贴对应代码,无法以类组件的方式进行重用,代码的复用性差。
  • BuyGoods 类的 calculate() 方法随着优惠方案的增多会非常庞大,代码中会出现很多if分 支,可维护性差。

此时,我们可以使用策略模式对 BuyGoods 类进行重构,将打折方案逻辑(算法)的定义和使用分离。
抽象策略类 AbstractDiscount,它是所有具体打折方案(算法)的父类,定义了一个discount 抽象方法

public abstract class AbstractDiscount {
    protected double finalPrice;
    protected String desc;
    
	public AbstractDiscount(String desc) { 
		this.desc = desc;
	}
    public abstract double discount(double price);
}

四种具体策略类,继承自抽象策略类 AbstractDiscount,并在 discount 方法中实现具体的打折方案(算法)

 
public class Discount85 extends AbstractDiscount {
    public Discount85() {
		super("该商品可享受8.5折优惠"); 
	}
	
	@Override
	public double discount(double price) { 
		finalPrice = price * 0.85;
		return finalPrice;
	} 
}
	
public class Discount6 extends AbstractDiscount {
    public Discount6() {
		super("该商品可享受6折优惠");	 
	}
	
	@Override
	public double discount(double price) { 
		finalPrice = price * 0.6;
		return finalPrice;
	} 
}

public class Return5 extends AbstractDiscount {
    public Return5() {
		super("该商品可返现5元"); 
	}
	
	@Override
	public double discount(double price) { 
		this.finalPrice = price >= 5 ? price - 5 : 0; 
		return finalPrice;
	} 
}

public class NoDiscount extends AbstractDiscount {
    public NoDiscount() {
		super("对不起,该商品不参与优惠活动"); 
	}
	
	@Override
	public double discount(double price) { 
		finalPrice = price;
		return finalPrice;
	} 
}
 

类 BuyGoods,维护了一个 AbstractDiscount 引用

public class BuyGoods {
    private String goods;
    private double price;
    private AbstractDiscount abstractDiscount;
    
    public BuyGoods(String goods, double price, AbstractDiscount abstractDiscount) {
		this.goods = goods;
		this.price = price;
		this.abstractDiscount = abstractDiscount;
	}
	
	public double calculate() {
		double finalPrice = abstractDiscount.discount(this.price);
		String desc = abstractDiscount.getDesc(); 
		System.out.println(MessageFormat.format("商品:{0},原价:{1},{2},最终价格为:{3}", goods, price, desc, finalPrice)); 
		return finalPrice;
	} 
}
public class Test {
    public static void main(String[] args) {
		BuyGoods buyGoods1 = new BuyGoods("Java编程思想", 99.00, new Discount85());
		buyGoods1.calculate();
		
		BuyGoods buyGoods2 = new BuyGoods("罗技鼠标", 66, new Discount6());
		buyGoods2.calculate();
		
		BuyGoods buyGoods3 = new BuyGoods("苹果笔记本", 15000.00, new Return5());
		buyGoods3.calculate();
		
		BuyGoods buyGoods4 = new BuyGoods("佳能相机", 1900, new NoDiscount());
		buyGoods4.calculate(); 
	}
}
 

重构后:
增加新的优惠方案时只需要继承抽象策略类即可,修改优惠方案时不需要修改BuyGoods类 源码;
代码复用也变得简单,直接复用某一个具体策略类即可; BuyGoods类的calculate变得简洁,没有了原本的if分支;

2、模板方法模式

  • 模板方法模式是指定义一个算法的⻣架,并允许子类为一个或者多个步骤提供实现。模板方法模式使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤,属于行为型设计模式。

  • 采用模板方法模式的核心思路是处理某个流程的代码已经具备,但其中某些节点的代码暂时不能确 定。此时可以使用模板方法。

  • 示例


/**
 * 面试大厂流程类 
 */
public abstract class Interview {
	private final void register() { 
		System.out.println("面试登记");
    }
    
    protected abstract void communicate();
    
	private final void notifyResult() { 
		System.out.println("HR小姐姐通知面试结果");
    }
    
    protected final void  process() {
		this.register(); 
		this.communicate();
		this.notifyResult(); 
	}
}

/**
* 面试人员1,它是来面试Java工程师的 
*/
public class Interviewee1  extends Interview{
    public void communicate() {
		System.out.println("我是面试人员1,来面试Java工程师,我们聊的是Java相关内容");
	} 
}

/**
* 面试人员2,它是来面试前端工程师的
*/
public class Interviewee2 extends Interview{
    public void communicate() {
		System.out.println("我是面试人员2,来面试前端工程师,我们聊的是前端相关内 容");
	} 
}

 
public class InterviewTest {
    public static void main(String[] args) {
		// 面试Java工程师
		Interview interviewee1 = new Interviewee1(); 
		interviewee1.process();
		
		// 面试前端工程师
		Interview interviewee2 = new Interviewee2(); 
		interviewee2.process();
	} 
}

3、适配器模式

使得原本由于接口不兼容而不能一起工作、不能统一管理的那些类可以一起工作、可以进行统一管理

3、1 解决接口不兼容而不能一起工作问题

看下面一个非常经典的案例:
在中国,⺠用电都是220v交流电,但是手机锂电池用的都是5v直流电。因此,我们给手机充电时就需要使用电源适配器来进行转换。使用代码还原这个生活场景 创建AC220类,表示220v交流电, 创建DC5接口,表示5V直流电

public class AC220 {
    public int outputAC220V() {
		int output = 220;
		System.out.println("输出交流电" + output + "V"); 
		return output;
	} 
}

public interface DC5 {
    int outputDC5V();
}

//创建电源适配器类 PowerAdapter
public class PowerAdapter implements DC5 {
    private AC220 ac220;
	public PowerAdapter(AC220 ac220) { 
		this.ac220 = ac220;
	}
	
	@Override
	public int outputDC5V() {
		int adapterInput = ac220.outputAC220V();
		// 变压器...
		int adapterOutput = adapterInput/44;
		System.out.println("使用 PowerAdapter 输入AC:" + adapterInput + "V输出DC:" + adapterOutput + "V"); 
		return adapterOutput;
	} 
}

public class AdapterTest {
	public static void main(String[] args) {
		DC5 dc5 = new PowerAdapter(new AC220());
		dc5.outputDC5V();
	} 
}

在上面的案例中,通过增加电源适配器类PowerAdapter实现了二者的兼容

3、2 解决不能统一管理的问题

SpringMVC中处理器适配器(HandlerAdapter)机制就是解决类统一管理问题非常经典的场景
其中HandlerAdapter接口是处理器适配器的顶级接口,它有多个子类,包括 AbstractHandlerMethodAdapter、SimpleServletHandlerAdapter、 SimpleControllerHandlerAdapter、HttpRequestHandlerAdapter、 RequestMappingHandlerAdapter
其适配器调用的关键代码也在DispatcherServlet的 doDispatch() 方法中
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值