Spring MVC ⾼级框架
第一部分 Spring MVC 的应用
springmvc简介
三层架构:
在java开发中基本上都是B/S架构,即浏览器/服务器。系统标准的三层架构包括:表现层、业务层、持久层。
-
表现层 :
也就是我们常说的web 层。它负责接收客户端请求,向客户端响应结果,通常客户端使⽤http 协议请求web 层,web 需要接收 http 请求,完成 http 响应。
表现层包括展示层和控制层:控制层负责接收请求对应controller,展示层负责结果的展示对应页面。表现层依赖业务层,接收到客户端请求⼀般会调⽤业务层进⾏业务处理,并将处理结果响应给客户端。表现层的设计⼀般都使⽤ MVC 模型。(MVC 是表现层的设计模型,和其他层没有关系)
-
业务层 :
也就是我们常说的 service 层。它负责业务逻辑处理,和我们开发项⽬的需求息息相关。web 层依赖业务层,但是业务层不依赖 web 层。
业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务⼀致性所以事务应该放到业务层
-
持久层 :
也就是我们是常说的 dao 层。负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进⾏持久化的载体,数据访问层是业务层和持久层交互的接⼝,业务层需要通过数据访问层将数据持久化到数据库中。通俗的讲,持久层就是和数据库交互,对数据库表进⾏增删改查的。
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的开发其作用是接收请求和返回响应,跳转⻚⾯
使用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
流程说明
-
第⼀步:⽤户发送请求⾄前端控制器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](https://i-blog.csdnimg.cn/blog_migrate/935c5f8dd5226736ee489e18f57d81b5.png)
拦截器的执⾏流程
在运⾏程序时,拦截器的执⾏是有⼀定顺序的,该顺序与配置⽂件中所定义的拦截器的顺序相关。 单个
拦截器,在程序中的执⾏流程如下图所示:
![截屏2021-07-23 下午8.59.02](/Users/dingkaige/Library/Application%20Support/typora-user-images/截屏2021-07-23%20下午8.59.02.png)
1)程序先执⾏preHandle()⽅法,如果该⽅法的返回值为true,则程序会继续向下执⾏处理器中的⽅法,否则将不再向下执⾏。
2)在业务处理器(即控制器Controller类)处理完请求后,会执⾏postHandle()⽅法,然后会通过DispatcherServlet向客户端返回响应。
3)在DispatcherServlet处理完请求后,才会执⾏afterCompletion()⽅法。
多个拦截器的执⾏流程
多个拦截器(假设有两个拦截器Interceptor1和Interceptor2,并且在配置⽂件中, Interceptor1拦截
器配置在前),在程序中的执⾏流程如下图所示:
![截屏2021-07-23 下午9.01.25](https://i-blog.csdnimg.cn/blog_migrate/2f1f9a7fd4ac379443ad519334ad1e85.png)
当有多个拦截器同时⼯作时,它们的preHandle()⽅法会按照配置⽂件中拦截器的配置顺序执⾏,⽽它们的postHandle()⽅法和afterCompletion()⽅法则会按照配置顺序的反序执⾏。
第二部分 Spring MVC 源码简单剖析
请求处理
一个请求进来都是通过DispatcherServlet进行映射handler并执行:
DispatcherServlet和HttpServlet的关系如下:
HttpServlet->HttpServletBean->FrameworkServlet->DispatcherServlet
其中在请求到来时会进入到FrameworkServlet的doGet/doPost 方法,这两个方法会调用processRequest,processRequest会调用DispatcherServlet的doService方法 ,doService会调用doDispatch方法。doDispatch是一个关键方法,关于handler的获取和执行、拦截器的执行和页面渲染都在该方法中,源码如下:
对于拦截器的执行:
-
mappedHandler.applyPreHandle最终会调⽤HandlerInterceptor的preHandle⽅法
-
mappedHandler.applyPostHandle最终会调⽤HandlerInterceptor的postHandle⽅法
-
triggerAfterCompletion最终会调⽤HandlerInterceptor的afterCompletion ⽅法
getHandler⽅法剖析
getHandler会遍历handlerMappings,对于注解开发的handler都是在org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping这个mapping中,改mapping的getHandler如下:
在getHandlerInternal中会找到对应的handler,在getHandlerExecutionChain中会找到对应的拦截器链
所以在getHandler方法中返回的是当前请求的执⾏链
getHandlerAdapter⽅法剖析
getHandlerAdapter会遍历handlerAdapters,对于注解开发的对应的adapter是RequestMappingHandlerAdapter
RequestMappingHandlerAdapter的supports会判断handler是否继承了HandlerMethod,supportsInternal方法默认返回true
ha.handle⽅法剖析
入口:
进⼊handle方法会调用handleInternal方法,然后会调用invokeHandlerMethod,然后调用invokeAndHandle,然后调用invokeForRequest:
invokeForRequest方法如下:
getMethodArgumentValues会处理参数的映射,doInvoke会执行具体的handler方法
processDispatchResult⽅法剖析
processDispatchResult的关键方法是render方法,该方法完成view视图的的解析和数据的渲染
render方法有resolveViewName、render两个关键方法, resolveViewName负责器解析出View视图对象,render负责渲染数据
- resolveViewName方法:
在viewResolver.resolveViewName中会调用createView生成一个视图对象,其中isCache是用来判断是否渲染过该视图
createView在解析出View视图对象的过程中会判断是否重定向、是否转发等,不同的情况封装的是不同的View实现,这里会调用super.createView来生成对像
super.createView最终会调用UrlBasedViewResolver类的buildView方法,下方红框的代码就是拼接前后缀解析出物理视图名
-
解析出装View视图对象之后,调⽤了view.render⽅法进行数据渲染
render会调用renderMergedOutputModel,renderMergedOutputModel会调用exposeModelAsRequestAttributes,exposeModelAsRequestAttributes会把modelMap中的数据暴露到request域中,这也是为什么后台model.add之后在jsp中可以从请求域取出来的根本原因
SpringMVC九⼤组件初始化
初始化顺序
HttpServletBean.init->FrameworkServlet.initServletBean->FrameworkServlet.initWebApplicationContext->DispatcherServlet.onRefresh->DispatcherServlet.initStrategies
在DispatcherServlet.initStrategies中会初始化九大组件,九大组件都是接口
initHandlerMappings初始化
如果按照类型和按照固定id从ioc容器中找不到对应组件,则会按照默认策略进⾏注册初始化,默认策略在DispatcherServlet.properties⽂件中配置
默认策略:
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中
文件解析器的初始化必须按照id(multipartResolver)注册对象的原因就是在初始化的时候是按照固定id从ioc容器中找的bean。其中MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver"
第三部分 乱码和三种设计模式
乱码
-
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()
在 doDispatch() ⽅法中调⽤了 getHandlerAdapter() ⽅法
在 getHandlerAdapter() ⽅法中循环调⽤了 supports() ⽅法判断是否兼容,循环迭代集合中的“Adapter” 在初始化时已经赋值。