记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步
文章目录
策略模式
实际项目开发中,策略模式较为常用,利用它避免冗长的if-else或者switch分支判断,还包括提供框架的扩展点等。
策略模式的原理和实现
策略模式,Strategy Design Pattern。定义: Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it. 定义一族算法类,将每个算法分别封装起来,让他们可以互相替换,策略模式可使得算法的变化独立于使用它的客户端(指的是使用算法的代码)。
工厂模式是解耦对象的创建和使用,观察者模式是解耦观察者和被观察者。策略模式类似,也起到解耦的作用,解耦的是策略的定义、创建、使用三部分。
1. 策略的定义
较为简单,包含一个策略接口和一组实现该接口的策略类。因为所有的策略类都实现相同的接口,所有,客户端代码基于接口而非实现编程,可灵活的替换不同的策略。
public interface Strategy {
void algorithmInterface();
}
public class ConcreteStrategyA implements Strategy {
@Override
public void algorithmInterface() {
//...具体的算法...
}
}
public class ConcreteStrategyB implements Strategy {
@Override
public void algorithmInterface() {
//...具体的算法...
}
}
2. 策略的创建
策略模式会包含一组策略,使用时,通过类型type判断创建哪种策略使用,为封装创建逻辑,需要对客户端代码屏蔽创建细节,可把根据type创建策略的逻辑抽离出来,放到工厂类。
public class StrategyFactory {
private static final Map<String, Strategy> strategies = new HashMap<>();
static {
strategies.put("A",new ConcreteStrategyA());
strategies.put("B",new ConcreteStrategyB());
}
public static Strategy getStrategy(String type){
if (type == null || type.isEmpty()){
throw new IllegalArgumentException("type should not be empty");
}
return strategies.get(type);
}
}
如果策略类是无状态的,不包含成员变量,只是纯粹的算法实现,这样的策略对象是可被共享使用的,不需要再每次调用getStrategy()的时候,都创建一个新的策略对象,针对这种情况,可使用上面的这种工厂类的实现方式,事先创建好每个策略对象,缓存到工厂类,用的时候直接返回;相反,如果策略类是有状态的,根据业务场景的需要,希望每次从工厂方法中,获得的是新创建的策略对象,而不是缓存好可共享的策略对象,代码如下:
public class StrategyFactory {
public static Strategy getStrategy(String type){
if (type == null || type.isEmpty()){
throw new IllegalArgumentException("type should not be empty.");
}
if (type.equals("A")){
return new ConcreteStrategyA();
}else if (type.equals("B")){
return new ConcreteStrategyB();
}
return null;
}
}
3. 策略的使用
客户端代码如何确定使用哪个策略?最常见的是运行时动态确定使用哪种策略,这也是策略模式最典型的应用场景。
运行时动态指的是,事先不知道会使用哪种策略,而是在程序运行时,根据配置、用户输入、计算结果等不确定因素,动态决定使用哪种策略。
public class UserCache {
private Map<String, User> cacheData = new HashMap<>();
private EvictionStrategy eviction;
public UserCache(EvictionStrategy eviction){
this.eviction = eviction;
}
//...
}
public class Application {
//运行时动态确定,根据配置文件的配置决定使用哪种策略
public static void main(String[] args) throws Exception {
EvictionStrategy evictionStrategy = null;
Properties properties = new Properties();
properties.load(new FileInputStream("./config.properties"));
String type = properties.getProperty("eviction_type");
evictionStrategy = EvictionStrategyFactory.getEvictionStrategy(type);
UserCache userCache = new UserCache(evictionStrategy);
//...
}
}
从上面代码看出,非运行时动态确定,也就是第二个Application中的使用方式,并不能发挥策略模式的优势,这种应用场景下,策略模式退化为面向对象的多态特性 或 基于接口而非实现编程原则。
如何利用策略模式避免分支判断
能移除分支判断逻辑的模式不仅有策略模式,还有状态模式,对于使用哪种模式,具体看应用场景。策略模式适用于根据不同类型的状态,决定使用哪种策略这种应用场景。
先看if-else货switch是如何产生的,如下:
public class OrderService {
public double discount(Order order){
double discount = 0.0;
OrderType type = order.getType();
if (type.equals(OrderType.NORMAL)){
//普通订单
//...省略折扣计算算法代码
}else if (type.equals(OrderType.GROUPON)){
//团购订单 ...省略折扣计算算法代码
}else if (type.equals(OrderType.PROMOTION)){
//促销订单 ...省略折扣计算算法代码
}
return discount;
}
}
那如何移除分支判断逻辑?策略模式,对代码重构,将不同类型的订单的打折策略设计为策略类,并由工厂类负责创建策略对象,具体代码:
public interface DiscountStrategy {
double calDiscount(Order order);
}
//省略NormalDiscountStrategy GrouponDiscountStrategy PromotionDiscountStrategy的创建
//策略的创建
public class DiscountStrategyFactory {
private static final Map<OrderType,DiscountStrategy> strategies = new HashMap<>();
static {
strategies.put(OrderType.NORMAL,new NormalDiscountStrategy());
strategies.put(OrderType.GROUPON,new GrouponDiscountStrategy());
strategies.put(OrderType.PROMOTION,new PromotionDiscountStrategy());
}
public static DiscountStrategy getDiscountStrategy(OrderType type){
return strategies.get(type);
}
}
public class OrderService {
public double discount(Order order){
OrderType type = order.getType();
DiscountStrategy discountStrategy = DiscountStrategyFactory.getDiscountStrategy(type);
return discountStrategy.calDiscount(order);
}
}
在策略工厂,用map缓存策略,根据type直接从map中获取对应的策略,避免if-else分支判断逻辑。本质上是借助“查表法”,根据type查表替代根据type分支判断。
但是,如果业务场景需要每次都创建不同的策略对象,就要用另一种工厂类的实现方式
public class DiscountStrategyFactory {
public static DiscountStrategy getDiscountStrategy(OrderType type){
if (type == null){
throw new IllegalArgumentException("type should not be null");
}
if (type.equals(OrderType.NORMAL)){
return new NormalDiscountStrategy();
}else if (type.equals(OrderType.GROUPON)){
return new GrouponDiscountStrategy();
}else if (type.equals(OrderType.PROMOTION)){
return new PromotionDiscountStrategy();
}
return null;
}
}
这种情况下,实际并未真正移除if-else逻辑,而是将其移到工厂类中。
文件排序
问题和解决思路
假设有个需求,希望写个小程序,实现对一文件进行排序的功能,文件只包含整型数,且相邻的数字通过逗号区分。如果你编写这个小程序,如何实现?
可能会觉得很简单,将文件的内容读出来,通过逗号分割为一个一个的数字,放到内存数组中,然后编写某种排序算法(如快排),或者直接用编程语言提供的排序函数。对数组排序,最后写入文件。
但如果文件很大呢?如有10GB,而内存有限(如只有8GB),没办法一次性加载文件的所有数据到内存,这时,利用外部排序算法了。
如果文件更大,如100GB,为利用CPU多核的优势,可在外排的基础上优化,加入多线程并发排序的功能,类似单机版的MapReduce。
如果文件非常大,如1TB,即便单机多线程程序,也算比较慢,这时可用MapReduce框架,利用多机处理能力,提高排序的效率。
代码实现和分析
先实现最简单直接的方式
public class Sorter {
private static final long GB = 1000*1000*1000;
public void sortFile(String filePath){
//省略校验逻辑
File file = new File(filePath);
long fileSize = file.length();
if (fileSize < 6*GB){
quickSort(filePath);
}else if (fileSize < 10*GB){
externalSort(filePath);
}else if (fileSize < 100*GB){
concurrentExternalSort(filePath);
}else {
mapreduceSort(filePath);
}
}
private void mapreduceSort(String filePath) {
//利用MapReduce排序
}
private void concurrentExternalSort(String filePath) {
//多线程外排
}
private void externalSort(String filePath) {
//外部排序
}
private void quickSort(String filePath) {
//快排
}
}
public class SortingTool {
public static void main(String[] args) {
Sorter sorter = new Sorter();
sorter.sortFile(args[0]);
}
}
为避免sortFile()方法过长,把每种排序算法从sortFile()方法中抽离出来,分为4个独立的排序方法。
如果正在开发一个大型项目,排序文件只是其中一个功能模块,就要在代码设计和代码质量上下功夫。
刚如果自己实现算法的逻辑,会比较复杂,代码行数过多,所有排序算法的代码都堆在Sorter类,导致该类代码很多,此外,所有的排序算法都设计为Sorter的私有函数,也影响代码的复用性。
代码优化和重构
拆分重构后:
public interface ISortAlg {
void sort(String filePath);
}
public class QuickSort implements ISortAlg {
@Override
public void sort(String filePath) {
//...
}
}
public class ExternalSort implements ISortAlg {
@Override
public void sort(String filePath) {
//...
}
}
public class ConcurrentExternalSort implements ISortAlg {
@Override
public void sort(String filePath) {
//...
}
}
public class MapReduceSort implements ISortAlg {
@Override
public void sort(String filePath) {
//...
}
}
public class Sorter {
private static final long GB = 1000*1000*1000;
public void sortFile(String filePath){
//省略校验逻辑
File file = new File(filePath);
long fileSize = file.length();
ISortAlg sortAlg;
if (fileSize < 6*GB){
sortAlg = new QuickSort();
}else if (fileSize < 10*GB){
sortAlg = new ExternalSort();
}else if (fileSize < 100*GB){
sortAlg = new ConcurrentExternalSort();
}else {
sortAlg = new MapReduceSort();
}
sortAlg.sort(filePath);
}
}
拆分后,每个类的代码都不会太多,每个类的逻辑都不会太复杂,代码的可读性、可维护性提高。此外,排序算法设计为独立的类,跟具体的业务逻辑解耦,让排序算法可复用,实际是策略模式的第一步,解耦出策略的定义。
实际上,可继续优化,每种排序类都是无状态的,没必要每次使用时,重新创建一个新的对象。所有,可使用工厂模式对对象的创建进行封装,重构后:
public class SortAlgFactory {
private static final Map<String, ISortAlg> algs = new HashMap<>();
static {
algs.put("QuickSort",new QuickSort());
algs.put("ExternalSort",new ExternalSort());
algs.put("ConcurrentExternalSort",new ConcurrentExternalSort());
algs.put("MapReduceSort",new MapReduceSort());
}
public static ISortAlg getSortAlg(String type){
if (type == null || type.isEmpty()){
throw new IllegalArgumentException("type should not be empty");
}
return algs.get(type);
}
}
public class Sorter {
private static final long GB = 1000*1000*1000;
public void sortFile(String filePath){
//省略校验逻辑
File file = new File(filePath);
long fileSize = file.length();
ISortAlg sortAlg;
if (fileSize < 6*GB){
sortAlg = SortAlgFactory.getSortAlg("QuickSort");
}else if (fileSize < 10*GB){
sortAlg = SortAlgFactory.getSortAlg("ExternalSort");
}else if (fileSize < 100*GB){
sortAlg = SortAlgFactory.getSortAlg("ConcurrentExternalSort");
}else {
sortAlg = SortAlgFactory.getSortAlg("MapReduceSort");
}
sortAlg.sort(filePath);
}
}
两次重构后,代码已经符合策略模式的代码结构了,但Sorter类的sortFile()函数还是有一堆if-else逻辑,如果特别想一出,可基于查表法,其中algs是表。
public class Sorter {
private static final long GB = 1000*1000*1000;
private static final List<AlgRange> algs = new ArrayList<>();
static {
algs.add(new AlgRange(0,6*GB, SortAlgFactory.getSortAlg("QuickSort")));
algs.add(new AlgRange(6*GB,10*GB, SortAlgFactory.getSortAlg("ExternalSort")));
algs.add(new AlgRange(10*GB,100*GB, SortAlgFactory.getSortAlg("ConcurrentExternalSort")));
algs.add(new AlgRange(100*GB,Long.MAX_VALUE, SortAlgFactory.getSortAlg("MapReduceSort")));
}
public void sortFile(String filePath) {
//省略校验逻辑
File file = new File(filePath);
long fileSize = file.length();
ISortAlg sortAlg = null;
for (AlgRange algRange:algs){
if (algRange.inRange(fileSize)){
sortAlg = algRange.getAlg();
break;
}
}
sortAlg.sort(filePath);
}
private static class AlgRange{
private long start;
private long end;
private ISortAlg alg;
public AlgRange(long start, long end, ISortAlg alg) {
this.start = start;
this.end = end;
this.alg = alg;
}
public ISortAlg getAlg() {
return alg;
}
public boolean inRange(long size){
return size >= start && size < end;
}
}
}
即便如此,当添加新的排序算法时,还要修改代码,不完全符合开闭原则,如何满足?通过反射来避免对策略工厂的类的修改。具体:通过一个配置文件或自定义的annotation标注有哪些策略类;策略工厂类读取配置文件或搜索被annotation标注的策略类,反射动态加载这些策略类、创建策略对象;当新添加一个策略的时候,只要将该策略类添加到配置文件或用annotation标注即可。
对sorter来说,同样避免修改,通过将文件大小区间和算法之间的对应关系放到配置文件。当添加新的排序算法,只需要改动配置文件,不用改代码。
责任链模式
模板模式、策略模式和责任链模式的相同作用:复用和扩展。
原理和实现
责任链,Chain Of Responsibility Design Pattern。定义:Avoid coupling the sender of a request to its receive by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. 将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,沿着这条链传递这个请求,直到链上的某个接收对象能处理它为止。
在责任链中,多个处理器(也就是定义中的接收对象)依次处理同一个请求。一个请求先经过A处理器处理,然后把请求传递给B处理器,B处理器处理完后再传递给C处理器,依次类推,形成链。链上的每个处理器各自承担各自的处理职责,所以叫责任链模式。
先看代码实现,有两种。
第一种实现
Handle是所有处理器的抽象父类,handle()是抽象方法,每个具体的处理器类(HandleA、HandleB)的handle()方法的代码结构类似,如果它能处理该请求,就不继续往下传递;如果不能,交由后面的处理器来处理(也就是调用successor.handle())。HandleChain是处理器链,从数据结构上看,就是个记录了链头、链尾的链表。其中,记录链尾是为了方便添加处理器。
public abstract class Handler {
protected Handler successor = null;
public void setSuccessor(Handler successor){
this.successor = successor;
}
public abstract void handle();
}
public class HandlerA extends Handler {
@Override
public void handle() {
boolean handled = false;
//...
if (!handled && successor != null){
successor.handle();
}
}
}
public class HandlerB extends Handler {
@Override
public void handle() {
boolean handled = false;
//...
if (!handled && successor != null){
successor.handle();
}
}
}
public class HandlerChain {
private Handler head = null;
private Handler tail = null;
public void addHandler(Handler handler){
handler.setSuccessor(null);
if (head == null){
head = handler;
tail = handler;
return;
}
tail.setSuccessor(handler);
tail = handler;
}
public void handle(){
if (head != null){
head.handle();
}
}
}
public class Application {
public static void main(String[] args) {
HandlerChain chain = new HandlerChain();
chain.addHandler(new HandlerA());
chain.addHandler(new HandlerB());
chain.handle();
}
}
上面的代码实现不够优雅,处理器的handle()方法,不仅包含自身的业务逻辑,还包含对下一个处理器的调用,也即是代码中的successor.handle()。很可能其他人在handle()方法调用它,导致代码出现bug。
针对该问题,重构,将调用它的逻辑从具体的处理器类中剥离出来,放到抽象父类中,具体的处理器类只需实现自己的业务逻辑即可。
public abstract class Handler {
protected Handler successor = null;
public void setSuccessor(Handler successor){
this.successor = successor;
}
public final void handle(){
boolean handled = doHandle();
if (!handled && successor != null){
successor.handle();
}
}
protected abstract boolean doHandle();
}
public class HandlerA extends Handler {
@Override
protected boolean doHandle() {
boolean handled = false;
//...
return handled;
}
}
public class HandleB extends Handler {
@Override
protected boolean doHandle() {
boolean handled = false;
//...
return handled;
}
}
第二种实现
更加简单,HandlerChain类用数组而非链表保存所有的处理器,并需要在HandlerChain的handle()方法,依次调用每个处理器的handle()方法。
public interface IHandler {
boolean handle();
}
public class HandlerA implements IHandler {
@Override
public boolean handle() {
boolean handled = false;
//...
return handled;
}
}
public class HandlerB implements IHandler {
@Override
public boolean handle() {
boolean handled = false;
//...
return handled;
}
}
public class HandlerChain {
private List<IHandler> handlers = new ArrayList<>();
public void addHandler(IHandler handler){
this.handlers.add(handler);
}
public void handle(){
for (IHandler handler:handlers){
boolean handled = handler.handle();
if (handled){
break;
}
}
}
}
public class Application {
public static void main(String[] args) {
HandlerChain chain = new HandlerChain();
chain.addHandler(new HandlerA());
chain.addHandler(new HandlerB());
chain.handle();
}
}
在GoF的定义中,如果责任链上某个处理器能够处理该请求,不会继续往下传递请求。实际,还有一种变体,就是请求会被所有处理器都处理一遍,不会中途终止。这种变体也有两种实现方式:用链表存储处理器和用数组存储处理器,和上面的两种实现方式类似,只需稍微修改即可。
只给出第一种实现方式:
public abstract class Handler {
protected Handler successor = null;
public void setSuccessor(Handler successor){
this.successor = successor;
}
public final void handle(){
doHandle();
if (successor != null){
successor.handle();
}
}
protected abstract void doHandle();
}
public class HandlerA extends Handler {
@Override
protected void doHandle() {
//...
}
}
public class HandlerB extends Handler {
@Override
protected void doHandle() {
//...
}
}
public class HandlerChain {
private Handler head = null;
private Handler tail = null;
public void addHandler(Handler handler){
handler.setSuccessor(null);
if (head == null){
head = handler;
tail = handler;
return;
}
tail.setSuccessor(handler);
tail = handler;
}
public void handle(){
if (head != null){
head.handle();
}
}
}
public class Application {
public static void main(String[] args) {
HandlerChain chain = new HandlerChain();
chain.addHandler(new HandlerA());
chain.addHandler(new HandlerB());
chain.handle();
}
}
责任链模式的应用场景
实际案例,对于支持UGC(User Generated Content,用户生成内容)的应用(如论坛),用户生成的内容可能会包含一些敏感词(如涉黄、广告、反动等词汇),针对该应用场景,可利用责任链模式过滤这些敏感词。
对于包含敏感词的内容,有两种处理方式,一种是直接禁止发布,另一种是给敏感词打马赛克(如用***替换敏感词)之后再发布。第一种符合GoF的责任链的定义,第二种算是它的变体。
只给出第一种方式的代码案例,另外,只给出了代码实现的骨架,具体的敏感词过滤算法并没有给出。
public interface SensitiveWordFilter {
boolean doFilter(Content content);
}
public class SexyWordFilter implements SensitiveWordFilter {
@Override
public boolean doFilter(Content content) {
boolean legal = true;
//...
return legal;
}
}
public class PoliticalWordFilter implements SensitiveWordFilter {
@Override
public boolean doFilter(Content content) {
boolean legal = true;
//...
return legal;
}
}
public class AdsWordFilter implements SensitiveWordFilter {
@Override
public boolean doFilter(Content content) {
boolean legal = true;
//...
return legal;
}
}
public class SensitiveWordFilterChain {
private List<SensitiveWordFilter> filters = new ArrayList<>();
public void addFilter(SensitiveWordFilter filter){
this.filters.add(filter);
}
//return true if content doesn't contain sensitive words
public boolean filter(Content content){
for (SensitiveWordFilter filter:filters){
if (!filter.doFilter(content)){
return false;
}
}
return true;
}
}
public class ApplicationDemo {
public static void main(String[] args) {
SensitiveWordFilterChain filterChain = new SensitiveWordFilterChain();
filterChain.addFilter(new AdsWordFilter());
filterChain.addFilter(new SexyWordFilter());
filterChain.addFilter(new PoliticalWordFilter());
boolean legal = filterChain.filter(new Content());
if (!legal){
//不发表
}else {
//发表
}
}
}
如果像下面这种也能实现,而且更简单,为啥还要用责任链模式呢?
public class SensitiveWordFilter {
//return true if content doesn't contain sensitive words.
public boolean filter(Content content){
if (!filterSexyWord(content)){
return false;
}
if (!filterAdsWord(content)){
return false;
}
if (!filterPoliticalWord(content)){
return false;
}
return true;
}
private boolean filterSexyWord(Content content){
//...
}
private boolean filterAdsWord(Content content){
//...
}
private boolean filterPoliticalWord(Content content){
//...
}
}
应用设计模式主要是为了应对代码的复杂性,让其满足开闭原则,提高代码的扩展性。
- 首先看,责任链模式如何应对代码的复杂性
将大块代码逻辑拆分为方法,将大类拆分为小类,第应对代码复杂性的常用方法,应用责任来呢模式,把各个敏感词过滤函数继续拆分,设计为独立的类,进一步简化SensitiveWordFilter类,让其代码不至于过多。
- 其次,责任链模式如何让代码满足开闭原则,提高代码的扩展性
当要扩展新的过滤算法,如还要过滤特殊符号,按照非责任链模式的代码实现,需要修改SensitiveWordFilter的代码,违反开闭原则,而责任链模式更加优雅,只需添加一个filter类,且通过addFilter()方法将其添加到FilterChain即可,其他代码完全不用改。
即使用责任链模式,当新添加过滤算法,还要修改客户端代码ApplicationDemo,没有完全符合开闭原则。其实细化,可把代码分为两类:框架代码和客户端代码。客户端代码属于使用框架的代码。
此外,利用责任链模式相对于不用的方式,还有好处是,配置过滤算法更灵活,可选择只用某几个过滤算法。
servlet filter
servlet filter是java servlet规范中定义的组件,过滤器,可实现对http请求的过滤功能,如鉴权、限流、记录日志、验证参数等。因为是servlet规范的而一部分,所有tomcat、jetty等web容器都支持过滤器功能。
实际项目如何使用servlet filter?简单示例,添加一个过滤器,只需定义一个实现javax.sevlet.Filter接口的过滤器类,并将其配置到web.xml配置文件中。web容器启动时,读取web.xml的配置,创建过滤器对象。当有请求到来,先经过过滤器,再由servlet处理。
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//创建filter时自动调用
//其中filterConfig包含这个filter的配置参数,如name之类
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("拦截客户端发来的请求");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("拦截发送给客户端的请求");
}
@Override
public void destroy() {
//销毁filter时自动调用
}
}
//web.xml的配置
<filter>
<filter-name>logFilter</filter-name>
<filter-class>com.ai.doc.chain.filter.v1.LogFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>logFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
添加过滤器非常方便,不需要改任何代码,那servlet如何做到的呢?用责任链模式。责任链的实现包含处理器接口IHandler或抽象类Handler,以及处理器链HandlerChain,对应到servlet filter,javax.servlet.Filter是处理器接口,FilterChain是处理器链,看FilterChain如何实现。以tomcat提供的实现类ApplicationFilterChain为例
public class ApplicationFilterChain implements FilterChain {
private int pos = 0;//当前执行到哪个filter
private int n;//filter的个数
private ApplicationFilterConfig[] filters;
private Servlet servlet;
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (pos < n){
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
filter.doFilter(request,response,this);
}else {
//filter都处理完毕,执行servlet
servlet.service(request, response);
}
}
public void addFilter(ApplicationFilterConfig filterConfig){
for (ApplicationFilterConfig filter:filters){
if (filter == filterConfig){
return;
}
}
if (n==filters.length){
//扩容
ApplicationFilterConfig[] newFilters = new ApplicationFilterConfig[n+n];
System.arraycopy(filters,0,newFilters,0,n);
filters = newFilters;
}
filters[n++] = filterConfig;
}
}
ApplicationFilterChain的doFilter()方法的代码实现比较有技巧,实际是个递归调用,可用每个Filter(如LogFilter)的doFilter()的代码实现,直接替换ApplicationFilterChain的第12行代码即可。
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (pos < n){
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
System.out.println("拦截客户端发来的请求");
chain.doFilter(request,response);//chain就是this
System.out.println("拦截发送给客户端的响应");
}else {
//filter都处理完毕,执行servlet
servlet.service(request, response);
}
}
这样实现是为了在一个doFilter()方法中,支持双向拦截,既能拦截客户端发来的请求,也能拦截发给客户端的响应。
spring Interceptor
拦截器,也是用来实现对http请求进行拦截处理。不同之处是,servlet filter是servlet规范的一部分,实现依赖于web容器。spring interceptor是spring mvc框架的一部分,由spring mvc框架来提供实现。客户端的请求,先经过servlet filter,再经过spring interceptor,最后到达具体的业务代码。
如何使用呢?如下,LogInterceptor实现的功能跟刚才的LogFilter一样,只是实现方式不同。LogFilter对请求和响应的拦截在doFilter()实现,而LogInterceptor对请求的拦截在preHandle()中实现,对响应的拦截在postHandle()中实现。
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
System.out.println("拦截客户端发来的请求");
return true;//继续后续的处理
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("拦截发给客户端的响应");
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
System.out.println("这里总是被执行");
}
}
//spring mvc配置文件中的配置
<mvc:interceptors>
<mvc:interceptor>
<bean class="com.ai.doc.chain.filter.LogInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
spring Interceptor底层如何实现?基于责任链模式,HandlerExecutionChain类是责任链模式的处理器链。实现相较于tomcat的ApplicationFilterChain,逻辑更清晰,不用递归,因为将请求和响应的拦截,拆分到两个方法中实现。源码如下:
public class HandlerExecutionChain {
private final Object handler;
private HandlerInterceptor[] interceptors;
public void addInterceptor(HandlerInterceptor interceptor){
initInterceptorList().add(interceptor);
}
boolean applyPreHandler(HttpServletRequest request, HttpServletResponse response){
handlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)){
for (int i=0;i<interceptors.length;i++){
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request,response,this.handler)){
triggerAfterCompletion(request,response,null);
return false;
}
}
}
return true;
}
void applyPostHandle(HttpServletRequest request,HttpServletResponse response){
handlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)){
for (int i=interceptors.length-1;i>=0;i--){
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request,response,this.handler,mv);
}
}
}
void triggerAfterCompletion(HttpServletRequest request,HttpServletResponse response){
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)){
for (int i=interceptors.length-1;i>=0;i--){
HandlerInterceptor interceptor = interceptors[i];
try{
interceptor.afterCompletion(request,response,this.handler,ex);
}catch (Throwable ex2){
logger.error("HandlerInterceptor.afterCompletion throw exception",ex2);
}
}
}
}
}
在spring框架,DsipatcherServlet的doDispatch()方法分发请求,在真正的业务逻辑执行前后,执行HandlerExecutionChain的applyPreHandle()和applyPostHandle()方法,用来实现拦截的功能。具体代码很简单。