spring MVC简介

Spring MVC ⾼级框架

第一部分 Spring MVC 的应用

springmvc简介

三层架构:

在java开发中基本上都是B/S架构,即浏览器/服务器。系统标准的三层架构包括:表现层、业务层、持久层。

  • 表现层 :

    也就是我们常说的web 层。它负责接收客户端请求,向客户端响应结果,通常客户端使⽤http 协议请求web 层,web 需要接收 http 请求,完成 http 响应。

    表现层包括展示层和控制层:控制层负责接收请求对应controller,展示层负责结果的展示对应页面。表现层依赖业务层,接收到客户端请求⼀般会调⽤业务层进⾏业务处理,并将处理结果响应给客户端。表现层的设计⼀般都使⽤ MVC 模型。(MVC 是表现层的设计模型,和其他层没有关系)

  • 业务层 :

    也就是我们常说的 service 层。它负责业务逻辑处理,和我们开发项⽬的需求息息相关。web 层依赖业务层,但是业务层不依赖 web 层。

    业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务⼀致性所以事务应该放到业务层

  • 持久层 :

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

    经典三层&mvc模式

MVC设计模式

MVC 全名是 Model View Controller,是 模型(model)-视图(view)-控制器(controller) 的缩写, 是⼀种⽤于设计创建 Web 应⽤程序表现层的模式。MVC 中每个部分各司其职:

  • Model(模型):模型包含业务模型和数据模型,数据模型⽤于封装数据,业务模型⽤于处理业务。

  • View(视图): 通常指的就是我们的 jsp 或者 html。作⽤⼀般就是展示数据的。通常视图是依据模型数据创建的。

  • Controller(控制器): 是应⽤程序中处理⽤户交互的部分。作⽤⼀般就是处理用户请求的。

MVC提倡:每⼀层只编写⾃⼰的东⻄,不编写任何其他的代码;分层是为了解耦,解耦是为了维护⽅便和分⼯协作。

Spring MVC 是什么?

SpringMVC 全名叫 Spring Web MVC,是⼀种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级Web 框架,属于 SpringFrameWork 的后续产品。

它通过⼀套注解,让⼀个简单的 Java 类成为处理请求的控制器,⽽⽆须实现任何接⼝。同时它还⽀持RESTful 编程⻛格的请求。

Spring MVC和Struts2⼀样,都是 为了解决表现层问题 的web框架,它们都是基于 MVC 设计模式的。⽽这些表现层框架的主要职责就是处理前端HTTP请求。

Spring MVC 本质可以认为是对servlet的封装,简化了我们serlvet的开发其作用是接收请求和返回响应,跳转⻚⾯

截屏2021-07-23 下午7.13.50

springmvc模式和原生servlet模式区别

使用springmvc

1.引入spring webmvc的依赖

<!--引入spring webmvc的依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.1.12.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>

2.在web.xml中配置servlet

<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

url-pattern是指要拦截的请求有三种方式

  • 方式一:带后缀,比如*.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
  • 方式三:/* 拦截所有,包括.jsp

对于拦截静态资源可以在spring的配置文件中配置<mvc:resources location="/WEB-INF/js/" mapping="/js/**"/> 来解决

3.创建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 base-package 是要扫描的路径-->
<context:component-scan base-package="com.edu.spring"></context:component-scan>
<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>
<!--自动注册最合适的处理器映射器,处理器适配器-->
<mvc:annotation-driven conversion-service="conversionServiceBean"></mvc:annotation-driven>
  </beans>

4.代码示例

@Controller
@RequestMapping("/demo")
public class DemoController {
  @RequestMapping("handle01")
    public String handle01(Model model) {
        LocalDateTime localDateTime = LocalDateTime.now();
        //像请求域中添加数据 相当于 request.setAttribute("date",date)
        model.addAttribute("date", localDateTime);
        return "handle01";
    }
}

访问http://localhost:8080/demo/handle01就可以发现跳转到/WEB-INF/jsp/handle01.jsp对应的页面了

在3中Model可以是Map(jdk中的接口)、Model(spring的接口)、ModelMap(实现Map接口) ,他们在运行时的具体类型都是BindingAwareModelMap而且BindingAwareModelMap继承了ExtendedModelMap,ExtendedModelMap继承了ModelMap,实现了Model接口,这也是为什么参数可以是Map、Model和ModelMap类型的原因

Spring MVC 请求处理流程

处理器映射器:就是把url映射到对应的handler(处理请求的地方)
处理器适配器:handler实现的方式不一样,不同的实现方式调用方式也不一样,处理器适配器就是根据不同的实现去调用handler

截屏2021-07-23 下午7.30.33

流程说明

  • 第⼀步:⽤户发送请求⾄前端控制器DispatcherServlet

  • 第⼆步:DispatcherServlet收到请求调⽤HandlerMapping处理器映射器

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

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

  • 第五步:处理器适配器执⾏Handler

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

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

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

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

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

  • 第⼗⼀步:前端控制器向⽤户响应结果

Spring MVC 九⼤组件

  • HandlerMapping(处理器映射器)

    HandlerMapping 是⽤来查找 Handler 的,也就是处理器,具体的表现形式可以是类,也可以是⽅法。⽐如,标注了@RequestMapping的每个⽅法都可以看成是⼀个Handler。Handler负责具体实际的请求处理,在请求到达后,HandlerMapping 的作⽤便是找到请求相应的处理器Handler 和 Interceptor.

  • HandlerAdapter(处理器适配器)

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

  • HandlerExceptionResolver

    HandlerExceptionResolver ⽤于处理 Handler 产⽣的异常情况。它的作⽤是根据异常设置ModelAndView,之后交给渲染⽅法进⾏渲染,渲染⽅法会将 ModelAndView 渲染成⻚⾯。

  • ViewResolver(视图解析器)

    ViewResolver即视图解析器,⽤于将String类型的视图名和Locale解析为View类型的视图,只有⼀个resolveViewName()⽅法。从⽅法的定义可以看出,Controller层返回的String类型视图名viewName 最终会在这⾥被解析成为View。View是⽤来渲染⻚⾯的,也就是说,它会将程序返回的参数和数据填⼊模板中,⽣成html⽂件。ViewResolver 在这个过程主要完成两件事情:ViewResolver 找到渲染所⽤的模板和所⽤的技术也就是找到视图的类型,如JSP,并填⼊参数。默认情况下,Spring MVC会⾃动为我们配置⼀个InternalResourceViewResolver,是针对 JSP 类型视图的。

  • RequestToViewNameTranslator

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

  • LocaleResolver

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

  • ThemeResolver

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

  • MultipartResolver

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

  • FlashMapManager

    FlashMap ⽤于重定向时的参数传递,如果重定向时不想把参数写进URL,那么就可以通过FlashMap来传递。

拦截器**(Inteceptor)**

监听器、过滤器和拦截器对⽐

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

从配置的⻆度也能够总结发现:serlvet、fifilter、listener是配置在web.xml中的,⽽interceptor是配置在表现层框架⾃⼰的配置⽂件中的在Handler业务逻辑执⾏之前拦截⼀次、在Handler逻辑执⾏完毕但未跳转⻚⾯之前拦截⼀次、在跳转⻚⾯之后拦截⼀次

截屏2021-07-23 下午8.22.13

拦截器的执⾏流程

在运⾏程序时,拦截器的执⾏是有⼀定顺序的,该顺序与配置⽂件中所定义的拦截器的顺序相关。 单个

拦截器,在程序中的执⾏流程如下图所示:

截屏2021-07-23 下午8.59.02

1)程序先执⾏preHandle()⽅法,如果该⽅法的返回值为true,则程序会继续向下执⾏处理器中的⽅法,否则将不再向下执⾏。

2)在业务处理器(即控制器Controller类)处理完请求后,会执⾏postHandle()⽅法,然后会通过DispatcherServlet向客户端返回响应。

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

多个拦截器的执⾏流程

多个拦截器(假设有两个拦截器Interceptor1和Interceptor2,并且在配置⽂件中, Interceptor1拦截

器配置在前),在程序中的执⾏流程如下图所示:

截屏2021-07-23 下午9.01.25

当有多个拦截器同时⼯作时,它们的preHandle()⽅法会按照配置⽂件中拦截器的配置顺序执⾏,⽽它们的postHandle()⽅法和afterCompletion()⽅法则会按照配置顺序的反序执⾏。

第二部分 Spring MVC 源码简单剖析

请求处理

一个请求进来都是通过DispatcherServlet进行映射handler并执行:

DispatcherServlet和HttpServlet的关系如下:

HttpServlet->HttpServletBean->FrameworkServlet->DispatcherServlet

其中在请求到来时会进入到FrameworkServlet的doGet/doPost 方法,这两个方法会调用processRequest,processRequest会调用DispatcherServlet的doService方法 ,doService会调用doDispatch方法。doDispatch是一个关键方法,关于handler的获取和执行、拦截器的执行和页面渲染都在该方法中,源码如下:

截屏2021-07-24 上午10.49.25

截屏2021-07-24 上午10.44.36

对于拦截器的执行:

  • mappedHandler.applyPreHandle最终会调⽤HandlerInterceptor的preHandle⽅法

  • mappedHandler.applyPostHandle最终会调⽤HandlerInterceptor的postHandle⽅法

  • triggerAfterCompletion最终会调⽤HandlerInterceptor的afterCompletion ⽅法

getHandler⽅法剖析

截屏2021-07-24 上午11.13.48

getHandler会遍历handlerMappings,对于注解开发的handler都是在org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping这个mapping中,改mapping的getHandler如下:

截屏2021-07-24 上午11.13.19

在getHandlerInternal中会找到对应的handler,在getHandlerExecutionChain中会找到对应的拦截器链

所以在getHandler方法中返回的是当前请求的执⾏链

截屏2021-07-24 上午11.19.26

getHandlerAdapter⽅法剖析

getHandlerAdapter会遍历handlerAdapters,对于注解开发的对应的adapter是RequestMappingHandlerAdapter

截屏2021-07-24 上午11.21.03

RequestMappingHandlerAdapter的supports会判断handler是否继承了HandlerMethod,supportsInternal方法默认返回true

截屏2021-07-24 上午11.35.41

ha.handle⽅法剖析

入口:

截屏2021-07-24 上午11.42.05

进⼊handle方法会调用handleInternal方法,然后会调用invokeHandlerMethod,然后调用invokeAndHandle,然后调用invokeForRequest:

截屏2021-07-24 上午11.49.26

invokeForRequest方法如下:

截屏2021-07-24 上午11.49.55

getMethodArgumentValues会处理参数的映射,doInvoke会执行具体的handler方法

processDispatchResult⽅法剖析

processDispatchResult的关键方法是render方法,该方法完成view视图的的解析和数据的渲染

截屏2021-07-24 下午2.58.13

render方法有resolveViewName、render两个关键方法, resolveViewName负责器解析出View视图对象,render负责渲染数据

  • resolveViewName方法:

在viewResolver.resolveViewName中会调用createView生成一个视图对象,其中isCache是用来判断是否渲染过该视图

截屏2021-07-24 下午3.16.50

createView在解析出View视图对象的过程中会判断是否重定向、是否转发等,不同的情况封装的是不同的View实现,这里会调用super.createView来生成对像

截屏2021-07-24 下午3.19.20

super.createView最终会调用UrlBasedViewResolver类的buildView方法,下方红框的代码就是拼接前后缀解析出物理视图名

截屏2021-07-24 下午3.21.54

  • 解析出装View视图对象之后,调⽤了view.render⽅法进行数据渲染

    render会调用renderMergedOutputModel,renderMergedOutputModel会调用exposeModelAsRequestAttributes,exposeModelAsRequestAttributes会把modelMap中的数据暴露到request域中,这也是为什么后台model.add之后在jsp中可以从请求域取出来的根本原因

截屏2021-07-24 下午3.28.40

截屏2021-07-24 下午3.29.21

截屏2021-07-24 下午3.29.36

SpringMVC九⼤组件初始化

初始化顺序

HttpServletBean.init->FrameworkServlet.initServletBean->FrameworkServlet.initWebApplicationContext->DispatcherServlet.onRefresh->DispatcherServlet.initStrategies

在DispatcherServlet.initStrategies中会初始化九大组件,九大组件都是接口

截屏2021-07-24 下午4.10.17

initHandlerMappings初始化

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

截屏2021-07-24 下午6.10.51

默认策略:

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

在createDefaultStrategy创建RequestMappingHandlerMapping bean的时候的时候会把url和handler的映射关系放在mappingRegistry的mappingLookup中

截屏2021-07-24 下午6.15.48

截屏2021-07-24 下午6.25.08

文件解析器的初始化必须按照id(multipartResolver)注册对象的原因就是在初始化的时候是按照固定id从ioc容器中找的bean。其中MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver"

截屏2021-07-24 下午6.27.20

第三部分 乱码和三种设计模式

乱码

  • Post请求乱码,web.xml中加⼊过滤器

    <!-- 解决post乱码问题 -->
    
    <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"/>

设计模式

策略模式

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

  • 结构

策略(Strategy)

定义所有⽀持算法的公共接⼝。 Context 使⽤这个接⼝来调⽤某 ConcreteStrategy 定义的算法。

策略实现(ConcreteStrategy)

实现了Strategy 接⼝的具体算法

上下⽂(Context)

维护⼀个 Strategy 对象的引⽤

⽤⼀个 ConcreteStrategy 对象来装配

可定义⼀个接⼝⽅法让 Strategy 访问它的数据

示例:假如现在有⼀个商场优惠活动,有的商品原价售卖,有的商品打8.5折,有的商品打6折,有的返现5元

1.抽象策略类 AbstractDiscount,它是所有具体打折⽅案(算法)的⽗类,定义了⼀个 discount抽象⽅法

package designpattern.strategy.now.discount;
public abstract class AbstractDiscount {
public double getFinalPrice() {
return finalPrice;
 }
public void setFinalPrice(double finalPrice) {
this.finalPrice = finalPrice;
 }
public String getDesc() {
return desc;
 }
public void setDesc(String desc) {
this.desc = desc;
 }
protected double finalPrice;
protected String desc;
public IDiscount(String desc) {
this.desc = desc;
 }
public abstract double discount(double price);
}

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

package designpattern.strategy.now.discount.impl;
import designpattern.strategy.now.discount.AbstractDiscount;
public class Discount85 extends AbstractDiscount {
public Discount85() {
super("该商品可享受8.5折优惠");
 }
@Override
public double discount(double price) {
finalPrice = price * 0.85;
return finalPrice;
 }
}
package designpattern.strategy.now.discount.impl;
import designpattern.strategy.now.discount.AbstractDiscount;
public class Discount6 extends AbstractDiscount {
public Discount6() {
super("该商品可享受6折优惠");
 }
@Override
public double discount(double price) {
finalPrice = price * 0.6;
return finalPrice;
 }
}
package designpattern.strategy.now.discount.impl;
import designpattern.strategy.now.discount.AbstractDiscount;
public class Return5 extends AbstractDiscount {
public Return5() {
super("该商品可返现5元");
 }
@Override
public double discount(double price) {
this.finalPrice = price >= 5 ? price - 5 : 0;
return finalPrice;
 }
}
package designpattern.strategy.now.discount.impl;
import designpattern.strategy.now.discount.AbstractDiscount;
public class NoDiscount extends AbstractDiscount {
public NoDiscount() {
super("对不起,该商品不参与优惠活动");
 }
@Override
public double discount(double price) {
finalPrice = price;
return finalPrice;
 }
}

3.类 BuyGoods,维护了⼀个 AbstractDiscount 引⽤

package designpattern.strategy.now;

import designpattern.strategy.now.discount.AbstractDiscount;

import java.text.MessageFormat;

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;

 }

}

测试

package designpattern.strategy.now;

import designpattern.strategy.now.discount.impl.*;

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类源码;代码复⽤也变得简单,直接复⽤某⼀个具体策略类即可;

模板⽅法模式

模板⽅法模式是指定义⼀个算法的⻣架,并允许⼦类为⼀个或者多个步骤提供实现。模板⽅法模式使得⼦类可以在不改变算法结构的情况下,重新定义算法的某些步骤,属于⾏为型设计模式。采⽤模板⽅法模式的核⼼思路是处理某个流程的代码已经具备,但其中某些节点的代码暂时不能确定。此时可以使⽤模板⽅法。

示例



/**

* ⾯试⼤⼚流程类

**/

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

Java岗位⾯试者



/**

* ⾯试⼈员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();

 }

}

打印结果

面试登记

我是⾯试⼈员1,来⾯试Java⼯程师,我们聊的是Java相关内容

HR⼩姐姐通知⾯试结果

面试登记

我是⾯试⼈员2,来⾯试前端⼯程师,我们聊的是前端相关内容

HR⼩姐姐通知⾯试结果

适配器模式

使得原本由于接⼝不兼容⽽不能⼀起⼯作、不能统⼀管理的那些类可以⼀起⼯作、可以进⾏统⼀管理

  • 解决接⼝不兼容⽽不能⼀起⼯作问题,看下⾯⼀个⾮常经典的案例

在中国,⺠⽤电都是220v交流电,但是⼿机锂电池⽤的都是5v直流电。因此,我们给⼿机充电时就需要使⽤电源适配器来进⾏转换。使⽤代码还原这个⽣活场景创建AC220类,表示220v交流电

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

创建DC5接⼝,表示5V直流电:

public interface DC5 {
int outputDC5V();
}

创建电源适配器类 PowerAdapter

public class PowerAdapter implements DC5 {
private AC220 ac220;
public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
 }
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实现了⼆者的兼容

  • 解决不能统⼀管理的问题

SpringMVC中处理器适配器(HandlerAdapter)机制就是解决类统⼀管理问题⾮常经典的场景

其中 HandlerAdapter接⼝是处理器适配器的顶级接⼝,它有多个⼦类,包括AbstractHandlerMethodAdapter、SimpleServletHandlerAdapter、SimpleControllerHandlerAdapter、HttpRequestHandlerAdapter、RequestMappingHandlerAdapter

其适配器调⽤的关键代码也在DispatcherServlet的doDispatch()

截屏2021-07-23 下午9.33.34

在 doDispatch() ⽅法中调⽤了 getHandlerAdapter() ⽅法

截屏2021-07-23 下午9.35.33

在 getHandlerAdapter() ⽅法中循环调⽤了 supports() ⽅法判断是否兼容,循环迭代集合中的“Adapter” 在初始化时已经赋值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值