欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring?AOP簡(jiǎn)介及統(tǒng)一處理

 更新時(shí)間:2023年09月19日 16:53:01   作者:允歆辰丶  
AOP面向切面編程,它是一種思想,它是對(duì)某一類事情的集中處理,本文給大家介紹Spring?AOP簡(jiǎn)介及統(tǒng)一處理,感興趣的朋友跟隨小編一起看看吧

一.Spring AOP 

1.什么是Spring AOP 

AOP(Aspect Oriented Programming):面向切面編程,它是一種思想,它是對(duì)某一類事情的集中處理。

2.AOP的作用

想象一個(gè)場(chǎng)景,我們?cè)谧龊笈_(tái)系統(tǒng)時(shí),除了登錄和注冊(cè)等幾個(gè)功能不需要做用戶登錄驗(yàn)證之外,其他幾乎所有頁(yè)面調(diào)用的前端控制器( Controller)都需要先驗(yàn)證用戶登錄的狀態(tài),那這個(gè)時(shí)候我們要怎么處 理呢?我們之前的處理方式是每個(gè) Controller 都要寫一遍用戶登錄驗(yàn)證,然而當(dāng)你的功能越來(lái)越多,那么你要 寫的登錄驗(yàn)證也越來(lái)越多,而這些方法又是相同的,這么多的方法就會(huì)代碼修改和維護(hù)的成本。那有沒 有簡(jiǎn)單的處理方案呢?答案是有的,對(duì)于這種功能統(tǒng)一,且使用的地方較多的功能,就可以考慮 AOP 來(lái)統(tǒng)一處理了。

  • 除了統(tǒng)一的用戶登錄判斷之外,AOP 還可以實(shí)現(xiàn):
  • 統(tǒng)一日志記錄
  • 統(tǒng)一方法執(zhí)行時(shí)間統(tǒng)計(jì)
  • 統(tǒng)一的返回格式設(shè)置
  • 統(tǒng)一的異常處理
  • 事務(wù)的開啟和提交等
  • AOP是OOP的補(bǔ)充 

 3.AOP的相關(guān)概念

1.切面(Aspect)

面(Aspect)由切點(diǎn)(Pointcut)和通知(Advice)組成,它既包含了橫切邏輯的定義,也包

括了連接點(diǎn)的定義。

2.連接點(diǎn) (Join Point)

應(yīng)用執(zhí)行過(guò)程中能夠插入面的一個(gè)點(diǎn),這個(gè)點(diǎn)可以是方法調(diào)用時(shí),拋出異常時(shí),甚至修改字段 時(shí)。切面代碼可以利用這些點(diǎn)插入到應(yīng)用的正常流程之中,并添加新的行為

3.切點(diǎn)(Pointcut)

Pointcut 是匹配 Join Point 的謂詞。 Pointcut 的作用就是提供一組規(guī)則(使用 AspectJ pointcut expression language 來(lái)描述)來(lái) 匹配 Join Point,給滿足規(guī)則的 Join Point 添加 Advice

4.通知(Advice)

 切面也是有目標(biāo)的 ——它必須完成的工作。在 AOP 術(shù)語(yǔ)中,切面的工作被稱之為通知。

通知:定義了切面是什么,何時(shí)使用,其描述了面要完成的工作,還解決何時(shí)執(zhí)行這個(gè)工作的

問(wèn)題。

Spring 切面類中,可以在方法上使用以下注解,會(huì)設(shè)置方法為通知方法,在滿足條件后會(huì)通知本

方法進(jìn)行調(diào)用:

  • 前置通知使用 @Before:通知方法會(huì)在目標(biāo)方法調(diào)用之前執(zhí)行。
  • 后置通知使用 @After:通知方法會(huì)在目標(biāo)方法返回或者拋出異常后調(diào)用。
  • 返回之后通知使用 @AfterReturning:通知方法會(huì)在目標(biāo)方法返回后調(diào)用。
  • 拋異常后通知使用 @AfterThrowing:通知方法會(huì)在目標(biāo)方法拋出異常后調(diào)用。
  • 環(huán)繞通知使用 @Around:通知包裹了被通知的方法,在被通知的方法通知之前和調(diào)用之后執(zhí)
  • 行自定義的行為。

 4.Spring AOP的實(shí)現(xiàn)

1.添加 AOP 框架支持

在 pom.xml 中添加如下配置:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
<!--        Springboot test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
<!--      Spring AOP 框架-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

2.定義切面和切點(diǎn)

@Component
@Slf4j
@Aspect
public class LoginAspect {
    @Pointcut("execution(* com.javastudy.springaopdemo5.controller.LoginController.*(..))")
    public void pointcut() {
    }
}

@Aspect類注解表示LoginAspect是一個(gè)切面類,@Pointcut表示定義一個(gè)切點(diǎn),其中的內(nèi)容表示連接點(diǎn)的規(guī)則,也就是包括哪些類或者方法屬于這個(gè)切點(diǎn),連接點(diǎn).

其中 pointcut 方法為空方法,它不需要有方法體,此方法名就是起到?個(gè)“標(biāo)識(shí)”的作用,標(biāo)識(shí)下面的

通知方法具體指的是哪個(gè)切點(diǎn)(因?yàn)榍悬c(diǎn)可能有很多個(gè))

AspectJ 支持三種通配符

* :匹配任意字符,只匹配?個(gè)元素(包,類,或方法,方法參數(shù))

.. :匹配任意字符,可以匹配多個(gè)元素 ,在表示類時(shí),必須和 * 聯(lián)合使用。

+ :表示按照類型匹配指定類的所有類,必須跟在類名后面,如 com.cad.Car+ ,表示繼承該類的

所有子類包括本身

切點(diǎn)表達(dá)式由切點(diǎn)函數(shù)組成,其中 execution() 是最常用的切點(diǎn)函數(shù),用來(lái)匹配方法,語(yǔ)法為:

execution(<修飾符><返回類型><包.類.方法(參數(shù))><異常>)

修飾符和異??梢允÷?,具體含義如下:

上面我們定義的含義為,匹配com.javastudy.springaopdemo5.controller.LoginController類下所有的方法.

3.定義相關(guān)通知

先來(lái)定義controller層的內(nèi)容

@RestController
@Slf4j
@RequestMapping("/user")
public class LoginController {
    @RequestMapping("/login")
    public String login() {
        log.info("login...");
        return "login...";
    }
    @RequestMapping("/register")
    public String register() {
        log.info("register...");
        return "register...";
    }
    @RequestMapping("/get")
    public String get() {
        log.info("get...");
        return "get...";
    }
}

1.前置通知 @Before

通知方法會(huì)在目標(biāo)方法調(diào)用之前執(zhí)行。

注意:@Before里面的內(nèi)容表示切點(diǎn),即在哪些接口中執(zhí)行這些通知.

@Component
@Slf4j
@Aspect
public class LoginAspect {
    @Pointcut("execution(* com.javastudy.springaopdemo5.controller.LoginController.*(..))")
    public void pointcut() {
    }
    //前置通知
    @Before("pointcut()")
    public void doBefore() {
        log.info("do before....");
    }
}

 當(dāng)我們?cè)L問(wèn)任意一個(gè)頁(yè)面的時(shí)候,控制臺(tái)打印如下日志

可以看到都會(huì)先執(zhí)行doBefore通知.

2.后置通知 @After

通知方法會(huì)在目標(biāo)方法返回或者拋出異常后調(diào)用

    //后置通知
    @After("pointcut()")
    public void doAfter() {
        log.info("do after...");
    }

 模擬異常情況,可以在LoginController中某一個(gè)方法加10/0,觀察

    @RequestMapping("/login")
    public String login() {
        log.info("login...");
        int i=10/0;
        return "login...";
    }

可以觀察到在方法異常之后, @After通知仍然會(huì)執(zhí)行.

3.返回之后通知 @AfterReturning

通知方法會(huì)在目標(biāo)方法返回后調(diào)用。

    // return 之前通知
    @AfterReturning("pointcut()")
    public void doAfterReturning() {
        log.info("do after returning...");
    }

當(dāng)我們?cè)L問(wèn)login接口的時(shí)候(10/0,有異常),觀察是否有輸出

此時(shí)是沒有輸出的.

訪問(wèn)其它接口,沒有異常的接口

 此時(shí)@AfterReturning通知正常執(zhí)行

4.拋異常后通知 @AfterThrowing

通知方法會(huì)在目標(biāo)方法拋出異常后調(diào)用。

    //拋出異常之前通知
    @AfterThrowing("pointcut()")
    public void doAfterThrowing() {
        log.info("do after throwing");
    }

執(zhí)行有異常的接口,有日志的打印

執(zhí)行沒有異常的接口,沒有日志的打印 

5.環(huán)繞通知 @Around

通知包裹了被通知的方法,在被通知的方法通知之前和調(diào)用之后執(zhí)行自定義的行為。

    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        Object oj = null;
        log.info("環(huán)繞通知執(zhí)行之前...");
        log.info(joinPoint.getSignature().toLongString());
        try {
            oj = joinPoint.proceed();//調(diào)用目標(biāo)方法
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        log.info("環(huán)繞通知執(zhí)行之后....");
        return oj;
    }

執(zhí)行沒有異常的接口:

執(zhí)行有異常的接口: 

接下來(lái)我們看看ProceedingJoinPoint 常用方法

toString連接點(diǎn)所在位置的相關(guān)信息
toShortString連接點(diǎn)所在位置的簡(jiǎn)短相關(guān)信息
toLongString連接點(diǎn)所在位置的全部相關(guān)信息
getThis返回AOP代理對(duì)象,也就是com.sun.proxy.$Proxy18
getTarget返回目標(biāo)對(duì)象(定義方法的接口或類)
getArgs()返回被通知方法參數(shù)列表
getSignature返回當(dāng)前連接點(diǎn)簽名,其getName()方法返回方法的FQN

執(zhí)行所有的通知,觀察環(huán)繞通知和前置通知和后置通知的先后順序

 可以觀察到,環(huán)繞通知先于before,后于after.

如果一個(gè)切點(diǎn)只含有一個(gè)通知,那么我們可以將切點(diǎn)的規(guī)則放在通知上

@Component
@Slf4j
@Aspect
public class LoginAspect {
    //前置通知
    @Before("execution(* com.javastudy.springaopdemo5.controller.LoginController.*(..))")
    public void doBefore() {
        log.info("do before....");
    }
}

4.Spring AOP 實(shí)現(xiàn)原理

Spring AOP 是構(gòu)建在動(dòng)態(tài)代理基礎(chǔ)上,因此 Spring 對(duì) AOP 的支持局限于方法級(jí)別的攔截。

Spring AOP 支持 JDK Proxy 和 CGLIB 方式實(shí)現(xiàn)動(dòng)態(tài)代理。默認(rèn)情況下,實(shí)現(xiàn)了接口的類,使

用 AOP 會(huì)基于 JDK 生成代理類,沒有實(shí)現(xiàn)接口的類,會(huì)基于 CGLIB 生成代理類。

代理模式:

1.靜態(tài)代理

1.定義接口

public interface PayService {
    void pay();
}

2.實(shí)現(xiàn)接口

public class AliPayService implements PayService{
    @Override
    public void pay() {
        System.out.println("ali pay...");
    }
}

3.創(chuàng)建代理類, 并同樣實(shí)現(xiàn)支付接口

public class StaticProxy implements PayService {
    private final PayService payService;
    public StaticProxy(PayService payService) {
        this.payService = payService;
    }
    @Override
    public void pay() {
        System.out.println("before...");
        payService.pay();
        System.out.println("after...");
    }
}

4.實(shí)際使用

    public static void main(String[] args) {
        PayService service = new AliPayService();
        PayService proxy = new StaticProxy(service);
        proxy.pay();
    }

 靜態(tài)代理有個(gè)很大的缺點(diǎn),就是當(dāng)有很多不同的接口的時(shí)候,我們需要定義很多個(gè)代理類實(shí)現(xiàn)不同的接口,當(dāng)我們代理實(shí)現(xiàn)的功能相同的時(shí)候,但是有多個(gè)接口,此時(shí)完成這么多代理類很麻煩,此時(shí)需要我們的動(dòng)態(tài)代理.

2.動(dòng)態(tài)代理

1.JDK動(dòng)態(tài)代理

從 JVM 角度來(lái)說(shuō),動(dòng)態(tài)代理是在運(yùn)行時(shí)動(dòng)態(tài)生成類字節(jié)碼,并加載到 JVM 中 的。

就 Java 來(lái)說(shuō),動(dòng)態(tài)代理的實(shí)現(xiàn)方式有很多種,比如 JDK 動(dòng)態(tài)代理、CGLIB 動(dòng)態(tài) 代理等等。

定義JDK動(dòng)態(tài)代理類

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
 * @author Chooker
 * @create 2023-07-27 22:36
 */
public class JDKInvocationHandler implements InvocationHandler {
    //?標(biāo)對(duì)象即就是被代理對(duì)象
    private Object target;
    public JDKInvocationHandler(Object target) {
        this.target = target;
    }
    //proxy代理對(duì)象
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //1.安全檢查
        System.out.println("安全檢查");
        //2.記錄?志
        System.out.println("記錄?志");
        //3.時(shí)間統(tǒng)計(jì)開始
        System.out.println("記錄開始時(shí)間");
        //通過(guò)反射調(diào)?被代理類的?法
        Object retVal = method.invoke(target, args);
        //4.時(shí)間統(tǒng)計(jì)結(jié)束
        System.out.println("記錄結(jié)束時(shí)間");
        return retVal;
    }
}

創(chuàng)建?個(gè)代理對(duì)象并使用 

    public static void main(String[] args) {
        PayService target = new AliPayService();
        //創(chuàng)建?個(gè)代理類:通過(guò)被代理類、被代理實(shí)現(xiàn)的接?、?法調(diào)?處理器來(lái)創(chuàng)建
        PayService proxy = (PayService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{PayService.class},
                new JDKInvocationHandler(target)
        );
        proxy.pay();
    }

缺點(diǎn):JDK的動(dòng)態(tài)代理必須有接口

2.CGLIB動(dòng)態(tài)代理

CGLIB 動(dòng)態(tài)代理類使用步驟

1. 定義一個(gè)類;

2. 自定義 MethodInterceptor 并重寫 intercept 方法,intercept 用于攔截增強(qiáng)

被代理類的方法,和 JDK 動(dòng)態(tài)代理中的 invoke 方法類似;

3. 通過(guò) Enhancer 類的 create()創(chuàng)建代理類

添加依賴(如果創(chuàng)建的是一個(gè)Spring項(xiàng)目,不需要引入,因?yàn)镾pring底層已經(jīng)引入了cglib框架)

<dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib</artifactId>
 <version>3.3.0</version>
</dependency>

自定義 MethodInterceptor(方法攔截器)

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
 * @author Chooker
 * @create 2023-07-27 22:48
 */
public class CGLIBInterceptor implements MethodInterceptor {
    //被代理對(duì)象
    private Object target;
    public CGLIBInterceptor(Object target) {
        this.target = target;
    }
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //1.安全檢查
        System.out.println("安全檢查");
        //2.記錄?志
        System.out.println("記錄?志");
        //3.時(shí)間統(tǒng)計(jì)開始
        System.out.println("記錄開始時(shí)間");
        //通過(guò)cglib的代理?法調(diào)?
        Object retVal = methodProxy.invoke(target, args);
        //4.時(shí)間統(tǒng)計(jì)結(jié)束
        System.out.println("記錄結(jié)束時(shí)間");
        return retVal;
    }
}

1. obj : 被代理的對(duì)象(需要增強(qiáng)的對(duì)象)

2. method : 被攔截的方法(需要增強(qiáng)的方法)

3. args : 方法入?yún)?/p>

4. proxy : 用于調(diào)用原始方法

 創(chuàng)建代理類, 并使用 

    public static void main(String[] args) {
        PayService target = new AliPayService();
        PayService proxy = (PayService) Enhancer.create(target.getClass(), new CGLIBInterceptor(target));
        proxy.pay();
    }

JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理對(duì)比

1. JDK 動(dòng)態(tài)代理只能代理實(shí)現(xiàn)了接口的類或者直接代理接口,而 CGLIB 可以代 理未實(shí)現(xiàn)任何接口的類。

2. CGLIB 動(dòng)態(tài)代理是通過(guò)生成?個(gè)被代理類的子類來(lái)攔截被代理類的方法調(diào)用,因此不能代理聲明為 final 

性能: 大部分情況都是 JDK 動(dòng)態(tài)代理更優(yōu)秀,隨著 JDK 版本的升級(jí),這個(gè)優(yōu)勢(shì)更 加明顯。

Spring代理選擇

1. proxyTargetClass 為false, 目標(biāo)實(shí)現(xiàn)了接口, 用jdk代理

2. proxyTargetClass 為false, 目標(biāo)未實(shí)現(xiàn)接口, 用cglib代理

3. proxyTargetClass 為true, 用cglib代理

織入(Weaving):代理的生成時(shí)機(jī)織入是把切面應(yīng)用到目標(biāo)對(duì)象并創(chuàng)建新的代理對(duì)象的過(guò)程,切面在指定的連接點(diǎn)被織入到目標(biāo)對(duì)象中。在目標(biāo)對(duì)象的生命周期里有多個(gè)點(diǎn)可以進(jìn)行織入:

  • 編譯期:切面在目標(biāo)類編譯時(shí)被織入。這種方式需要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。
  • 類加載期:切面在目標(biāo)類加載到JVM時(shí)被織入。這種方式需要特殊的類加載器(ClassLoader),它可以在目標(biāo)類被引入應(yīng)用之前增強(qiáng)該目標(biāo)類的字節(jié)碼。AspectJ5的加載時(shí)織入(load-time weaving. LTW)就支持以這種方式織入切面。
  • 運(yùn)行期:切面在應(yīng)用運(yùn)行的某?時(shí)刻被織入。?般情況下,在織入切面時(shí),AOP容器會(huì)為目標(biāo)對(duì)象動(dòng)態(tài)創(chuàng)建?個(gè)代理對(duì)象。SpringAOP就是以這種方式織入切面的。

上面我們學(xué)習(xí)的是Spring AOP的原理,是接下來(lái)我們學(xué)習(xí)內(nèi)容的底層,SpringBoot一些常見的功能進(jìn)行了封裝,底層使用AOP實(shí)現(xiàn)的

二.SpringBoot 統(tǒng)一功能處理

需要實(shí)現(xiàn)用戶的登錄權(quán)限的校驗(yàn)功能,在Servlet階段,我們可以通過(guò)在Session中保存用戶的信息,之后每個(gè)頁(yè)面先通過(guò)session中判斷是否存在用戶的信息,如果存在說(shuō)明用戶已經(jīng)登錄過(guò)了,沒有就跳轉(zhuǎn)到登錄的頁(yè)面.

1.Spring AOP 用戶統(tǒng)一登錄驗(yàn)證的問(wèn)題

我們第一時(shí)間想到的就是通過(guò)環(huán)繞通知來(lái)解決這個(gè)問(wèn)題,可以對(duì)除了登錄和注冊(cè)的頁(yè)面采用環(huán)繞通知,用來(lái)判斷用戶是否登錄過(guò)了.但是會(huì)出現(xiàn)以下兩個(gè)問(wèn)題

1.. 沒辦法獲取到 HttpSession 對(duì)象。

2. 我們要對(duì)一部分方法進(jìn)行攔截,而另一部分方法不攔截,如注冊(cè)方法和登錄方法是不攔截的,這樣 的話排除方法的規(guī)則很難定義,甚至沒辦法定義。

那么該如何解決呢?

2.Spring 攔截器

對(duì)于以上問(wèn)題 Spring 中提供了具體的實(shí)現(xiàn)攔截器:HandlerInterceptor,攔截器的實(shí)現(xiàn)分為以下兩個(gè)步驟:

1. 創(chuàng)建自定義攔截器,實(shí)現(xiàn) HandlerInterceptor 接口的 preHandle(執(zhí)行具體方法之前的預(yù)處理)方法。

2. 將自定義攔截器加入 WebMvcConfigurer 的 addInterceptors 方法中

1.自定義攔截器

@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("username") != null) {
            //通過(guò)
            return true;
        }
        //沒有權(quán)限訪問(wèn)
        response.setStatus(401);
        return false;
    }
}

2.將自定義攔截器加入到系統(tǒng)配置

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor).
                //表示攔截所有的路徑
                addPathPatterns("/**").
                //不攔截login接口
                excludePathPatterns("/login").
                //不攔截register接口
                excludePathPatterns("/register");
    }
}

其中:

addPathPatterns:表示需要攔截的 URL,“**”表示攔截任意方法(也就是所有方法)。

excludePathPatterns:表示需要排除的 URL。

說(shuō)明:以上攔截規(guī)則可以攔截此項(xiàng)目中的使用 URL,包括靜態(tài)文件(圖片文件、JS 和 CSS 等文件)。

排除所有的靜態(tài)資源

    // 攔截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**") // 攔截所有接?
                .excludePathPatterns("/**/*.js")
                .excludePathPatterns("/**/*.css")
                .excludePathPatterns("/**/*.jpg")
                .excludePathPatterns("/login.html")
                .excludePathPatterns("/**/login"); // 排除接?
    }

拓展以下,可以在里面添加統(tǒng)一前綴的添加

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")// 攔截所有url
                .excludePathPatterns("/api/user/login")
                .excludePathPatterns("/api/user/reg");
    }
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("api", c -> true);
    }
}

3.controller接口模仿登錄

@RestController
@Slf4j
@RequestMapping("/user")
public class LoginController {
    @RequestMapping("/login")
    public boolean login(HttpServletRequest request, String username, String password) {
        log.info("login...");
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return false;
        }
        //此時(shí)表示賬號(hào)密碼正確
        if ("admin".equals(username) && "123456".equals(password)) {
            HttpSession session = request.getSession(true);
            session.setAttribute("username", username);
            return true;
        }
        return false;
    }
    @RequestMapping("/register")
    public String register() {
        log.info("register...");
        return "register...";
    }
    @RequestMapping("/get")
    public String get() {
        log.info("get...");
        return "get...";
    }
}

當(dāng)我們直接訪問(wèn)get接口的時(shí)候:顯示的是401,表示沒有權(quán)限

正常訪問(wèn)login和register接口都是可以實(shí)現(xiàn)的

 此時(shí)我們使用正確的賬號(hào)密碼登錄:可以看到此時(shí)已經(jīng)正確登錄了

 此時(shí)我們?cè)俅卧L問(wèn)get接口:可以看到此時(shí)正確訪問(wèn)

4.攔截器的實(shí)現(xiàn)原理

正常情況下的調(diào)用順序:

 然而有了攔截器之后,會(huì)在調(diào)用 Controller 之前進(jìn)行相應(yīng)的業(yè)務(wù)處理,執(zhí)行的流程如下圖所示:

攔截器是基于AOP的,Spring是基于Servlet的 

三.統(tǒng)一異常處理

統(tǒng)一異常處理使用的是 @ControllerAdvice + @ExceptionHandler 來(lái)實(shí)現(xiàn)的,@ControllerAdvice 表示控制器通知類,@ExceptionHandler 是異常處理器,兩個(gè)結(jié)合表示當(dāng)出現(xiàn)異常的時(shí)候執(zhí)行某個(gè)通知, 也就是執(zhí)行某個(gè)方法事件,具體實(shí)現(xiàn)代碼如下

@ControllerAdvice
@ResponseBody
public class ErrorHandler {
    @ExceptionHandler(Exception.class)
    public Object error(Exception e) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("success", 0);
        map.put("status", -1);
        map.put("msg", e.getMessage());
        return map;
    }
    @ExceptionHandler(NullPointerException.class)
    public Object error2(NullPointerException e) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("success", 0);
        result.put("status", -2);
        result.put("message", "空指針異常:" + e.getMessage());
        return result;
    }
    @ExceptionHandler(ArithmeticException.class)
    public Object error2(ArithmeticException e) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("success", 0);
        result.put("status", -3);
        result.put("message", "算數(shù)異常:" + e.getMessage());
        return result;
    }
}

controller

@RestController
@Slf4j
@RequestMapping("/error")
public class ErrorController {
    @RequestMapping("/test1")
    public boolean test1() {
        int i = 10 / 0;
        return true;
    }
    @RequestMapping("/test2")
    public boolean test2() {
        String a = null;
        a.length();
        return true;
    }
    @RequestMapping("/test3")
    public String test3() {
        throw new RuntimeException("test3手動(dòng)創(chuàng)建異常");
    }
}

當(dāng)有多個(gè)異常通知時(shí),匹配順序?yàn)楫?dāng)前類及其子類向上依次匹配

訪問(wèn)test1

訪問(wèn)test2

訪問(wèn)test3

可以觀察到當(dāng)錯(cuò)誤異常為子類的時(shí)候,匹配順序?yàn)楫?dāng)前類及其子類向上依次匹配

四.統(tǒng)一數(shù)據(jù)返回格式

1.為什么需要統(tǒng)一返回格式

統(tǒng)一數(shù)據(jù)返回格式的優(yōu)點(diǎn)有很多,比如以下幾個(gè):

  • 方便前端程序員更好的接收和解析后端數(shù)據(jù)接口返回的數(shù)據(jù)。
  • 降低前端程序員和后端程序員的溝通成本,按照某個(gè)格式實(shí)現(xiàn)就行了,因?yàn)樗薪涌诙际沁@樣返回的。
  • 有利于項(xiàng)目統(tǒng)一數(shù)據(jù)的維護(hù)和修改。
  • 有利于后端技術(shù)部門的統(tǒng)一規(guī)范的標(biāo)準(zhǔn)制定,不會(huì)出現(xiàn)稀奇古怪的返回內(nèi)容

2.統(tǒng)一數(shù)據(jù)返回格式的實(shí)現(xiàn)

統(tǒng)一的數(shù)據(jù)返回格式可以使? @ControllerAdvice + ResponseBodyAdvice 的方式實(shí)現(xiàn),具體實(shí)現(xiàn)代碼如下:

@ControllerAdvice
public class ResponseHandler implements ResponseBodyAdvice {
    /**
     * 內(nèi)容是否需要重寫(通過(guò)此?法可以選擇性部分控制器和?法進(jìn)?重寫)
     * 返回 true 表示重寫
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }
    /**
     * ?法返回之前調(diào)?此?法
     */
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 構(gòu)造統(tǒng)?返回對(duì)象
        HashMap<String, Object> result = new HashMap<>();
        result.put("state", 1);
        result.put("msg", "");
        result.put("data", body);
        if(body instanceof String){
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(result);
        }
        return result;
    }
}

假如沒有if((body instanceof String)這一段代碼,會(huì)發(fā)生如下的錯(cuò)誤

 controller:

@RestController
@Slf4j
@RequestMapping("/user")
public class LoginController {
    @RequestMapping("/login")
    public boolean login(HttpServletRequest request, String username, String password) {
        log.info("login...");
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return false;
        }
        //此時(shí)表示賬號(hào)密碼正確
        if ("admin".equals(username) && "123456".equals(password)) {
            HttpSession session = request.getSession(true);
            session.setAttribute("username", username);
            return true;
        }
        return false;
    }
    @RequestMapping("/register")
    public String register() {
        log.info("register...");
        return "register...";
    }
    @RequestMapping("/get")
    public String get() {
        log.info("get...");
        return "get...";
    }
}

到此這篇關(guān)于Spring AOP簡(jiǎn)介及統(tǒng)一處理的文章就介紹到這了,更多相關(guān)Spring AOP內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JavaWeb實(shí)現(xiàn)上傳文件功能

    JavaWeb實(shí)現(xiàn)上傳文件功能

    這篇文章主要為大家詳細(xì)介紹了JavaWeb實(shí)現(xiàn)上傳文件功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • Springboot集成mybatis實(shí)現(xiàn)多數(shù)據(jù)源配置詳解流程

    Springboot集成mybatis實(shí)現(xiàn)多數(shù)據(jù)源配置詳解流程

    在日常開發(fā)中,若遇到多個(gè)數(shù)據(jù)源的需求,怎么辦呢?通過(guò)springboot集成mybatis實(shí)現(xiàn)多數(shù)據(jù)源配置,簡(jiǎn)單嘗試一下,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • ZooKeeper命令及JavaAPI操作代碼

    ZooKeeper命令及JavaAPI操作代碼

    ZooKeeper是一個(gè)樹形目錄服務(wù),其數(shù)據(jù)模型和Uiix的文件目錄樹很類似,擁有一個(gè)層次化結(jié)構(gòu),這篇文章主要介紹了ZooKeeper命令及JavaAPI操作代碼,需要的朋友可以參考下
    2023-03-03
  • SpringBoot 請(qǐng)求參數(shù)忽略大小寫的實(shí)例

    SpringBoot 請(qǐng)求參數(shù)忽略大小寫的實(shí)例

    這篇文章主要介紹了SpringBoot 請(qǐng)求參數(shù)忽略大小寫的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-01-01
  • springboot實(shí)現(xiàn)異步調(diào)用@Async的示例

    springboot實(shí)現(xiàn)異步調(diào)用@Async的示例

    這篇文章主要介紹了springboot實(shí)現(xiàn)異步調(diào)用@Async的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • java使用篩選法求n以內(nèi)的素?cái)?shù)示例(java求素?cái)?shù))

    java使用篩選法求n以內(nèi)的素?cái)?shù)示例(java求素?cái)?shù))

    這篇文章主要介紹了java使用篩選法求n以內(nèi)的素?cái)?shù)示例(java求素?cái)?shù)),需要的朋友可以參考下
    2014-04-04
  • 使用Servlet處理一個(gè)上傳的文件

    使用Servlet處理一個(gè)上傳的文件

    今天小編就為大家分享一篇關(guān)于使用Servlet處理一個(gè)上傳的文件,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-01-01
  • Java死鎖產(chǎn)生原因及示例

    Java死鎖產(chǎn)生原因及示例

    本文主要介紹了Java死鎖產(chǎn)生原因及示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • Java使用線程池批量處理數(shù)據(jù)操作具體流程

    Java使用線程池批量處理數(shù)據(jù)操作具體流程

    這篇文章主要給大家介紹了關(guān)于Java使用線程池批量處理數(shù)據(jù)操作的相關(guān)資料,Java多線程編程中線程池是一個(gè)非常重要的概念,線程池可以提高線程的復(fù)用率和任務(wù)調(diào)度的效率,尤其是當(dāng)需要查詢大批量數(shù)據(jù)時(shí),需要的朋友可以參考下
    2023-06-06
  • Java中Cookie和Session的那些事兒

    Java中Cookie和Session的那些事兒

    Cookie和Session都是為了保持用戶的訪問(wèn)狀態(tài),一方面為了方便業(yè)務(wù)實(shí)現(xiàn),另一方面為了簡(jiǎn)化服務(wù)端的程序設(shè)計(jì)。這篇文章主要介紹了java中cookie和session的知識(shí),需要的朋友可以參考下
    2016-09-09

最新評(píng)論