SpringAop自定義切面注解、自定義過濾器及ThreadLocal詳解
一、切面表達(dá)式
execution()是最常用的切點(diǎn)函數(shù),其語法如下所示:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
execution(<修飾符模式>? <返回類型模式><聲明類型模式><方法名模式>(<參數(shù)模式>) <異常模式>?) 除了返回類型模式、方法名模式和參數(shù)模式外,其它項(xiàng)都是可選的
返回類型模式確定方法的返回類型必須是什么,以便匹配連接點(diǎn)。*最常用作返回類型模式。它匹配任何返回類型。只有當(dāng)方法返回給定類型時(shí),完全限定的類型名稱才匹配。
參數(shù)模式稍微復(fù)雜一些:
()匹配一個(gè)不帶參數(shù)的方法,而(..)匹配任意數(shù)量(零個(gè)或更多)的參數(shù)。
(*)模式匹配采用任意類型的一個(gè)參數(shù)的方法。(*,String)匹配采用兩個(gè)參數(shù)的方法。第一個(gè)可以是任何類型,而第二個(gè)必須是字符串。
二、實(shí)戰(zhàn)代碼
Controller
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEventPublisher; import org.springframework.util.CustomizableThreadCreator; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @RestController @RequestMapping("/controller") public class TestController implements ApplicationContextAware { private static ApplicationContext applicationContext; @Resource private ApplicationEventPublisher applicationEventPublisher; @GetMapping("/test") public String test(@RequestParam String name){ System.out.println("TestController請求進(jìn)來了:"+Thread.currentThread().getName()); TestEvent event = new TestEvent(this,name); // String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); // for (int i = 0; i <beanDefinitionNames.length ; i++) { // System.out.println(beanDefinitionNames[i]); // } applicationEventPublisher.publishEvent(event); System.out.println("TestController請求出去了:"+Thread.currentThread().getName()); CustomizableThreadCreator bean = applicationContext.getBean(CustomizableThreadCreator.class); System.out.println("CustomizableThreadCreator:"+bean.getThreadNamePrefix()); return "success"; } @PostMapping("/test2") @LogAespect public String test2(@RequestBody TestRequest request){ System.out.println("TestController請求進(jìn)來了:"+Thread.currentThread().getName()); try { Thread.sleep(1000); System.out.println("hobby:"+request.getHobby()); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("TestController請求出去了:"+Thread.currentThread().getName()); return "success"; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { TestController.applicationContext=applicationContext; } }
定義切面
常用的兩種:一種是基于表達(dá)式,另一種是基于注解的。
注解十分靈活,所以編程中使用的較多。在你需要攔截的方法上面加上自定義注解@LogAespect即可。
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; @Aspect @Component public class TestAspect { @Pointcut("execution(public * com.test.realname.controller.TestController.test(..))") public void pointCut(){ //這里一般無實(shí)際意義 } @Before("pointCut()") public void before() { System.out.println("===before===="); } @AfterReturning("pointCut()") public void afterReturning() { System.out.println("===afterReturning==="); } @Around("@annotation(com.test.realname.controller.LogAespect)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { long l = System.currentTimeMillis(); HttpServletRequest request = MyFilter.CURRENT_REQUEST.get(); String requestURI = request.getRequestURI(); Object proceed = joinPoint.proceed(); String s=null; if(proceed instanceof String){ s = (String) proceed; } System.out.println("請求路徑"+requestURI); System.out.println("響應(yīng)"+s); System.out.println("cost time"+(System.currentTimeMillis()-l)); return proceed; } }
定義切面注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface LogAespect { }
request
import java.io.Serializable; public class TestRequest implements Serializable { private String hobby; private String streamNo; public String getHobby() { return hobby; } public void setHobby(String hobby) { this.hobby = hobby; } public String getStreamNo() { return streamNo; } public void setStreamNo(String streamNo) { this.streamNo = streamNo; } @Override public String toString() { return "TestRequest{" + "hobby='" + hobby + '\'' + ", streamNo='" + streamNo + '\'' + '}'; } }
request請求中的RequestBody只能讀取一次,因?yàn)镽equestBody是流ServletInputStream,只能讀取一次,所以需要對request請求進(jìn)行包裝,使其能多次重復(fù)讀取。
import org.springframework.util.StreamUtils; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; public class HttpRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public HttpRequestWrapper(HttpServletRequest request) throws IOException { super(request); body= StreamUtils.copyToByteArray(request.getInputStream()); } @Override public BufferedReader getReader() throws IOException{ return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException{ final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return byteArrayInputStream.read(); } }; } }
自定義過濾器,將request緩存到ThreadLocal中,方便在切面中使用。
ThreadLocal記得在finally中清空,防止內(nèi)存泄漏。
import com.alibaba.fastjson.JSON; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import org.springframework.util.StreamUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Map; @Component public class MyFilter extends OncePerRequestFilter { public static final ThreadLocal<HttpServletRequest> CURRENT_REQUEST = new ThreadLocal<>(); public static boolean isJsonRequest(HttpServletRequest request){ if(request==null){ return false; } return StringUtils.contains(request.getContentType(),"json"); } @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { boolean jsonRequest = isJsonRequest(httpServletRequest); try { if(jsonRequest){ HttpRequestWrapper wrapper = new HttpRequestWrapper(httpServletRequest); CURRENT_REQUEST.set(wrapper); String s = StreamUtils.copyToString(wrapper.getInputStream(), StandardCharsets.UTF_8); Map map = JSON.parseObject(s, Map.class); System.out.println(map); filterChain.doFilter(wrapper,httpServletResponse); }else { CURRENT_REQUEST.set(httpServletRequest); filterChain.doFilter(httpServletRequest,httpServletResponse); } }finally { CURRENT_REQUEST.remove(); } } }
正常返回
{hobby=ball}
TestController請求進(jìn)來了:http-nio-8080-exec-1
hobby:ball
TestController請求出去了:http-nio-8080-exec-1
請求路徑/controller/test2
響應(yīng)success
cost time1009
如果取消包裝的話,就會(huì)直接報(bào)錯(cuò)
{hobby=ball} 2023-01-03 15:45:31.664 WARN 13596 — [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public java.lang.String com.test.realname.controller.TestController.test2(com.test.realname.controller.TestRequest)]
到此這篇關(guān)于SpringAop自定義切面注解、自定義過濾器及ThreadLocal詳解的文章就介紹到這了,更多相關(guān)SpringAop自定義切面注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Quarkus篇入門創(chuàng)建項(xiàng)目搭建debug環(huán)境
這篇文章主要為大家介紹了Quarkus篇入門創(chuàng)建項(xiàng)目搭建debug環(huán)境,先來一套hello?world,來搭建基本的運(yùn)行及調(diào)試環(huán)境吧2022-02-02Spring Boot集成Redis實(shí)戰(zhàn)操作功能
這篇文章主要介紹了Spring Boot集成Redis實(shí)戰(zhàn)操作,包括如何集成redis以及redis的一些優(yōu)點(diǎn),本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-11-11多模塊maven的deploy集成gitlab?ci自動(dòng)發(fā)版配置
這篇文章主要為大家介紹了多模塊maven項(xiàng)目deploy集成gitlab?ci自動(dòng)發(fā)版的配置流程步驟,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-02-02Java中的MapStruct實(shí)現(xiàn)詳解
這篇文章主要介紹了Java中的MapStruct實(shí)現(xiàn)詳解,MapStruct 是一個(gè)代碼生成器,它基于約定優(yōu)先于配置的方法大大簡化了 JavaBean 類型之間映射的實(shí)現(xiàn),生成的映射代碼使用普通方法調(diào)用,需要的朋友可以參考下2023-11-11Spring 自動(dòng)裝配的二義性實(shí)例解析
這篇文章主要介紹了Spring 自動(dòng)裝配的二義性實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11Struts 2 數(shù)據(jù)校驗(yàn)功能及校驗(yàn)問題的解決方案
這篇文章主要介紹了Struts 2 數(shù)據(jù)校驗(yàn)功能及校驗(yàn)問題的解決方案的相關(guān)資料,需要的朋友可以參考下2016-09-09