最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

注解類

@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頭信息傳入錯誤:"+ 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切不到的詭異案例

我曾遇到過這么一個案例

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

一開始這個項目使用的是客戶端的負(fù)載均衡,也就是讓 Ribbon 來做負(fù)載均衡,代碼沒啥問題。后來因為后端服務(wù)通過 Nginx 實現(xiàn)服務(wù)端負(fù)載均衡,所以開發(fā)同學(xué)把@FeignClient 的配置設(shè)置了 URL 屬性,直接通過一個固定 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)用了。為了還原這個場景,我寫了一段代碼,定義兩個方法分別通過 Client 和 ClientWithUrl 這兩個 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

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

<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)的,明顯是一個 Bean:

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

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

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

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

進(jìn)一步查看 HttpClientFeignLoadBalancedConfiguration 的源碼可以發(fā)現(xiàn),LoadBalancerFeignClient 這個 Bean 在實例化的時候,new 出來一個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是一個 Bean。有了這個信息,我們再來捋一下,為什么 within(feign.Client+) 無法切入設(shè)置過 URL 的@FeignClient ClientWithUrl:因此,定義了 URL 的 FeignClient 采用 within(feign.Client+) 無法切入。那,如何解決這個問題呢?有一位同學(xué)提出,修改一下切點表達(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ì)一看就會發(fā)現(xiàn),這次切入的是 ClientWithUrl 接口的 API 方法,并不是client.Feign 接口的 execute 方法,顯然不符合預(yù)期。

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

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

這么做的原因很明確,如果我們不希望做客戶端負(fù)載均衡的話,應(yīng)該不會引用 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>

但,問題并沒解決,啟動出錯誤了:

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 實現(xiàn)動態(tài)代理的兩種方式:Spring Boot 2.x 默認(rèn)使用 CGLIB 的方式,但通過繼承實現(xiàn)代理有個問題是,無法繼承final 的類。因為,ApacheHttpClient 類就是定義為了 final

public final class ApacheHttpClient implements Client {

為解決這個問題,我們把配置參數(shù) proxy-target-class 的值修改為 false,以切換到使用JDK 動態(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 使用了自動裝配來根據(jù)依賴裝配組件,組件是否成為Bean 決定了 AOP 是否可以切入,在嘗試通過 AOP 切入 Spring Bean 的時候要注意加上上一講的兩個案例,我就把 IoC 和 AOP 相關(guān)的坑點和你說清楚了。除此之外,我們在業(yè)務(wù)開發(fā)時,還有一個繞不開的點是,Spring 程序的配置問題。接下來,我們就看具體吧。

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

相關(guān)文章

  • JPA中@ElementCollection使用示例詳解

    JPA中@ElementCollection使用示例詳解

    在JPA中,@ElementCollection注解主要用于映射集合屬性,例如List、Set或數(shù)組等集合屬性,以及Map結(jié)構(gòu)的集合屬性,每個屬性值都有對應(yīng)的key映射,這篇文章主要介紹了JPA中@ElementCollection使用,需要的朋友可以參考下
    2023-11-11
  • 在SpringBoot中使用lombok的注意事項

    在SpringBoot中使用lombok的注意事項

    這篇文章主要介紹了在SpringBoot中使用lombok的注意事項,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • mybatis-plus查詢源碼詳解

    mybatis-plus查詢源碼詳解

    這篇文章主要介紹了mybatis-plus查詢源碼解讀,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-03-03
  • Java中的反射機(jī)制詳解

    Java中的反射機(jī)制詳解

    這篇文章主要介紹了Java中的反射機(jī)制詳解的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • 一篇文章輕松了解SpringBoot配置高級

    一篇文章輕松了解SpringBoot配置高級

    大家都知道SpringBoot擁有良好的基因,還能簡化編碼、配置、部署、監(jiān)控,也是現(xiàn)在面試必問的一個點,下面這篇文章主要給大家介紹了如何通過一篇文章輕松了解SpringBoot配置高級的相關(guān)資料,需要的朋友可以參考下
    2022-11-11
  • java 服務(wù)器接口快速開發(fā)之servlet詳細(xì)教程

    java 服務(wù)器接口快速開發(fā)之servlet詳細(xì)教程

    Servlet(Server Applet)是Java Servlet的簡稱,稱為小服務(wù)程序或服務(wù)連接器,用Java編寫的服務(wù)器端程序,具有獨立于平臺和協(xié)議的特性,主要功能在于交互式地瀏覽和生成數(shù)據(jù),生成動態(tài)Web內(nèi)容
    2021-06-06
  • Maven倉庫無用文件和文件夾清理的方法實現(xiàn)

    Maven倉庫無用文件和文件夾清理的方法實現(xiàn)

    這篇文章主要介紹了Maven倉庫無用文件和文件夾清理的方法實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • JAVA實現(xiàn)打印ascii碼表代碼

    JAVA實現(xiàn)打印ascii碼表代碼

    這篇文章主要介紹了JAVA實現(xiàn)打印ascii碼表代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • java生成隨機(jī)數(shù)的常用方法分析

    java生成隨機(jī)數(shù)的常用方法分析

    這篇文章主要介紹了java生成隨機(jī)數(shù)的常用方法,結(jié)合實例形式分析了java生成隨機(jī)數(shù)常用的方法功能與相關(guān)使用技巧,需要的朋友可以參考下
    2017-10-10
  • SpringBoot使用注解集成Redis緩存的示例代碼

    SpringBoot使用注解集成Redis緩存的示例代碼

    這篇文章主要介紹了在?Spring?Boot?中使用注解集成?Redis?緩存的步驟,包括添加依賴、創(chuàng)建相關(guān)配置類、需要緩存數(shù)據(jù)的類(TestService)以及測試方法(如在?Controller?中的?redisTest?和?redisCache?方法),還對一些關(guān)鍵代碼和配置進(jìn)行了詳細(xì)說明
    2025-01-01

最新評論

石泉县| 天镇县| 双牌县| 桂阳县| 安多县| 阿拉尔市| 山东省| 郴州市| 巴东县| 盐源县| 竹山县| 高淳县| 满城县| 东莞市| 沾化县| 鲁甸县| 铁力市| 汝南县| 宁晋县| 贵溪市| 太保市| 普陀区| 金堂县| 民权县| 县级市| 循化| 桂林市| 泸州市| 黎城县| 盐山县| 平湖市| 鹿邑县| 普陀区| 石屏县| 定结县| 南部县| 霍城县| 神木县| 荣昌县| 霍林郭勒市| 拜泉县|