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

鑒權(quán)認(rèn)證+aop+注解+過濾feign請(qǐng)求的實(shí)例

 更新時(shí)間:2022年03月15日 09:07:02   作者:cx372877498  
這篇文章主要介紹了鑒權(quán)認(rèn)證+aop+注解+過濾feign請(qǐng)求的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

注解類

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {
? ? String code() default "";
}

切面

@Aspect
@Component
public class AuthAspect {?
? ? public static final String FEIGN_FLAG = "YES";
? ? public static final String URL = "http://service/xxxx";
?
? ? @Autowired
? ? private RestTemplate restTemplate;
?
? ? @Pointcut("@annotation(com.jvv.csr.service.base.annotation.Auth)")
? ? public void auAspect(){}
?
? ? @Before(value = "auAspect() && @annotation(param)")
? ? public void doBefore(Auth param){
? ? ? ? ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
? ? ? ? HttpServletRequest request = attributes.getRequest();
? ? ? ? String code = request.getHeader("feign");
? ? ? ? if(FEIGN_FLAG.equals(code)){
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? Long networkId = null;
? ? ? ? String token = null;
? ? ? ? Long scope = null;
? ? ? ? try {
? ? ? ? ? ? networkId = Long.valueOf(request.getHeader("networkId"));
? ? ? ? ? ? token = request.getHeader("authToken");
? ? ? ? ? ? scope = Long.valueOf(request.getHeader("scope"));
? ? ? ? } catch (NumberFormatException e) {
? ? ? ? ? ? throw new RuntimeException("認(rèn)證信息失敗,head頭信息傳入錯(cuò)誤:"+ e.getMessage());
? ? ? ? }
? ? ? ? HashMap object = null;
? ? ? ? try {
? ? ? ? ? ? MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
? ? ? ? ? ? paramMap.add("networkId",networkId);
? ? ? ? ? ? paramMap.add("scope",scope);
? ? ? ? ? ? paramMap.add("token",token);
? ? ? ? ? ? paramMap.add("ecode",param.code());
? ? ? ? ? ? object = restTemplate.postForObject(URL,paramMap,HashMap.class);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? throw new RuntimeException("調(diào)用3A認(rèn)證接口異常:"+ e.getMessage());
? ? ? ? }
? ? ? ? if (0 != (Integer) object.get("code")) {
? ? ? ? ? ? throw new RuntimeException("調(diào)用3A認(rèn)證接口失?。?+ object.get("msg"));
? ? ? ? }
? ? }
}

內(nèi)部feign調(diào)用不用認(rèn)證

@Configuration
public class FeignRequestInterceptorConfig implements RequestInterceptor {?
? ? ?@Bean
? ? ?@LoadBalanced
? ? ?RestTemplate restTemplate(){
? ? ? ? ?return new RestTemplate();
? ? ?}
? ? @Override
? ? public void apply(RequestTemplate requestTemplate) {
? ? ? ? requestTemplate.header("feign","YES");
? ? }
}

需要認(rèn)證的接口

?? ?@Auth(code = "co-005-1-1")
?? ?@RequestMapping(value ="" ,method = RequestMethod.POST)
?? ?public ResultVO add(@RequestBody ?GoodsAllInfoInsertParam insertParam){
?
?? ??? ?ResultVO resultVO = new ResultVO(CodeEnum.SUCCESS,goodsService.addInfo(insertParam));
?? ??? ?return resultVO;
?? ?}

feign aop切不到的詭異案例

我曾遇到過這么一個(gè)案例

使用 Spring Cloud 做微服務(wù)調(diào)用,為方便統(tǒng)一處理 Feign,想到了用 AOP 實(shí)現(xiàn),即使用 within 指示器匹配 feign.Client 接口的實(shí)現(xiàn)進(jìn)行 AOP 切入。代碼如下,通過 @Before 注解在執(zhí)行方法前打印日志,并在代碼中定義了一個(gè)標(biāo)記了@FeignClient 注解的 Client 類,讓其成為一個(gè) Feign 接口:

package org.geekbang.time.commonmistakes.springpart2.aopfeign.feign;?
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
?
@FeignClient(name = "client")
public interface Client {
? ? @GetMapping("/feignaop/server")
? ? String api();
}
package org.geekbang.time.commonmistakes.springpart2.aopfeign;?
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
?
@Configuration
@EnableFeignClients(basePackages = "org.geekbang.time.commonmistakes.springpart2.aopfeign.feign")
public class Config {
}
package org.geekbang.time.commonmistakes.springpart2.aopfeign;?
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
?
@Aspect
@Slf4j
@Component
public class WrongAspect {
? ? @Before("within(feign.Client+)")
? ? public void before(JoinPoint pjp) {
? ? ? ? log.info("within(feign.Client+) pjp {}, args:{}", pjp, pjp.getArgs());
? ? }
}

通過 Feign 調(diào)用服務(wù)后可以看到日志中有輸出,的確實(shí)現(xiàn)了 feign.Client 的切入,切入的是 execute 方法:

[15:48:32.850] [http-nio-45678-exec-1] [INFO ] [o.g.t.c.spring.demo4.WrongAspec
Binary data, feign.Request$Options@5c16561a]

一開始這個(gè)項(xiàng)目使用的是客戶端的負(fù)載均衡,也就是讓 Ribbon 來做負(fù)載均衡,代碼沒啥問題。后來因?yàn)楹蠖朔?wù)通過 Nginx 實(shí)現(xiàn)服務(wù)端負(fù)載均衡,所以開發(fā)同學(xué)把@FeignClient 的配置設(shè)置了 URL 屬性,直接通過一個(gè)固定 URL 調(diào)用后端服務(wù):

package org.geekbang.time.commonmistakes.springpart2.aopfeign.feign; 
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
 
@FeignClient(name = "anotherClient", url = "http://localhost:45678")
public interface ClientWithUrl {
    @GetMapping("/feignaop/server")
    String api();
}

但這樣配置后,之前的 AOP 切面竟然失效了,也就是 within(feign.Client+) 無法切入ClientWithUrl 的調(diào)用了。為了還原這個(gè)場(chǎng)景,我寫了一段代碼,定義兩個(gè)方法分別通過 Client 和 ClientWithUrl 這兩個(gè) Feign 進(jìn)行接口調(diào)用:

package org.geekbang.time.commonmistakes.springpart2.aopfeign; 
import lombok.extern.slf4j.Slf4j;
import org.geekbang.time.commonmistakes.springpart2.aopfeign.feign.Client;
import org.geekbang.time.commonmistakes.springpart2.aopfeign.feign.ClientWithUrl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@Slf4j
@RequestMapping("feignaop")
@RestController
public class FeignAopConntroller {
 
    @Autowired
    private Client client;
 
    @Autowired
    private ClientWithUrl clientWithUrl;
 
    @Autowired
    private ApplicationContext applicationContext;
 
    @GetMapping("client")
    public String client() {
        return client.api();
    }
 
    @GetMapping("clientWithUrl")
    public String clientWithUrl() {
        return clientWithUrl.api();
    }
 
    @GetMapping("server")
    public String server() {
        return "OK";
    }
}

可以看到,調(diào)用 Client 后 AOP 有日志輸出,調(diào)用 ClientWithUrl 后卻沒有:

[15:50:32.850] [http-nio-45678-exec-1] [INFO ] [o.g.t.c.spring.demo4.WrongAspec
Binary data, feign.Request$Options@5c16561

這就很費(fèi)解了。難道為 Feign 指定了 URL,其實(shí)現(xiàn)就不是 feign.Clinet 了嗎?要明白原因,我們需要分析一下 FeignClient 的創(chuàng)建過程,也就是分析FeignClientFactoryBean 類的 getTarget 方法。源碼第 4 行有一個(gè) if 判斷,當(dāng) URL 沒有內(nèi)容也就是為空或者不配置時(shí)調(diào)用 loadBalance 方法,在其內(nèi)部通過 FeignContext 從容器獲取 feign.Client 的實(shí)例:

<T> T getTarget() {
  FeignContext context = this.applicationContext.getBean(FeignContext.class);
  Feign.Builder builder = feign(context);
  if (!StringUtils.hasText(this.url)) {
  ...
  return (T) loadBalance(builder, context,
  new HardCodedTarget<>(this.type, this.name, this.url));
}.
..
  String url = this.url + cleanPath();
  Client client = getOptional(context, Client.class);
  if (client != null) {
  if (client instanceof LoadBalancerFeignClient) {
 // not load balancing because we have a url,
  // but ribbon is on the classpath, so unwrap
  client = ((LoadBalancerFeignClient) client).getDelegate();
}builder.client(client);
}.
..
}protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
  HardCodedTarget<T> target) {
  Client client = getOptional(context, Client.class);
  if (client != null) {
    builder.client(client);
    Targeter targeter = get(context, Targeter.class);
    return targeter.target(this, builder, context, target);
  }
...
}
protected <T> T getOptional(FeignContext context, Class<T> type) {
   return context.getInstance(this.contextId, type);
}

調(diào)試一下可以看到,client 是 LoadBalanceFeignClient,已經(jīng)是經(jīng)過代理增強(qiáng)的,明顯是一個(gè) Bean:

 所以,沒有指定 URL 的 @FeignClient 對(duì)應(yīng)的 LoadBalanceFeignClient,是可以通過feign.Client 切入的。在我們上面貼出來的源碼的 16 行可以看到,當(dāng) URL 不為空的時(shí)候,client 設(shè)置為了LoadBalanceFeignClient 的 delegate 屬性。

其原因注釋中有提到,因?yàn)橛辛?URL 就不需要客戶端負(fù)載均衡了,但因?yàn)?Ribbon 在 classpath 中,所以需要從LoadBalanceFeignClient 提取出真正的 Client。斷點(diǎn)調(diào)試下可以看到,這時(shí) client 是一個(gè)ApacheHttpClient

那么,這個(gè) ApacheHttpClient 是從哪里來的呢?這里,我教你一個(gè)小技巧:如果你希望知道一個(gè)類是怎樣調(diào)用棧初始化的,可以在構(gòu)造方法中設(shè)置一個(gè)斷點(diǎn)進(jìn)行調(diào)試。這樣,你就可以在 IDE 的棧窗口看到整個(gè)方法調(diào)用棧,然后點(diǎn)擊每一個(gè)棧幀看到整個(gè)過程。

用這種方式,我們可以看到,是 HttpClientFeignLoadBalancedConfiguration 類實(shí)例化的 ApacheHttpClient:

進(jìn)一步查看 HttpClientFeignLoadBalancedConfiguration 的源碼可以發(fā)現(xiàn),LoadBalancerFeignClient 這個(gè) Bean 在實(shí)例化的時(shí)候,new 出來一個(gè)ApacheHttpClient 作為 delegate 放到了 LoadBalancerFeignClient 中:

@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
  SpringClientFactory clientFactory, HttpClient httpClient) {
  ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
  return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory)
} 
 
public LoadBalancerFeignClient(Client delegate,
  CachingSpringLoadBalancerFactory lbClientFactory,
  SpringClientFactory clientFactory) {
  this.delegate = delegate;
  this.lbClientFactory = lbClientFactory;
  this.clientFactory = clientFactory;
}

顯然,ApacheHttpClient 是 new 出來的,并不是 Bean,而 LoadBalancerFeignClient是一個(gè) Bean。有了這個(gè)信息,我們?cè)賮磙垡幌?,為什?within(feign.Client+) 無法切入設(shè)置過 URL 的@FeignClient ClientWithUrl:因此,定義了 URL 的 FeignClient 采用 within(feign.Client+) 無法切入。那,如何解決這個(gè)問題呢?有一位同學(xué)提出,修改一下切點(diǎn)表達(dá)式,通過 @FeignClient 注解來切:

package org.geekbang.time.commonmistakes.springpart2.aopfeign; 
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
 
@Aspect
@Slf4j
//@Component
public class Wrong2Aspect {
 
    @Before("@within(org.springframework.cloud.openfeign.FeignClient)")
    public void before(JoinPoint pjp) {
        log.info("@within(org.springframework.cloud.openfeign.FeignClient) pjp {}, args:{}", pjp, pjp.getArgs());
    }
}

修改后通過日志看到,AOP 的確切成功了:

[15:53:39.093] [http-nio-45678-exec-3] [INFO ] [o.g.t.c.spring.demo4.Wrong2Aspe

但仔細(xì)一看就會(huì)發(fā)現(xiàn),這次切入的是 ClientWithUrl 接口的 API 方法,并不是client.Feign 接口的 execute 方法,顯然不符合預(yù)期。

這位同學(xué)犯的錯(cuò)誤是,沒有弄清楚真正希望切的是什么對(duì)象。@FeignClient 注解標(biāo)記在Feign Client 接口上,所以切的是 Feign 定義的接口,也就是每一個(gè)實(shí)際的 API 接口。而通過 feign.Client 接口切的是客戶端實(shí)現(xiàn)類,切到的是通用的、執(zhí)行所有 Feign 調(diào)用的execute 方法。那么問題來了,ApacheHttpClient 不是 Bean 無法切入,切 Feign 接口本身又不符合要求。怎么辦呢?

經(jīng)過一番研究發(fā)現(xiàn),ApacheHttpClient 其實(shí)有機(jī)會(huì)獨(dú)立成為 Bean。查看HttpClientFeignConfiguration 的源碼可以發(fā)現(xiàn),當(dāng)沒有 ILoadBalancer 類型的時(shí)候,自動(dòng)裝配會(huì)把 ApacheHttpClient 設(shè)置為 Bean。

這么做的原因很明確,如果我們不希望做客戶端負(fù)載均衡的話,應(yīng)該不會(huì)引用 Ribbon 組件的依賴,自然沒有 LoadBalancerFeignClient,只有 ApacheHttpClient:

@Configuration
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = tru
protected static class HttpClientFeignConfiguration {
  @Bean
  @ConditionalOnMissingBean(Client.class)
  public Client feignClient(HttpClient httpClient) {
     return new ApacheHttpClient(httpClient);
   }
}

那,把 pom.xml 中的 ribbon 模塊注釋之后,是不是可以解決問題呢?

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

但,問題并沒解決,啟動(dòng)出錯(cuò)誤了:

Caused by: java.lang.IllegalArgumentException: Cannot subclass final class feig
at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:657)
at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGe

這里,又涉及了 Spring 實(shí)現(xiàn)動(dòng)態(tài)代理的兩種方式:Spring Boot 2.x 默認(rèn)使用 CGLIB 的方式,但通過繼承實(shí)現(xiàn)代理有個(gè)問題是,無法繼承final 的類。因?yàn)椋珹pacheHttpClient 類就是定義為了 final

public final class ApacheHttpClient implements Client {

為解決這個(gè)問題,我們把配置參數(shù) proxy-target-class 的值修改為 false,以切換到使用JDK 動(dòng)態(tài)代理的方式:

spring.aop.proxy-target-class=false

修改后執(zhí)行 clientWithUrl 接口可以看到,通過 within(feign.Client+) 方式可以切入feign.Client 子類了。以下日志顯示了 @within 和 within 的兩次切入:

[16:29:55.303] [http-nio-45678-exec-1] [INFO ] [o.g.t.c.spring.demo4.Wrong2Aspe
[16:29:55.310] [http-nio-45678-exec-1] [INFO ] [o.g.t.c.spring.demo4.WrongAspec
Binary data, feign.Request$Options@387550b0]

這下我們就明白了,Spring Cloud 使用了自動(dòng)裝配來根據(jù)依賴裝配組件,組件是否成為Bean 決定了 AOP 是否可以切入,在嘗試通過 AOP 切入 Spring Bean 的時(shí)候要注意加上上一講的兩個(gè)案例,我就把 IoC 和 AOP 相關(guān)的坑點(diǎn)和你說清楚了。除此之外,我們?cè)跇I(yè)務(wù)開發(fā)時(shí),還有一個(gè)繞不開的點(diǎn)是,Spring 程序的配置問題。接下來,我們就看具體吧。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 詳解SpringBoot讀取resource目錄下properties文件的常見方式

    詳解SpringBoot讀取resource目錄下properties文件的常見方式

    這篇文章主要介紹了SpringBoot讀取resource目錄下properties文件的常見方式,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-02-02
  • Java隊(duì)列同步器之CountDownLatch實(shí)現(xiàn)詳解

    Java隊(duì)列同步器之CountDownLatch實(shí)現(xiàn)詳解

    這篇文章主要介紹了Java隊(duì)列同步器之CountDownLatch實(shí)現(xiàn)詳解,CountDownLatch是一個(gè)同步工具類,它允許一個(gè)或多個(gè)線程一直等待,直到其他線程執(zhí)行完后再執(zhí)行,例如,應(yīng)用程序的主線程希望在負(fù)責(zé)啟動(dòng)框架服務(wù)的線程已經(jīng)啟動(dòng)所有框架服務(wù)之后執(zhí)行,需要的朋友可以參考下
    2023-12-12
  • mybatis plus in方法使用詳解

    mybatis plus in方法使用詳解

    這篇文章主要介紹了mybatis plus in方法使用詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Mybatis-Plus支持GBase8s分頁(yè)查詢的實(shí)現(xiàn)示例

    Mybatis-Plus支持GBase8s分頁(yè)查詢的實(shí)現(xiàn)示例

    本文主要介紹了使?Mybatis-Plus?支持?GBase8s?的分頁(yè)查詢,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • 雙重檢查鎖定模式Java中的陷阱案例

    雙重檢查鎖定模式Java中的陷阱案例

    這篇文章主要介紹了雙重檢查鎖定模式Java中的陷阱,雙重檢查鎖定(也叫做雙重檢查鎖定優(yōu)化)是一種軟件設(shè)計(jì)模式,它的作用是減少延遲初始化在多線程環(huán)境下獲取鎖的次數(shù),尤其是單例模式下比較突出,想具體了解的小伙伴可以參考下面文章內(nèi)容,附呦詳細(xì)的舉例說明
    2021-10-10
  • Spring Boot Feign服務(wù)調(diào)用之間帶token問題

    Spring Boot Feign服務(wù)調(diào)用之間帶token問題

    這篇文章主要介紹了Spring Boot Feign服務(wù)調(diào)用之間帶token的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • spring boot admin 搭建詳解

    spring boot admin 搭建詳解

    本篇文章主要介紹了spring boot admin 搭建詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-04-04
  • java 實(shí)現(xiàn)Comparable接口排序,升序、降序、倒敘

    java 實(shí)現(xiàn)Comparable接口排序,升序、降序、倒敘

    這篇文章主要介紹了java 實(shí)現(xiàn)Comparable接口排序,升序、降序、倒敘,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • 淺談Java中SimpleDateFormat 多線程不安全原因

    淺談Java中SimpleDateFormat 多線程不安全原因

    SimpleDateFormat是Java中用于日期時(shí)間格式化的一個(gè)類,本文主要介紹了淺談Java中SimpleDateFormat 多線程不安全原因,感興趣的可以了解一下
    2024-01-01
  • 一個(gè)JAVA小項(xiàng)目--Web應(yīng)用自動(dòng)生成Word

    一個(gè)JAVA小項(xiàng)目--Web應(yīng)用自動(dòng)生成Word

    前段時(shí)間接到一個(gè)Web應(yīng)用自動(dòng)生成Word的需求,現(xiàn)整理了下一些關(guān)鍵步驟拿來分享一下。
    2014-05-05

最新評(píng)論