<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      AOP 切面編程

      什么是 AOP ?

      AOP

      • Aspect Oriented Programming(面向切面編程、面向方面編程),其實(shí)就是面向特定方法編程。

      實(shí)現(xiàn):

      • 動(dòng)態(tài)代理是面向切面編程最主流的實(shí)現(xiàn)。而SpringAOPSpring框架的高級(jí)技術(shù),旨在管理bean對(duì)象的過程中,主要通過底層的動(dòng)態(tài)代理機(jī)制,對(duì)特定的方法進(jìn)行編程。

      AOP 核心概念

      • 連接點(diǎn): JoinPoint, 可以被AOP控制的方法(暗含方法執(zhí)行時(shí)的相關(guān)信息)
      • 通知: Advice, 指哪些重復(fù)的邏輯,也就是共性功能(最終體現(xiàn)為一個(gè)方法)
      • 切入點(diǎn): PointCut, 匹配連接點(diǎn)的條件,通知僅會(huì)在切入點(diǎn)方法執(zhí)行時(shí)被應(yīng)用
      • 切面: Aspect, 描述通知與切入點(diǎn)的對(duì)應(yīng)關(guān)系(通知+切入點(diǎn))
      • 目標(biāo)對(duì)象: Target, 通知所應(yīng)用的對(duì)象

      場(chǎng)景說明

      例如現(xiàn)有一個(gè)場(chǎng)景:定位執(zhí)行耗時(shí)較長(zhǎng)的業(yè)務(wù)方法,統(tǒng)計(jì)各個(gè)業(yè)務(wù)層方法的執(zhí)行耗時(shí)

      @Component
      @Aspect // 切面類
      @Slf4j
      public class TimeAspect {
          @Around ("execution (* com.itheima.service.impl.DeptServiceImpl.list ())")  // 切面表達(dá)式
          public Object recordTime (ProceedingJoinPoint joinPoint) throws Throwable { 
          long begin = System.currentTimeMillis ();
              // 調(diào)用原始操作
          Object result = joinPoint.proceed (); 
          long end = System.currentTimeMillis ();
          log.info("執(zhí)行耗時(shí) : {} ms", (end-begin));
          return result;
          }
      }
      
      // 目標(biāo)對(duì)象
      @Service
      public class DeptServiceImpl implements DeptService {
          @Autowired
          private DeptMapper deptMapper;
      
          // region-begin 連接點(diǎn) 
          @Override
          public List<Dept> list() {
              List<Dept> deptList = deptMapper.list(); // 切入點(diǎn)
              return deptList;
          }
      
          @Override
          public void delete(Integer id) {
              deptMapper.delete(id);
          }
      
          @Override
          public void save(Dept dept) {
              dept.setCreateTime(LocalDateTime.now());
              dept.setUpdateTime(LocalDateTime.now());
              deptMapper.save(dept);
          }
          // region-end 連接點(diǎn) 
      }
      

      對(duì)于aop的五大核心概念,我們可以使用更加通俗易懂的類比來說明:

      可以用 "學(xué)校檢查衛(wèi)生" 來類比:

      • 連接點(diǎn):學(xué)校里所有可能被檢查的班級(jí)(每個(gè)班級(jí)都是一個(gè)潛在的檢查點(diǎn))
      • 通知:檢查衛(wèi)生的具體操作流程(比如看地面是否干凈、桌椅是否整齊,這是一套固定重復(fù)的動(dòng)作)
      • 切入點(diǎn):篩選要檢查的班級(jí)的條件(比如 "只查一年級(jí)的班級(jí)" 或 "只查偶數(shù)號(hào)的班級(jí)")
      • 切面:檢查計(jì)劃(把 "檢查流程" 和 "篩選條件" 結(jié)合起來,比如 "用標(biāo)準(zhǔn)流程檢查所有一年級(jí)班級(jí)")
      • 目標(biāo)對(duì)象:最終被檢查的那些班級(jí)(符合篩選條件,實(shí)際接受檢查的對(duì)象)

      簡(jiǎn)單來說就是:學(xué)校(AOP)要檢查衛(wèi)生(通知),所有的班級(jí)都可能被抽查到(連接點(diǎn)),但是只會(huì)查到一年級(jí)的(切入點(diǎn)),"用標(biāo)準(zhǔn)流程查一年級(jí)班級(jí)" 這個(gè)整體安排就是切面,而被查到的那些一年級(jí)班級(jí)就是目標(biāo)對(duì)象。

      特別注意:連接點(diǎn)

      • 在Spring中用JoinPoint抽象了連接點(diǎn),用它可以獲得方法執(zhí)行時(shí)的相關(guān)信息,如目標(biāo)類名、方法名、方法參數(shù)等。

        • 對(duì)于 @Around 通知,獲取連接點(diǎn)信息只能使用 ProceedingJoinPoint
        • 對(duì)于其他四種通知,獲取連接點(diǎn)信息只能使用 JoinPoint,它是 ProceedingJoinPoint 的父類型

      通知類型

      通知類型

      1. **@Around**:環(huán)繞通知,此注解標(biāo)注的通知方法在目標(biāo)方法前、后都被執(zhí)行(常用)
      2. @Before:前置通知,此注解標(biāo)注的通知方法在目標(biāo)方法前被執(zhí)行
      3. @After:后置通知,此注解標(biāo)注的通知方法在目標(biāo)方法后被執(zhí)行,無(wú)論是否有異常都會(huì)執(zhí)行
      4. @AfterReturning:返回后通知,此注解標(biāo)注的通知方法在目標(biāo)方法后被執(zhí)行,有異常不會(huì)執(zhí)行
      5. @AfterThrowing:異常后通知,此注解標(biāo)注的通知方法發(fā)生異常后執(zhí)行

      注意事項(xiàng)

      • @Around環(huán)繞通知需要自己調(diào)用 ProceedingJoinPoint.proceed() 來讓原始方法執(zhí)行,其他通知不需要考慮目標(biāo)方法執(zhí)行
      • @Around環(huán)繞通知方法的返回值,必須指定為Object,來接收原始方法的返回值。

      通知順序

      當(dāng)有多個(gè)切面的切入點(diǎn)都匹配到了目標(biāo)方法,目標(biāo)方法運(yùn)行時(shí),多個(gè)通知方法都會(huì)被執(zhí)行。

      執(zhí)行順序

      1. 不同切面類中,默認(rèn)按照切面類的類名字母排序:
        • 目標(biāo)方法前的通知方法:字母排名靠前的先執(zhí)行
        • 目標(biāo)方法后的通知方法:字母排名靠前的后執(zhí)行
      1. @Order(數(shù)字) 加在切面類上來控制順序
        • 目標(biāo)方法前的通知方法:數(shù)字小的先執(zhí)行
        • 目標(biāo)方法后的通知方法:數(shù)字小的后執(zhí)行

      @PointCut

      該注解的作用是將公共的切點(diǎn)表達(dá)式抽取出來,需要用到時(shí)引用該切點(diǎn)表達(dá)式即可。

      @Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
      public void pt(){}
      
      @Around("pt()")
      public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
          // 方法體內(nèi)容
      }
      

      切入點(diǎn)表達(dá)式

      execution

      execution 主要根據(jù)方法的返回值、包名、類名、方法名、方法參數(shù)等信息來匹配,語(yǔ)法為:

      execution(訪問修飾符? 返回值 包名.類名.?方法名(方法參數(shù)) throws 異常?)
      
      • 可以使用通配符描述切入點(diǎn)

        • *:?jiǎn)蝹€(gè)獨(dú)立的任意符號(hào),可以通配任意返回值、包名、類名、方法名、任意類型的一個(gè)參數(shù),也可以通配包、類、方法名的一部分
          execution(* com.**.service.**.update*(*))
        • ..:多個(gè)連續(xù)的任意符號(hào),可以通配任意層級(jí)的包,或任意類型、任意個(gè)數(shù)的參數(shù)
          execution(* com.itheima..DeptService.*(..))

      切入點(diǎn)表達(dá)式-@annotation

      • @annotation 切入點(diǎn)表達(dá)式,用于匹配標(biāo)識(shí)有特定注解的方法。
        @annotation(com.itheima.anno.Log)
      @Before("@annotation(com.itheima.anno.Log)")
      public void before(){
          log.info("before ....");
      }
      

      Spring 實(shí)戰(zhàn)代碼演示

      依賴導(dǎo)入

      引入aop依賴

      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
      </dependency>
      

      日志響應(yīng)攔截

      主要作用:對(duì)controller上的

      @Around("execution(* com.lantzuc.lanucbackend.controller.*.*(..))")
      public Object doInterceptor(ProceedingJoinPoint joinPoint) throws Throwable {
          // 開始計(jì)時(shí)
          StopWatch stopWatch = new StopWatch();
          stopWatch.start();
      
          // 獲取請(qǐng)求路徑
          RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
          HttpServletRequest servletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
      
          // 生成唯一請(qǐng)求id
          String requestId = UUID.randomUUID().toString();
          String url = servletRequest.getRequestURI();
      
          // 獲取請(qǐng)求參數(shù)
          Object[] args = joinPoint.getArgs();
          String reqParams = "[" + StringUtils.join(args, ", ") + "]";
      
          // 輸出請(qǐng)求日志
          log.info("request start,id: {}, path: {}, ip: {}, params: {}", requestId, url,
                   servletRequest.getRemoteHost(), reqParams);
      
          // 執(zhí)行原方法
          Object result = joinPoint.proceed();
      
          // 輸出原日志
          stopWatch.stop();
          long totalTimeMillis = stopWatch.getTotalTimeMillis();
          log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis);
      
          return result;
      }
      

      結(jié)果顯示

      2025-10-15 17:13:48.649  INFO 23396 --- [nio-8080-exec-5] c.l.lanucbackend.aop.LogInterceptor      : request start,id: ad4061ee-d5ed-47f3-bf2e-e0567867fabc, path: /api/user/login, ip: 0:0:0:0:0:0:0:1, params: [UseLoginRequest(userAccount=Lantz, userPassword=12345678), org.apache.catalina.connector.RequestFacade@6ce7aed7]
      
      2025-10-15 17:13:49.265  INFO 23396 --- [nio-8080-exec-5] c.l.lanucbackend.aop.LogInterceptor      : request end, id: ad4061ee-d5ed-47f3-bf2e-e0567867fabc, cost: 623ms
      

      相比原來沒有添加日志攔截的,我們可以更加清晰地看到對(duì)某一路徑發(fā)送請(qǐng)求的狀態(tài),比如請(qǐng)求路徑,請(qǐng)求參數(shù),IP 地址等等信息,而且我們還可以獲悉到某一請(qǐng)求的執(zhí)行時(shí)間是多少,可以在后續(xù)有針對(duì)的目的優(yōu)化

      權(quán)限響應(yīng)攔截

      首先要?jiǎng)?chuàng)建一個(gè)注解:

      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.METHOD)
      public @interface AuthCheck {
          /**
           * 必須有某一個(gè)角色(默認(rèn)無(wú))
           * @return
           */
          String mustRole() default "";
      }
      

      然后再編寫權(quán)限校驗(yàn)攔截代碼:

      @Around("@annotation(authCheck)")
      public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
      
          String mustRole = authCheck.mustRole();
          RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
          HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
          // 當(dāng)前登錄用戶
          User loginUer = userService.getLoginUer(httpServletRequest);
          UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole);
          // 不需要權(quán)限,放行
          if (mustRoleEnum == null) {
              return joinPoint.proceed();
          }
      
          // 必須有該權(quán)限才放行
          UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUer.getUserRole());
          if (userRoleEnum == null) {
              throw new BusinessException(ErrorCode.NO_AUTH);
          }
          // 如果被封號(hào),直接拒絕
          if (UserRoleEnum.BAN.equals(userRoleEnum)) {
              throw new BusinessException(ErrorCode.NO_AUTH);
          }
          // 必需有管理員權(quán)限
          if (UserRoleEnum.ADMIN.equals(mustRoleEnum)) {
              // 用戶沒有管理員權(quán)限,拒絕
              if (!UserRoleEnum.ADMIN.equals(userRoleEnum)) {
                  throw new BusinessException(ErrorCode.NO_AUTH);
              }
          }
      
          // 通過管理權(quán)限,放行
          return joinPoint.proceed();
      }
      

      controller中使用:

      @GetMapping("/search")
      @AuthCheck(mustRole = ADMIN_ROLE) // 需要管理員權(quán)限
      public List<User> searchUser(String userName, HttpServletRequest request){
          QueryWrapper<User> queryWrapper = new QueryWrapper<>();
          if (StringUtils.isNotBlank(userName)) {
              queryWrapper.like("userName", userName);
          }
          List<User> userList = userService.list(queryWrapper);
          return userList.stream().map(user -> userService.getSafetyUser(user)).collect(Collectors.toList());
      }
      

      測(cè)試結(jié)果:

      如果登錄用戶為管理員則正常通過,如果不是,則會(huì)報(bào)錯(cuò):

      img

      Postman Body 顯示:

      {
        "code": 40101,
        "data": null,
        "message": "無(wú)權(quán)限",
        "description": ""
      }
      

      記得如果使用了jwt鑒權(quán),在Postman中測(cè)試的時(shí)候記得選擇Bearer Token然后粘貼進(jìn)去登錄時(shí)候產(chǎn)生的Token

      posted @ 2025-10-15 18:29  Lantz12  閱讀(2)  評(píng)論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 波多野结衣一区二区三区高清av| 尉犁县| 亚洲天堂亚洲天堂亚洲色图| 影音先锋在线资源无码| 国模一区二区三区私拍视频| 一本色道久久东京热| 国产专区精品三级免费看| 国产精品欧美福利久久| 亚洲爆乳WWW无码专区| 亚洲日韩成人无码不卡网站| 亚洲av无码精品色午夜| 色噜噜一区二区三区| 欧美国产激情18| 日韩精品一卡二卡在线观看| 激情综合色综合啪啪开心| 99国精品午夜福利视频不卡99| 亚洲中文无码av永久不收费| 亚洲AV成人无码久久精品四虎| 亚洲中文无码手机永久| 久久精品国产2020| 麻豆国产高清精品国在线| 人妻少妇偷人无码视频| 国产无套护士在线观看| 国产互换人妻xxxx69| 亚洲中文字幕无码一久久区| 亚洲中文字幕一二区日韩| 亚洲免费成人av一区| 亚洲欧美日韩愉拍自拍美利坚| 久久人人97超碰国产精品| 亚洲人午夜精品射精日韩| 日本a在线播放| 日本高清一区免费中文视频| 国产精品毛片在线看不卡| 一面膜上边一面膜下边视频| 吉川爱美一区二区三区视频| 影音先锋啪啪av资源网站| 中文字幕亚洲综合久久| 久久亚洲精品11p| 精品国产成人三级在线观看| 亚洲综合色区另类av| 久久久亚洲欧洲日产国码农村|