一. AOP仰迁,攔截器甸昏,異步隊列

系列文章中的大章節(jié)編號為代碼模塊號

結(jié)構(gòu)

一. aspect

1.1 LogAspect

@Aspect
@Component
public class LogAspect {
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    @Before("execution(* com.nowcoder.controller.*Controller.*(..))")
    public void beforeMethod(JoinPoint joinPoint) {
        StringBuilder sb = new StringBuilder();
        for (Object arg : joinPoint.getArgs()) {
            sb.append("arg:" + arg.toString() + "|");
        }
        logger.info("before method: " + sb.toString());
    }

    @After("execution(* com.nowcoder.controller.IndexController.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        logger.info("after method: ");
    }
}

1) @Component

使用@Component標(biāo)記為IOC容器中的組件

2) Logger LOG = LoggerFactory.getLogger()

用Logger工廠獲取Logger實例

3) JoinPoint 對象

JoinPoint對象封裝了SpringAop中切面方法的信息,在切面方法中添加JoinPoint參數(shù),就可以獲取到封裝了該方法信息的JoinPoint對象.
常用api:

方法名 功能
Signature getSignature() 獲取封裝了署名信息的對象,在該對象中可以獲取到目標(biāo)方法名,所屬類的Class等信息
Object[] getArgs() 獲取傳入目標(biāo)方法的參數(shù)對象
Object getTarget() 獲取被代理的對象
Object getThis() 獲取代理對象

2. async

async

異步點贊

為什么異步

點贊,回復(fù)評論的時候徐许,表面上是贊數(shù)增加了施蜜,其實還有很多其他的工作要做。比如雌隅,對方要收到消息提醒翻默,成就值增加。一些行為會引起一系列連鎖反應(yīng)恰起。如果在點贊時立馬處理修械,會影響程序運(yùn)行效率。而且會造成代碼冗余检盼,比如發(fā)布新聞肯污,和回復(fù)評論都可以使得成就值增加,如果都跟著寫在后面的代碼里會把成就值增加這段代碼寫兩遍,所以大型服務(wù)需要服務(wù)化和異步化蹦渣。

  • 服務(wù)化
    服務(wù)化:某一個單獨的業(yè)務(wù)獨立成一個工程哄芜,提供接口。不只是service層的一個類柬唯。
    暴露一些接口认臊,比如數(shù)據(jù)庫服務(wù),如果一個部門要去數(shù)據(jù)庫查詢锄奢,小公司可能寫個SQL語句失晴。對于大公司,需要一個組單獨做數(shù)據(jù)庫服務(wù)斟薇,暴露接口給別的部門用师坎。好處是防止別的部門有數(shù)據(jù)庫權(quán)限,數(shù)據(jù)庫單獨有一個組維護(hù)堪滨,出問題找他們運(yùn)維就好胯陋。
  • 異步化
    異步化:用戶點贊,用戶首先要知道的是這個贊已經(jīng)點上了袱箱。用戶提交Oj遏乔,用戶要立馬知道的是代碼有沒有通過。而后面應(yīng)該的東西发笔,比如積分增加了盟萨,用戶不會有立馬想知道的需求,如果間隔幾秒鐘在更新了讨,用戶也不會有很大的意見捻激。

概述

在一個網(wǎng)站中,一個業(yè)務(wù)發(fā)生的同時前计,還有一些后續(xù)業(yè)務(wù)需要發(fā)生胞谭。

比如點贊,除了完成點贊功能外男杈,還有一系列丈屹,比如提醒被點贊的用戶等等,為了能夠?qū)崿F(xiàn)這些操作并且不拖慢單純點贊功能的實現(xiàn)伶棒,我們將這些使用異步隊列實現(xiàn)旺垒。

處理流程如下圖:

處理流程
  • Biz(生產(chǎn))
    Biz為業(yè)務(wù)部門,理解為點贊的實現(xiàn)肤无,也就是在實現(xiàn)點贊的同時通過EventProducer發(fā)送一個事件
  • 進(jìn)入隊列
    這個事件進(jìn)入隊列等待先蒋,隊列另一頭,有一個EventConsumer舅锄,不斷消費(fèi)事件
  • EventHandler(消費(fèi))
    EventConsumer下面有很多EventHandler鞭达,只要EventHandler發(fā)現(xiàn)自己需要處理的事件類型司忱,就會進(jìn)行相應(yīng)的操作皇忿。

優(yōu)點:①后續(xù)業(yè)務(wù)的實現(xiàn)畴蹭,不會拖慢主業(yè)務(wù)。②如果后續(xù)業(yè)務(wù)的服務(wù)器掛掉鳍烁,只要重啟叨襟,繼續(xù)從優(yōu)先隊列消費(fèi)事件即可。

2.1 LikeHandler

記得開啟Rrdis---redis-server.exe redis.windows.conf

具體的執(zhí)行動作幔荒,具體的實現(xiàn)類糊闽,這個是點贊后要執(zhí)行的行為,給別人發(fā)提醒爹梁。

@Component
public class LikeHandler implements EventHandler {
    @Autowired
    MessageService messageService;

    @Autowired
    UserService userService;

    @Override
    public void doHandle(EventModel model) {
        Message message = new Message();
        message.setFromId(3);
        //message.setToId(model.getEntityOwnerId());
        message.setToId(model.getActorId());
        User user = userService.getUser(model.getActorId());
        message.setContent("用戶" + user.getName()
                + "贊了你的資訊,http://127.0.0.1:8080/news/" + model.getEntityId());
        message.setCreatedDate(new Date());
        messageService.addMessage(message);
    }

    @Override
    public List<EventType> getSupportEventTypes() {
        return Arrays.asList(EventType.LIKE);
    }
}

1)參考 Spring@Autowired注解與自動裝

Spring 2.5 引入了 @Autowired 注釋右犹,它可以對類成員變量、方法及構(gòu)造函數(shù)進(jìn)行標(biāo)注姚垃,完成自動裝配的工作念链。 通過 @Autowired的使用來消除 set ,get方法积糯。

  1. 事先在 Spring 容器中聲明
    Spring 通過一個 BeanPostProcessor 對 @Autowired 進(jìn)行解析掂墓,所以要讓 @Autowired 起作用必須事先在 Spring 容器中聲明 AutowiredAnnotationBeanPostProcessor Bean。
<!-- 該 BeanPostProcessor 將自動對標(biāo)注 @Autowired 的 Bean 進(jìn)行注入 -->     
  <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/> 
  1. 修改在原來注入spirng容器中的bean的方法
    修改在原來注入spirng容器中的bean的方法:在域變量上加上標(biāo)簽@Autowired,并且去掉 相應(yīng)的get 和set方法

  2. <porpery >也去掉
    在applicatonContext.xml中 把原來 引用的<porpery >標(biāo)簽也去掉看成。

2) Date

java.util 包提供了 Date 類來封裝當(dāng)前的日期和時間君编。 Date 類提供兩個構(gòu)造函數(shù)來實例化 Date 對象。

  • 第一個構(gòu)造函數(shù)使用當(dāng)前日期和時間來初始化對象川慌。
    Date( )
  • 第二個構(gòu)造函數(shù)接收一個參數(shù)吃嘿,該參數(shù)是從1970年1月1日起的毫秒數(shù)。
    Date(long millisec)
方法 作用
boolean after(Date date) 若當(dāng)調(diào)用此方法的Date對象在指定日期之后返回true,否則返回false
boolean before(Date date) 若當(dāng)調(diào)用此方法的Date對象在指定日期之前返回true,否則返回false梦重。
long getTime( ) 返回自 1970 年 1 月 1 日 00:00:00 GMT 以來此 Date 對象表示的毫秒數(shù)兑燥。
String toString( ) 把此 Date 對象轉(zhuǎn)換為以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat)。

3)Arrays.asList

Arrays.asList使用指南

List 是一種很有用的數(shù)據(jù)結(jié)構(gòu)忍饰,如果需要將一個數(shù)組轉(zhuǎn)換為 List 以便進(jìn)行更豐富的操作的話贪嫂,可以這么實現(xiàn):

String[] myArray = { "Apple", "Banana", "Orange" }; 
List<String> myList = Arrays.asList(myArray);

或者

List<String> myList = Arrays.asList("Apple", "Orange");

上面這兩種形式都是十分常見的:將需要轉(zhuǎn)化的數(shù)組作為參數(shù)艾蓝,或者直接把數(shù)組元素作為參數(shù)力崇,都可以實現(xiàn)轉(zhuǎn)換。

2.2 LoginExceptionHandler

@Component
public class LoginExceptionHandler implements EventHandler {
    @Autowired
    MessageService messageService;

    @Autowired
    MailSender mailSender;

    @Override
    public void doHandle(EventModel model) {
        // 判斷是否有異常登陸
        Message message = new Message();
        message.setToId(model.getActorId());
        message.setContent("你上次的登陸ip異常");
        message.setFromId(3);
        message.setCreatedDate(new Date());
        messageService.addMessage(message);
    //郵件發(fā)送三
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("username", model.getExt("username"));
        mailSender.sendWithHTMLTemplate(model.getExt("email"), "登陸異常", "mails/welcome.html",
                map);
    }

    @Override
    public List<EventType> getSupportEventTypes() {
        return Arrays.asList(EventType.LOGIN);
    }
}

2.3 EventConsumer

解決的問題

如何將活動分發(fā)下去給相關(guān)的所有handle實現(xiàn)赢织。

步驟

消費(fèi)活動亮靴,在初始化前,先得到Handler接口所有的實現(xiàn)類于置,遍歷實現(xiàn)類茧吊。
通過getSupportEventType得到每個實現(xiàn)類對應(yīng)處理的活動類型。反過來記錄在config哈希表中,config中的key是活動的類型搓侄,比如說是LIKE瞄桨,COMMENT,是枚舉里的成員讶踪,value是一個ArrayList的數(shù)組芯侥,里面存放的是各種實現(xiàn)方法。見代碼中的乳讥。當(dāng)從隊列中獲得一個活動時柱查,這里用的是從右向外pop()一個活動實體。進(jìn)行解析云石。這里的config.get(eventModel.getType())是一個數(shù)組唉工,里面存放著所有關(guān)于這個活動要執(zhí)行的實現(xiàn)類。遍歷這個數(shù)組汹忠,開始執(zhí)行實現(xiàn)類里的方法淋硝。

一些注釋

  1. implements InitializingBean , @Override afterPropertiesSet()
    InitializingBean 通過實現(xiàn)此接口的afterPropertiesSet()方法記錄哪些Event需要哪些handler來處理
  2. implements ApplicationContextAware
    ApplicationContextAware 通過實現(xiàn)此接口的setApplicationContext方法獲取上下文
  3. Map<EventType, List<EventHandler>> config = new HashMap<>();
    用來存儲一個事件類型對應(yīng)的所有的eventhandler,下次有該事件產(chǎn)生時错维,即可直接調(diào)用對應(yīng)的list
  4. Map<String, EventHandler> beans = applicationContext.getBeansOfType(EventHandler.class);
    找出上下文中所有實現(xiàn)了EventHandler接口的類奖地,存入beans
  5. if (beans != null)……
    遍歷所有的handler,將他們存入他們所監(jiān)聽的eventType對應(yīng)的list中
  6. List<String> messages = jedisAdapter.brpop(0, key);
    從redis的隊列中取出事件赋焕,并存入list中
  7. for (String message : messages)
    遍歷取出的事件
    7.1 if (message.equals(key))
    第一個元素是隊列名字参歹,跳過
    7.2 if (!config.containsKey(eventModel.getType()))
    跳過不能處理的事件
    7.3 for (EventHandler handler : config.get(eventModel.getType()))
    處理他的所有的handler
@Service
//1------------------------
public class EventConsumer implements InitializingBean, ApplicationContextAware {

    private static final Logger logger = LoggerFactory.getLogger(EventConsumer.class);
    //2--------------
    private Map<EventType, List<EventHandler>> config = new HashMap<>();
    private ApplicationContext applicationContext;

    @Autowired
    private JedisAdapter jedisAdapter;

    @Override
    public void afterPropertiesSet() throws Exception {
        //4----------------------
        Map<String, EventHandler> beans = applicationContext.getBeansOfType(EventHandler.class);
        //5-----------------------------
        if (beans != null) {
            for (Map.Entry<String, EventHandler> entry : beans.entrySet()) {
                List<EventType> eventTypes = entry.getValue().getSupportEventTypes();//查看事件的監(jiān)視事件
                for (EventType type : eventTypes) {
                    if (!config.containsKey(type)) {
                        config.put(type, new ArrayList<EventHandler>());
                        System.out.println("添加一個新的 :" + type);//20180802
                    }
                    config.get(type).add(entry.getValue());// 注冊每個事件的處理函數(shù)
                    System.out.println("注冊每個事件的處理函數(shù) :" + type + " " + entry.getValue()); //20180802
                }
            }
        }

        Thread thread = new Thread(new Runnable() {// 啟動線程去消費(fèi)事件
            @Override
            public void run() {
                while (true) {// 從隊列一直消費(fèi)
                    String key = RedisKeyUtil.getEventQueueKey();
                    //6------------------------------
                    List<String> messages = jedisAdapter.brpop(0, key);
                    
                    for (String message : messages) {
                        //7.1---------------
                        if (message.equals(key)) {
                            continue;
                        }
                        EventModel eventModel = JSON.parseObject(message, EventModel.class);
                        //7.2---------------
                        System.out.println("找到這個事件的處理handler列表 : " + eventModel.getType()) //20180802
                        if (!config.containsKey(eventModel.getType())) { //找到這個事件的處理handler列表
                            logger.error("不能識別的事件");
                            continue;
                        }
                        //7.3---------------
                        for (EventHandler handler : config.get(eventModel.getType())) {//處理他的所有的handler
                            System.out.println("處理事件 : " + eventModel.getType() + " " + handler.getClass() );//20180802
                            handler.doHandle(eventModel);
                        }
                    }
                }
            }
        });
        thread.start();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

1)Jedis

Jedis

BLPOP key [key ...] timeout

BLPOP 是列表的阻塞式(blocking)彈出原語。
它是 LPOP 命令的阻塞版本隆判,當(dāng)給定列表內(nèi)沒有任何元素可供彈出的時候犬庇,連接將被 BLPOP 命令阻塞,直到等待超時或發(fā)現(xiàn)可彈出元素為止侨嘀。
當(dāng)給定多個 key 參數(shù)時臭挽,按參數(shù) key 的先后順序依次檢查各個列表,彈出第一個非空列表的頭元素咬腕。
非阻塞行為:
當(dāng) BLPOP 被調(diào)用時欢峰,如果給定 key 內(nèi)至少有一個非空列表,那么彈出遇到的第一個非空列表的頭元素涨共,并和被彈出元素所屬的列表的名字一起纽帖,組成結(jié)果返回給調(diào)用者。
當(dāng)存在多個給定 key 時举反, BLPOP 按給定 key 參數(shù)排列的先后順序懊直,依次檢查各個列表。
假設(shè)現(xiàn)在有 job 火鼻、 command 和 request 三個列表室囊,其中 job 不存在雕崩, command 和 request 都持有非空列表∪谧玻考慮以下命令:BLPOP job command request 0

超時參數(shù) timeout 接受一個以秒為單位的數(shù)字作為值盼铁。超時參數(shù)設(shè)為 0 表示阻塞時間可以無限期延長(block indefinitely) 。

BRPOP key [key ...] timeout

它是 RPOP 命令的阻塞版本懦铺,當(dāng)給定列表內(nèi)沒有任何元素可供彈出的時候捉貌,連接將被 BRPOP 命令阻塞支鸡,直到等待超時或發(fā)現(xiàn)可彈出元素為止冬念。
BRPOP 除了彈出元素的位置和 BLPOP 不同之外,其他表現(xiàn)一致牧挣。

2)Map.Entry

Map.Entry使用詳解

Map.Entry是Map聲明的一個內(nèi)部接口急前,此接口為泛型,定義為Entry<K,V>瀑构。它表示Map中的一個實體(一個key-value對)裆针。接口中有g(shù)etKey(),getValue方法。

3)Fastjson

Fastjson API

2.4 EventHandler

設(shè)計為一個接口寺晌,handler都實現(xiàn)此接口世吨。

public interface EventHandler {
    void doHandle(EventModel model);//處理此事件
    List<EventType> getSupportEventTypes();//添加監(jiān)視的事件類型
}

2.5 EventModel

即發(fā)送的隊列的事件模型,只有一些基本屬性和get呻征、set方法耘婚。

其中一些set的return 設(shè)置為this,是因為方便連續(xù)set多個屬性陆赋。

public class EventModel {
    private EventType type;
    private int actorId;
    private int entityType;
    private int entityId;
    private int entityOwnerId;
    private Map<String, String> exts = new HashMap<String, String>();

    public EventModel(EventType type) {
        this.type = type;
    }
    public EventModel() {
    }

    public String getExt(String key) {
        return exts.get(key);
    }
    public EventModel setExt(String key, String value) {
        exts.put(key, value);
        return this;//方便連續(xù)set多個屬性沐祷。
    }

    public EventType getType() {
        return type;
    }
    public EventModel setType(EventType type) {
        this.type = type;
        return this;
    }

    public int getActorId() {
        return actorId;
    }
    public EventModel setActorId(int actorId) {
        this.actorId = actorId;
        return this;
    }

    public int getEntityType() {
        return entityType;
    }
    public EventModel setEntityType(int entityType) {
        this.entityType = entityType;
        return this;
    }

    public int getEntityId() {
        return entityId;
    }
    public EventModel setEntityId(int entityId) {
        this.entityId = entityId;
        return this;
    }

    public int getEntityOwnerId() {
        return entityOwnerId;
    }
    public EventModel setEntityOwnerId(int entityOwnerId) {
        this.entityOwnerId = entityOwnerId;
        return this;
    }

    public Map<String, String> getExts() {
        return exts;
    }
    public void setExts(Map<String, String> exts) {
        this.exts = exts;
    }
}

2.6 EventProducer

活動生產(chǎn)者,相當(dāng)于生產(chǎn)消費(fèi)者中的生產(chǎn)者攒岛,在controller層執(zhí)行一個動作后赖临,用這個類把需要異步的信息打包好,放進(jìn)Redis的隊列中灾锯。放入是把EventModel序列化為JSON兢榨,存入Redis的列表中。

@Service
public class EventProducer {
    @Autowired
    JedisAdapter jedisAdapter;

    public boolean fireEvent(EventModel model) {
        try {
            String json = JSONObject.toJSONString(model);
            String key = RedisKeyUtil.getEventQueueKey();
            jedisAdapter.lpush(key, json);
            System.out.println("產(chǎn)生一個異步事件:" + eventModel.getType());//20180802
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

1) JSON

由于 JSON 語法是 JavaScript 語法的子集顺饮,JavaScript 函數(shù) eval() 可用于將 JSON 文本轉(zhuǎn)換為 JavaScript 對象吵聪。

概覽

  • JSON(JavaScript Object Notation)是一種輕量級的數(shù)據(jù)交換格式。
  • JSON 是存儲和交換文本信息的語法领突。類似 XML暖璧。
  • JSON 比 XML 更小、更快君旦,更易解析澎办。

為什么使用 JSON嘲碱?

對于 AJAX 應(yīng)用程序來說,JSON 比 XML 更快更易使用:

使用 XML
  • 讀取 XML 文檔
  • 使用 XML DOM 來循環(huán)遍歷文檔
  • 讀取值并存儲在變量中
使用 JSON
  • 讀取 JSON 字符串
  • eval() 處理 JSON 字符串

書寫格式

JSON 數(shù)據(jù)的書寫格式是:名稱/值對局蚀。
名稱/值對包括字段名稱(在雙引號中)麦锯,后面寫一個冒號,然后是值:
“firstName” : “John”
這很容易理解琅绅,等價于這條 JavaScript 語句:
firstName = “John”

json嵌套

對于json嵌套扶欣,只要記住

  • 符號”前是鍵,符號后是值
  • 大括號成對找

一層層剝開千扶,就清楚了料祠。

2)前后臺的傳輸

  • JSON.parseObject,是將Json字符串轉(zhuǎn)化為相應(yīng)的對象
  • JSON.toJSONString則是將對象轉(zhuǎn)化為Json字符串澎羞。

2.7 EventType

EventType 是獲得活動的類型髓绽,可以有點贊,評論妆绞,登陸等待

public enum EventType {
    LIKE(0),
    COMMENT(1),
    LOGIN(2),
    MAIL(3);

    private int value;

    EventType(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

3. configuration

3.1. ToutiaoWebConfiguration

@Component
public class ToutiaoWebConfiguration extends WebMvcConfigurerAdapter {
    @Autowired
    PassportInterceptor passportInterceptor;

    @Autowired
    LoginRequiredInterceptor loginRequiredInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(passportInterceptor);
        registry.addInterceptor(loginRequiredInterceptor).
                addPathPatterns("/msg/*").addPathPatterns("/like").addPathPatterns("/dislike");
        super.addInterceptors(registry);
    }
}

1)spring boot中使用攔截器

1顺呕、注冊攔截器

創(chuàng)建一個類MyWebConfig繼承WebMvcConfigurerAdapter,并重寫addInterceptors方法

多個攔截器組成一個攔截器鏈

  • addPathPatterns
    添加攔截規(guī)則
  • excludePathPatterns
    排除攔截
@Configuration
public class MyWebConfig extends WebMvcConfigurerAdapter {
    @Autowired
    MyiInterceptor myiInterceptor;

    @Override    //注冊 攔截器
    public void addInterceptors(InterceptorRegistry registry) {
//攔截器myiInterceptor只攔截'/111'的請求括饶,不攔截'/helloWorld'      
        registry.addInterceptor(myiInterceptor).addPathPatterns("/111")
                          .excludePathPatterns("/helloWorld");
        super.addInterceptors(registry);
    }
}

2株茶、自定義攔截器

創(chuàng)建一個自定義攔截器MyiInterceptor實現(xiàn)HandlerInterceptor接口重寫所有的方法實現(xiàn)自己的業(yè)務(wù)

6. interceptor

interceptor

6.1 LoginRequiredInterceptor

@Component
public class LoginRequiredInterceptor implements HandlerInterceptor {

    @Autowired
    private HostHolder hostHolder;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        if (hostHolder.getUser() == null) {
            httpServletResponse.sendRedirect("/?pop=1");
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
    }
}

1)攔截器(Interceptor)

作用

在Web開發(fā)中图焰,攔截器(Interceptor)可以用來驗證是否登錄启盛、預(yù)先設(shè)置數(shù)據(jù)以及統(tǒng)計方法的執(zhí)行效率等。

分類

Spring中的攔截器分兩種楞泼,一是HandlerInterceptor驰徊,另一個是MethodInterceptor。這里主要說以下HandlerInterceptor堕阔。

HandlerInterceptor是SpringMVC項目中的攔截器棍厂,攔截目標(biāo)是請求的地址,比MethodInterceptor先執(zhí)行超陆。實現(xiàn)一個HandlerInterceptor攔截器可以直接實現(xiàn)該接口牺弹,也可以繼承HandlerInterceptorAdapter類。

SpringMVC處理請求過程

SpringMVC處理請求的整個過程是

  1. 先根據(jù)請求找到對應(yīng)的HandlerExecutionChain时呀,它包含了處理請求的handler和所有的HandlerInterceptor攔截器
  2. 然后在調(diào)用hander之前分別調(diào)用每個HandlerInterceptor攔截器preHandle方法
    2.1 若有一個攔截器返回false张漂,則會調(diào)用triggerAfterCompletion方法,并且立即返回不再往下執(zhí)行
    2.2 若所有的攔截器全部返回true并且沒有出現(xiàn)異常谨娜,則調(diào)用handler返回ModelAndView對象航攒;再然后分別調(diào)用每個攔截器的postHandle方法;最后趴梢,即使是之前的步驟拋出了異常漠畜,也會執(zhí)行triggerAfterCompletion方法币他。

多攔截器工作流程:

多攔截器工作流程

需要Override的三種方法

(1 )preHandle (HttpServletRequest request, HttpServletResponse response, Object handle)
controller 執(zhí)行之前調(diào)用
該方法將在請求處理之前進(jìn)行調(diào)用。SpringMVC 中的Interceptor 是鏈?zhǔn)秸{(diào)用的憔狞,在一個請求中可以同時存在多個Interceptor 蝴悉。每個Interceptor 的調(diào)用會依據(jù)它的聲明順序依次執(zhí)行,而且最先執(zhí)行的都是Interceptor 中的preHandle 方法瘾敢,所以可以在這個方法中進(jìn)行一些前置初始化操作或者是對當(dāng)前請求的一個預(yù)處理拍冠,也可以在這個方法中進(jìn)行一些判斷來決定請求是否要繼續(xù)進(jìn)行下去。該方法的返回值是布爾值Boolean 類型的簇抵,當(dāng)它返回為false 時庆杜,表示請求結(jié)束,后續(xù)的Interceptor 和Controller 都不會再執(zhí)行正压;當(dāng)返回值為true 時就會繼續(xù)調(diào)用下一個Interceptor 的preHandle 方法欣福,如果已經(jīng)是最后一個Interceptor 的時候就會是調(diào)用當(dāng)前請求的Controller 方法。
(2 )postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView)
controller 執(zhí)行之后焦履,且頁面渲染之前調(diào)用
這個方法包括后面要說到的afterCompletion 方法都只能是在當(dāng)前所屬的Interceptor 的preHandle 方法的返回值為true 時才能被調(diào)用。postHandle 方法雏逾,是在當(dāng)前請求進(jìn)行處理之后嘉裤,也就是Controller 方法調(diào)用之后執(zhí)行,但是它會在DispatcherServlet 進(jìn)行視圖渲染之前被調(diào)用栖博,所以我們可以在這個方法中對Controller 處理之后的ModelAndView 對象進(jìn)行操作屑宠。postHandle 方法被調(diào)用的方向跟preHandle 是相反的,也就是說先聲明的Interceptor 的postHandle 方法反而會后執(zhí)行仇让。
(3 )afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex)
頁面渲染之后調(diào)用典奉,一般用于資源清理操作。
該方法也是需要當(dāng)前對應(yīng)的Interceptor 的preHandle 方法的返回值為true 時才會執(zhí)行丧叽。該方法將在整個請求結(jié)束之后卫玖,也就是在DispatcherServlet 渲染了對應(yīng)的視圖之后執(zhí)行。這個方法的主要作用是用于進(jìn)行資源清理工作的踊淳。

在Spring Boot中配置攔截器假瞬,需要寫一個配置類繼承WebMvcConfigurerAdapter類并添加該攔截器(見2)

@Component
public class XdtoutiaoWebConfiguration extends WebMvcConfigurerAdapter {
    @Autowired
    PassportInterceptor passportInterceptor;
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(passportInterceptor);
        super.addInterceptors(registry);
    }
}

6.2 PassportInterceptor

@Component
public class PassportInterceptor implements HandlerInterceptor {

    @Autowired
    private LoginTicketDAO loginTicketDAO;

    @Autowired
    private UserDAO userDAO;

    @Autowired
    private HostHolder hostHolder;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        String ticket = null;
        if (httpServletRequest.getCookies() != null) {
            for (Cookie cookie : httpServletRequest.getCookies()) {
                if (cookie.getName().equals("ticket")) {
                    ticket = cookie.getValue();
                    break;
                }
            }
        }

        if (ticket != null) {
            LoginTicket loginTicket = loginTicketDAO.selectByTicket(ticket);
            if (loginTicket == null || loginTicket.getExpired().before(new Date()) || loginTicket.getStatus() != 0) {
                return true;
            }

            User user = userDAO.selectById(loginTicket.getUserId());
            hostHolder.setUser(user);
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        if (modelAndView != null && hostHolder.getUser() != null) {
            modelAndView.addObject("user", hostHolder.getUser());
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        hostHolder.clear();
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市迂尝,隨后出現(xiàn)的幾起案子脱茉,更是在濱河造成了極大的恐慌,老刑警劉巖垄开,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件琴许,死亡現(xiàn)場離奇詭異,居然都是意外死亡溉躲,警方通過查閱死者的電腦和手機(jī)榜田,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門寸认,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人串慰,你說我怎么就攤上這事偏塞。” “怎么了邦鲫?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵灸叼,是天一觀的道長。 經(jīng)常有香客問我庆捺,道長古今,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任滔以,我火速辦了婚禮捉腥,結(jié)果婚禮上冒晰,老公的妹妹穿的比我還像新娘此迅。我一直安慰自己,他們只是感情好贷盲,可當(dāng)我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布坏匪。 她就那樣靜靜地躺著拟逮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪适滓。 梳的紋絲不亂的頭發(fā)上敦迄,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天,我揣著相機(jī)與錄音凭迹,去河邊找鬼罚屋。 笑死,一個胖子當(dāng)著我的面吹牛嗅绸,可吹牛的內(nèi)容都是我干的脾猛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼朽砰,長吁一口氣:“原來是場噩夢啊……” “哼尖滚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瞧柔,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤漆弄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后造锅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撼唾,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年哥蔚,在試婚紗的時候發(fā)現(xiàn)自己被綠了倒谷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛛蒙。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖渤愁,靈堂內(nèi)的尸體忽然破棺而出牵祟,到底是詐尸還是另有隱情,我是刑警寧澤抖格,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布诺苹,位于F島的核電站,受9級特大地震影響雹拄,放射性物質(zhì)發(fā)生泄漏收奔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一滓玖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧势篡,春花似錦翩肌、人聲如沸殊霞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疤估。三九已至沈撞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間磷雇,已是汗流浹背尿庐。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工抄瑟, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人褪测。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓畏吓,卻偏偏與公主長得像菲饼,于是被迫代替她去往敵國和親汛兜。 傳聞我的和親對象是個殘疾皇子臼氨,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理损离,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 29,392評論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,103評論 1 32
  • 北京時間9月13日凌晨1點笑诅,全球電子科技業(yè)的年度盛事贬循,新一代蘋果產(chǎn)品發(fā)布會召開。蘋果公司終于正式發(fā)布了期待已久的i...
    一笑雜談閱讀 339評論 0 4
  • "你們以前遇到不會的選擇題開始亂猜答案的時候,會不會猶豫?不知道哪一個是正確答案矮冬,或許那個沒選的選項,沒填滿的四方...
    艾莉鯊閱讀 295評論 0 2