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

鑒權(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ù)載均衡的話,應(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)?,ApacheHttpClient 類(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)文章

  • JPA中@ElementCollection使用示例詳解

    JPA中@ElementCollection使用示例詳解

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

    在SpringBoot中使用lombok的注意事項(xiàng)

    這篇文章主要介紹了在SpringBoot中使用lombok的注意事項(xiàng),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • mybatis-plus查詢(xún)?cè)创a詳解

    mybatis-plus查詢(xún)?cè)创a詳解

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

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

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

    一篇文章輕松了解SpringBoot配置高級(jí)

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

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

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

    Maven倉(cāng)庫(kù)無(wú)用文件和文件夾清理的方法實(shí)現(xiàn)

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

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

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

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

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

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

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

最新評(píng)論

南平市| 大港区| 聂拉木县| 黄龙县| 长宁区| 阿荣旗| 辉县市| 东平县| 宣化县| 隆德县| 汨罗市| 文成县| 金秀| 云霄县| 临猗县| 黄龙县| 婺源县| 汉沽区| 芦溪县| 武强县| 蒙阴县| 达孜县| 昌图县| 金川县| 紫云| 珲春市| 阿坝县| 尚志市| 河曲县| 民权县| 镇平县| 蓬安县| 滨州市| 潼关县| 清水县| 台东县| 漳州市| 乌海市| 同心县| 宜宾市| 依安县|