系列文章中的大章節(jié)編號為代碼模塊號
一. 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
異步點贊
為什么異步
點贊,回復(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方法积糯。
- 事先在 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"/>
修改在原來注入spirng容器中的bean的方法
修改在原來注入spirng容器中的bean的方法:在域變量上加上標(biāo)簽@Autowired,并且去掉 相應(yīng)的get 和set方法<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
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)類里的方法淋硝。
一些注釋
-
implements InitializingBean
,@Override afterPropertiesSet()
InitializingBean 通過實現(xiàn)此接口的afterPropertiesSet()方法記錄哪些Event需要哪些handler來處理 -
implements ApplicationContextAware
ApplicationContextAware 通過實現(xiàn)此接口的setApplicationContext方法獲取上下文 -
Map<EventType, List<EventHandler>> config = new HashMap<>();
用來存儲一個事件類型對應(yīng)的所有的eventhandler,下次有該事件產(chǎn)生時错维,即可直接調(diào)用對應(yīng)的list -
Map<String, EventHandler> beans = applicationContext.getBeansOfType(EventHandler.class);
找出上下文中所有實現(xiàn)了EventHandler接口的類奖地,存入beans -
if (beans != null)……
遍歷所有的handler,將他們存入他們所監(jiān)聽的eventType對應(yīng)的list中 -
List<String> messages = jedisAdapter.brpop(0, key);
從redis的隊列中取出事件赋焕,并存入list中 -
for (String message : messages)
遍歷取出的事件
7.1if (message.equals(key))
第一個元素是隊列名字参歹,跳過
7.2if (!config.containsKey(eventModel.getType()))
跳過不能處理的事件
7.3for (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
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聲明的一個內(nèi)部接口急前,此接口為泛型,定義為Entry<K,V>瀑构。它表示Map中的一個實體(一個key-value對)裆针。接口中有g(shù)etKey(),getValue方法。
3)Fastjson
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
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處理請求的整個過程是
- 先根據(jù)請求找到對應(yīng)的
HandlerExecutionChain
时呀,它包含了處理請求的handler和所有的HandlerInterceptor攔截器 - 然后在調(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();
}
}