赞
踩
在之前的示例中是使用RestTemplate实现RESTAPI调用的,代码如下。
public User findById(Long id) {
return this.restTemplate.getForObject("http://microservice-provider-user"/+id,User.class);
}
上面的方式使用的是拼接字符串的方式构造的URL的,该URL只有一个参数,然而在现实中,URL往往有多个参数,这个时候使用上面的方式就显得很低效,难以维护,比如请求的URL如下。
http://localhost:8010/search?name=张三&username=李四&age=24
对其进行字符串的拼接方式构建URL,如下:
public User[] fingfById(String name,String username,Integer age) {
Map<String,Object> paramMap = new HashMap();
paramMap.put("name",name);
paramMap.put("username",username);
paranMap.put("age",age);
return this.restTemplate.getForObject("http://microservice-provider-user/search?name={name}&username={username}&age={age}",User[].class,paramMap);
}
当参数越来越多,URL会变得越来越复杂,同时代码会变得难以维护。
在pom中添加Feign依赖
<dependency>
<groupId>org.springframwork.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
创建接口UserFeignClient
@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient {
@RequestMapping("/{id}")
public User findById(@PathVariable("id")Long id);
}
@FeignClient注解中的name是一个任意客户端名称,用于创建Ribbon的负载均衡器,但由于在项目中使用了Eureka,所以Ribbon会把name的值(microservice-provider-user)解析成Eureka Server服务注册表中的服务。
如果没有使用Eureka可以在配置中(application.yml)配置。
service:
ribbon:
listOfServers: 服务器列表
或者使用URL属性指定请求的URL(URL可以是完整的URL或者是主机名)
@FeignClient(name = "microservice-provider-user",url = "http://localhost:8000/")
修改MovieController,让其调用Feign接口
@RestController
public class MovieController {
@Autowired
private UserFeignClient userFeignClient;
@GetMapping("/user/{id}")
public User findByUser(@PathVariable Long id) {
return userFeignClient.findById(id);
}
}
修改启动类添加**@EnableFeignClients **注解
@SpringBootApplication
@EnableFeignClients
public class ConsumerMovieApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerMovieApplication.class, args);
}
}
测试
创建配置类FeignConfiguration
/** * 该类为Feign的配置类, * 注:该类可以不写@Configuration注解, * 如果加了该注解,那么该类就不能放在主应用程序上下文@ComponentScan所扫描的包中了。 */ @Configuration public class FeignConfiguration { /** * 将契约改为feign原生的默认契约,这样就可以使用feign自带的注解了 * @return 默认的feign契约 */ @Bean public Contract feignContract() { return new feign.Contract.Default(); } }
如果将带有**@Configuration的配置类存放在主应用程序上下文@ComponentScan所扫描的包中,那么该类中的配置的feign.Decoder**,feign.Encoder,feign.Contract等配置就会被所有的**@FeignClient**共享。
修改接口UserFeignClient
/**
* 使用@FeignClient的configuration属性指定配置类,
* 同时,将findById上的Spring MVC注解修改为Feign自带的注解@RequestLine
*/
@FeignClient(name = "microservice-provider-user",configuration = FeignConfiguration.class)
public interface UserFeignClient {
//"GET /{id}"之间一定要有一个空格否则会报错
@RequestLine("GET /{id}")
public User findById(@Param("id") Long id);
}
启动microservice-discovery-eureka
启动microservice-provider-user
启动microservice-consumer-movie-feign-customizing
访问http://localhost:8010/user/1,访问成功说明已经实现Feign配置的自定义。
注解**@EnableFeignClients提供了defaultConfiguration**属性,用来指定默认的配置类。如
@EnableFeignClients(defaultConfiguration = DefaultRibbonConfig.class)
对于指定名称的Feign Client(本例中Feign Client的名称为feignName),配置如下:
feign: client: config: feignName: #相当于Request.Options connectTimeout: 5000 #相当于Request.Options readTimeout: 5000 #配置Feign的日志级别,相当于代码配置方式中的Logger loggerLevel: full #Feign的错误解码器,相当于代码配置中的ErrorDecoder errorDecoder: com.example.SimpleErrorDecoder #配置重试,相当于代码配置方式中的Retryer retryer: com.example.SimpleRetryer #配置拦截器,相当于代码配置方式中的RequestInterceptor requestInterceptors: - com.example.FooRequestInterceptor - com.example.BarRequestInterceptor decode404: false
如果想配置所有的Feign Client,该如何配置。配置如下。
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
注:属性配置的方式比Java代码配置的优先级更高,如果想让Java代码配置方式优先于属性,可配置属性:feign.client.default-to-properties=false
。
添加security依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
创建Spring Security配置类
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) /** * @EnableGlobalMethodSecurity(securedEnabled=true) 开启@Secured 注解过滤权限 * @EnableGlobalMethodSecurity(jsr250Enabled=true)开启@RolesAllowed 注解过滤权限 * @EnableGlobalMethodSecurity(prePostEnabled=true) 使用表达式时间方法级别的安全性 */ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { //所有的请求都要经过HTTP basic认证 http.authorizeRequests().anyRequest().authenticated().and().httpBasic(); } @Bean public PasswordEncoder passwordEncoder() { // 明文编码器,这是一个不做任何操作的密码编码器,是Spring提供给我们做明文测试用的 return NoOpPasswordEncoder.getInstance(); } @Autowired private CustomUserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(this.userDetailsService).passwordEncoder(this.passwordEncoder()); } @Component class CustomUserDetailsService implements UserDetailsService { /** * 模拟两个账号: * 账号user,密码是password1.角色是user-role * 账号admin,密码是password2,角色是admin-role * @param s * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { if ("user".equals(s)) { return new SecurityUser("user", "password1", "user-role"); } else if ("admin".equals(s)) { return new SecurityUser("admin", "password2", "admin-role"); } else { return null; } } } class SecurityUser implements UserDetails { private static final long serialVersionUID = 1L; public SecurityUser(String username, String password, String role) { super(); this.username = username; this.password = password; this.role = role; } public SecurityUser() { } private Long id; private String username; private String password; private String role; @Override public Collection<? extends GrantedAuthority> getAuthorities() { Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); SimpleGrantedAuthority authority = new SimpleGrantedAuthority(this.role); authorities.add(authority); return authorities; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.username; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } } }
上面定义了两个账号,user和admin,密码分别为password1,password2,角色本别为user-role和admin-role。
修改Controller,在其中打印当前登录的用户信息。
@RestController public class UserController { private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class); @Autowired private UserRepository userRepository; @GetMapping("/{id}") public User findById(@PathVariable Long id) { Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { UserDetails user = (UserDetails)principal; Collection<? extends GrantedAuthority> collection = user.getAuthorities(); for (GrantedAuthority c : collection) { //打印当前登录用户的信息 UserController.LOGGER.info("当前用户为{},角色为{}",user.getUsername(),c.getAuthority()); } }else { //do other things } User user = userRepository.findOne(id); return user; } }
测试
去除UserFeignClient接口上的**@FeignClient**注解
去除启动类的**@EnableFeignClients**注解
修改MovieController
@Import(FeignClientsConfiguration.class) @RestController public class MovieController { //下面两个属性分别对应user账号,和admin账号 private UserFeignClient userUserFeignClient; private UserFeignClient adminUserFeignClient; @Autowired public MovieController(Decoder decoder, Encoder encoder, Client client, Contract contract) { this.userUserFeignClient = Feign.builder().client(client).encoder(encoder) .decoder(decoder).contract(contract) .requestInterceptor(new BasicAuthRequestInterceptor("user","password1")) .target(UserFeignClient.class,"http://microservice-provider-user/"); this.adminUserFeignClient = Feign.builder().client(client).encoder(encoder) .decoder(decoder).contract(contract) .requestInterceptor(new BasicAuthRequestInterceptor("admin","password2")) .target(UserFeignClient.class,"http://microservice-provider-user/"); } @GetMapping("/user-user/{id}") public User findByIdUser(@PathVariable Long id) { return this.userUserFeignClient.findById(id); } @GetMapping("/user-admin/{id}") public User findByIdAdmin(@PathVariable Long id) { return this.adminUserFeignClient.findById(id); } }
@Import导入的FeignClientsConfiguration是Spring Cloud为Feign默认提供的配置类。
userUserFeignClient登录账号user,adminUserFeignClient登录账号admin,但是他们使用的是同一个接口。
测试
Feign支持继承,使用继承后,可以将一些公共操作分组到一些父接口中,从而简化Feign,如下
基础接口:UserService.java
public interface UserService {
@RequestMapping(method = RequestMethod.GET,value = "/users/{id}")
User getUser(@PathVariable("id") long id);
}
服务提供者:UserResource.java
@RestController
public class UserResource implements UserService {
//...
}
服务消费者:UserClient.java
@FeignClient("users")
public interface UserClient extends UserService {
}
注:尽管Feign的继承可帮助我们进一步简化Feign开发,但是Spring Cloud官方,不建议在服务器端与客户端之间共享接口,因为这种方式造成了服务器端与客户端代码的紧耦合。并且,Feign本身并不使用Spring MVC的工作机制(方法参数映射不被继承)
在某些场景下,可能需要对请求或响应进行压缩,可使用以下属性启用Feign的压缩功能。
feign:
compression:
request:
enabled: true
response:
enabled: true
对于请求的压缩,Feign还提供了更为详细的设置,如:
feign:
compression:
request:
enable: true
mime-types: text/xml,application/xml,application/json
# feign.compression.request.mine-types用于支持的媒体类型列表,默认是text/xml,application/xml以及application/json
min-request-size: 2048
# feign.compression.request.mine-request-size用于设置请求的最小阈值,默认是2048
编写Feign配置类
@Configuration
public class FeignConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
修改UserFeignClient接口。
@FeignClient(name = "microservice-provider-user",configuration = FeignConfiguration.class)
//使用configuration属性配置自定义日志类
public interface UserFeignClient {
@RequestMapping("/{id}")
public User findById(@PathVariable("id")Long id);
}
在application.yml中添加信息
logging:
level:
# 将Feign接口的日志级别设置为DEBUG,因为Feign的Logger.Lever只对DEBUG做出响应
com.ym.cloud.study.feign.UserFeignClient: DEBUG
测试
启动microservice-discovery-eureka
启动microservice-provider-user
启动microservice-consumer-movie-feign-logging
访问http://localhost:8010/user/1,观察控制台可看到如下日志:
2020-02-09 16:40:00.787 DEBUG 13220 --- [nio-8010-exec-3] c.ym.cloud.study.feign.UserFeignClient : [UserFeignClient#findById] ---> GET http://microservice-provider-user/1 HTTP/1.1
2020-02-09 16:40:00.787 DEBUG 13220 --- [nio-8010-exec-3] c.ym.cloud.study.feign.UserFeignClient : [UserFeignClient#findById] ---> END HTTP (0-byte body)
2020-02-09 16:40:00.787 DEBUG 13220 --- [nio-8010-exec-3] c.ym.cloud.study.feign.UserFeignClient : [UserFeignClient#findById] <--- HTTP/1.1 200 (7ms)
2020-02-09 16:40:00.787 DEBUG 13220 --- [nio-8010-exec-3] c.ym.cloud.study.feign.UserFeignClient : [UserFeignClient#findById] content-type: application/json;charset=UTF-8
2020-02-09 16:40:00.787 DEBUG 13220 --- [nio-8010-exec-3] c.ym.cloud.study.feign.UserFeignClient : [UserFeignClient#findById] date: Sun, 09 Feb 2020 08:40:00 GMT
2020-02-09 16:40:00.787 DEBUG 13220 --- [nio-8010-exec-3] c.ym.cloud.study.feign.UserFeignClient : [UserFeignClient#findById] transfer-encoding: chunked
2020-02-09 16:40:00.787 DEBUG 13220 --- [nio-8010-exec-3] c.ym.cloud.study.feign.UserFeignClient : [UserFeignClient#findById] x-application-context: microservice-provider-user:8001
2020-02-09 16:40:00.787 DEBUG 13220 --- [nio-8010-exec-3] c.ym.cloud.study.feign.UserFeignClient : [UserFeignClient#findById]
2020-02-09 16:40:00.787 DEBUG 13220 --- [nio-8010-exec-3] c.ym.cloud.study.feign.UserFeignClient : [UserFeignClient#findById] {"id":1,"username":"account1","name":"张三","age":20,"balance":100.00}
2020-02-09 16:40:00.787 DEBUG 13220 --- [nio-8010-exec-3] c.ym.cloud.study.feign.UserFeignClient : [UserFeignClient#findById] <--- END HTTP (72-byte body)
从Spring Cloud Edgware开始,可以使用配置属性直接定义Feign的日志级别,如下
feign:
client:
config:
microservice-provider-user:
loggerLevel: full
logging:
level:
com.ym.cloud.study.feign.UserFeignClient: DEBUG
修改UserController,此处添加get/post请求演示
@GetMapping("/get")
public User getUser(User user) {
return user;
}
@PostMapping("post")
public User postUser(@RequestBody User user) {
return user;
}
修改MovieController
/** * 测试路径为http://localhost:8010/get/user?id=xxx&username=xxx * @param user * @return用户信息 */ @GetMapping("/get/user") public User getUser(User user) { return this.userFeignClient.get1(user.getId(),user.getUsername()); } /** * 当请求参数比较多时,可以使用Map来构建 * 测试路径为http://localhost:8010/get/user2?id=xxx&username=xxx * @param user * @return用户信息 */ @GetMapping("get/user2") public User getUserMap(User user) { Map<String,Object> userMap = new HashMap<>(); userMap.put("id",user.getId()); userMap.put("username",user.getUsername()); return userFeignClient.get2(userMap); } /** * 测试路径为http://localhost:8010/post?id=xxx&username=xxx * @param user * @return用户信息 */ @GetMapping("/post") public User postUser(User user) { return userFeignClient.postUser(user); }
修改UserFeignClient
@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient {
@RequestMapping("/{id}")
public User findById(@PathVariable("id")Long id);
//下面注解的值在@FeignClient的name属性对应的服务中要能找到,否则会报错
@GetMapping("/get")
public User get1(@RequestParam("id") Long id,@RequestParam("username") String username);
@GetMapping("/get")
public User get2(@RequestParam Map<String,Object> userMap);
@PostMapping("/post")
public User postUser(@RequestBody User user);
}
ost")
public User postUser(User user) {
return userFeignClient.postUser(user);
}
```
修改UserFeignClient
@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient {
@RequestMapping("/{id}")
public User findById(@PathVariable("id")Long id);
//下面注解的值在@FeignClient的name属性对应的服务中要能找到,否则会报错
@GetMapping("/get")
public User get1(@RequestParam("id") Long id,@RequestParam("username") String username);
@GetMapping("/get")
public User get2(@RequestParam Map<String,Object> userMap);
@PostMapping("/post")
public User postUser(@RequestBody User user);
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。