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

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

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

注解類(lèi)

@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切不到的詭異案例

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

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

通過(guò) 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]

一開(kāi)始這個(gè)項(xiàng)目使用的是客戶(hù)端的負(fù)載均衡,也就是讓 Ribbon 來(lái)做負(fù)載均衡,代碼沒(méi)啥問(wèn)題。后來(lái)因?yàn)楹蠖朔?wù)通過(guò) Nginx 實(shí)現(xiàn)服務(wù)端負(fù)載均衡,所以開(kāi)發(fā)同學(xué)把@FeignClient 的配置設(shè)置了 URL 屬性,直接通過(guò)一個(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+) 無(wú)法切入ClientWithUrl 的調(diào)用了。為了還原這個(gè)場(chǎng)景,我寫(xiě)了一段代碼,定義兩個(gè)方法分別通過(guò) 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 后卻沒(méi)有:

[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)建過(guò)程,也就是分析FeignClientFactoryBean 類(lèi)的 getTarget 方法。源碼第 4 行有一個(gè) if 判斷,當(dāng) URL 沒(méi)有內(nèi)容也就是為空或者不配置時(shí)調(diào)用 loadBalance 方法,在其內(nèi)部通過(guò) 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)過(guò)代理增強(qiáng)的,明顯是一個(gè) Bean:

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

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

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

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

進(jìn)一步查看 HttpClientFeignLoadBalancedConfiguration 的源碼可以發(fā)現(xiàn),LoadBalancerFeignClient 這個(gè) Bean 在實(shí)例化的時(shí)候,new 出來(lái)一個(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 出來(lái)的,并不是 Bean,而 LoadBalancerFeignClient是一個(gè) Bean。有了這個(gè)信息,我們?cè)賮?lái)捋一下,為什么 within(feign.Client+) 無(wú)法切入設(shè)置過(guò) URL 的@FeignClient ClientWithUrl:因此,定義了 URL 的 FeignClient 采用 within(feign.Client+) 無(wú)法切入。那,如何解決這個(gè)問(wèn)題呢?有一位同學(xué)提出,修改一下切點(diǎn)表達(dá)式,通過(guò) @FeignClient 注解來(lái)切:

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());
    }
}

修改后通過(guò)日志看到,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ò)誤是,沒(méi)有弄清楚真正希望切的是什么對(duì)象。@FeignClient 注解標(biāo)記在Feign Client 接口上,所以切的是 Feign 定義的接口,也就是每一個(gè)實(shí)際的 API 接口。而通過(guò) feign.Client 接口切的是客戶(hù)端實(shí)現(xiàn)類(lèi),切到的是通用的、執(zhí)行所有 Feign 調(diào)用的execute 方法。那么問(wèn)題來(lái)了,ApacheHttpClient 不是 Bean 無(wú)法切入,切 Feign 接口本身又不符合要求。怎么辦呢?

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

這么做的原因很明確,如果我們不希望做客戶(hù)端負(fù)載均衡的話(huà),應(yīng)該不會(huì)引用 Ribbon 組件的依賴(lài),自然沒(méi)有 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 模塊注釋之后,是不是可以解決問(wèn)題呢?

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

但,問(wèn)題并沒(méi)解決,啟動(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 的方式,但通過(guò)繼承實(shí)現(xiàn)代理有個(gè)問(wèn)題是,無(wú)法繼承final 的類(lèi)。因?yàn)椋珹pacheHttpClient 類(lèi)就是定義為了 final

public final class ApacheHttpClient implements Client {

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

spring.aop.proxy-target-class=false

修改后執(zhí)行 clientWithUrl 接口可以看到,通過(guò) within(feign.Client+) 方式可以切入feign.Client 子類(lèi)了。以下日志顯示了 @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)裝配來(lái)根據(jù)依賴(lài)裝配組件,組件是否成為Bean 決定了 AOP 是否可以切入,在嘗試通過(guò) AOP 切入 Spring Bean 的時(shí)候要注意加上上一講的兩個(gè)案例,我就把 IoC 和 AOP 相關(guān)的坑點(diǎn)和你說(shuō)清楚了。除此之外,我們?cè)跇I(yè)務(wù)開(kāi)發(fā)時(shí),還有一個(gè)繞不開(kāi)的點(diǎn)是,Spring 程序的配置問(wèn)題。接下來(lái),我們就看具體吧。

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

相關(guān)文章

  • 詳解SpringBoot讀取resource目錄下properties文件的常見(jiàn)方式

    詳解SpringBoot讀取resource目錄下properties文件的常見(jiàn)方式

    這篇文章主要介紹了SpringBoot讀取resource目錄下properties文件的常見(jiàn)方式,本文給大家介紹的非常詳細(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è)同步工具類(lèi),它允許一個(gè)或多個(gè)線(xiàn)程一直等待,直到其他線(xiàn)程執(zhí)行完后再執(zhí)行,例如,應(yīng)用程序的主線(xiàn)程希望在負(fù)責(zé)啟動(dòng)框架服務(wù)的線(xiàn)程已經(jīng)啟動(dòng)所有框架服務(wù)之后執(zhí)行,需要的朋友可以參考下
    2023-12-12
  • mybatis plus in方法使用詳解

    mybatis plus in方法使用詳解

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

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

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

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

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

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

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

    spring boot admin 搭建詳解

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

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

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

    淺談Java中SimpleDateFormat 多線(xiàn)程不安全原因

    SimpleDateFormat是Java中用于日期時(shí)間格式化的一個(gè)類(lèi),本文主要介紹了淺談Java中SimpleDateFormat 多線(xiàn)程不安全原因,感興趣的可以了解一下
    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)鍵步驟拿來(lái)分享一下。
    2014-05-05

最新評(píng)論