pom.xmljava
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>microservice-consumer-movie-feign</artifactId> <packaging>jar</packaging> <parent> <groupId>com.itmuch.cloud</groupId> <artifactId>microservice-spring-cloud</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> </dependencies> </project>
Feign類git
package com.itmuch.cloud.feign; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.itmuch.cloud.entity.User; @FeignClient("microservice-provider-user") public interface UserFeignClient { @RequestMapping(value = "/simple/{id}", method = RequestMethod.GET) //@GetMapping("/simple/{id}") public User findById(@PathVariable("id") Long id); // 兩個坑:1. @GetMapping不支持 2. @PathVariable得設置value }
圖文:github
pom.xmlweb
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>microservice-consumer-movie-feign-customizing</artifactId> <packaging>jar</packaging> <parent> <groupId>com.itmuch.cloud</groupId> <artifactId>microservice-spring-cloud</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> </dependencies> </project>
配置文件spring
spring: application: name: microservice-consumer-movie-feign-customizing server: port: 7901 eureka: client: healthcheck: enabled: true serviceUrl: defaultZone: http://user:password123@localhost:8761/eureka instance: prefer-ip-address: true logging: level: com.itmuch.cloud.feign.UserFeignClient: DEBUG # 解決第一次請求報超時異常的方案: # hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000 # 或者: # hystrix.command.default.execution.timeout.enabled: false # 或者: feign.hystrix.enabled: false ## 索性禁用feign的hystrix支持 # 超時的issue:https://github.com/spring-cloud/spring-cloud-netflix/issues/768 # 超時的解決方案: http://stackoverflow.com/questions/27375557/hystrix-command-fails-with-timed-out-and-no-fallback-available # hystrix配置: https://github.com/Netflix/Hystrix/wiki/Configuration#execution.isolation.thread.timeoutInMilliseconds
configsql
package com.itmuch.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import feign.Contract; @Configuration public class FooConfiguration { @Bean public Contract feignContract() { return new feign.Contract.Default(); } }
實體apache
package com.itmuch.cloud.entity; import java.math.BigDecimal; public class User { private Long id; private String username; private String name; private Short age; private BigDecimal balance; public Long getId() { return this.id; } public void setId(Long id) { this.id = id; } public String getUsername() { return this.username; } public void setUsername(String username) { this.username = username; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Short getAge() { return this.age; } public void setAge(Short age) { this.age = age; } public BigDecimal getBalance() { return this.balance; } public void setBalance(BigDecimal balance) { this.balance = balance; } }
Feignjson
package com.itmuch.cloud.feign; import org.springframework.cloud.netflix.feign.FeignClient; import com.itmuch.cloud.entity.User; import com.itmuch.config.FooConfiguration; import feign.Param; import feign.RequestLine; @FeignClient(name = "microservice-provider-user", configuration = FooConfiguration.class) public interface UserFeignClient { @RequestLine("GET /simple/{id}") public User findById(@Param("id") Long id); }
Controllerapp
package com.itmuch.cloud.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import com.itmuch.cloud.entity.User; import com.itmuch.cloud.feign.UserFeignClient; @RestController public class MovieController { @Autowired private UserFeignClient userFeignClient; @GetMapping("/movie/{id}") public User findById(@PathVariable Long id) { return this.userFeignClient.findById(id); } }
啓動類負載均衡
package com.itmuch.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; @SpringBootApplication @EnableEurekaClient @EnableFeignClients public class ConsumerMovieFeignApplication { public static void main(String[] args) { SpringApplication.run(ConsumerMovieFeignApplication.class, args); } }
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>microservice-provider-user-with-auth</artifactId> <packaging>jar</packaging> <name>microservice-provider-user-with-auth</name> <description>Demo project for Spring Boot</description> <parent> <groupId>com.itmuch.cloud</groupId> <artifactId>microservice-spring-cloud</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies> </project>
配置文件:
server: port: 7900 spring: jpa: generate-ddl: false show-sql: true hibernate: ddl-auto: none datasource: platform: h2 schema: classpath:schema.sql data: classpath:data.sql application: name: microservice-provider-user-with-auth logging: level: root: INFO org.hibernate: INFO org.hibernate.type.descriptor.sql.BasicBinder: TRACE org.hibernate.type.descriptor.sql.BasicExtractor: TRACE com.itmuch: DEBUG eureka: client: healthcheck: enabled: true serviceUrl: defaultZone: http://user:password123@localhost:8761/eureka instance: prefer-ip-address: true instance-id: ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}} metadata-map: zone: ABC # eureka能夠理解的元數據 lilizhou: BBC # 不會影響客戶端行爲 lease-renewal-interval-in-seconds: 5
Spring Security的配置類
package com.itmuch.cloud.entity; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.ArrayList; import java.util.Collection; public class SecurityUser implements UserDetails { private static final long serialVersoinUID = 1L; private Long id; private String username; private String password; private String role; public SecurityUser() { } public SecurityUser(String username, String password, String role) { this.username = username; this.password = password; this.role = 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 String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } 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; } }
package com.itmuch.cloud.microserviceprovideruserwithauth.security; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; @Component public class CustomUserDetailsService implements UserDetailsService { /** * 模擬兩個帳戶 * ① 帳號是user,密碼是password1,角色是user-role * ② 帳號時候admin,密碼是password1,角色是admin-role * @param username * 用戶名 * @return * * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if ("user".equals(username)) { return new SecurityUser("user", "password1", "user-role"); } else if ("admin".equals(username)) { return new SecurityUser("admin", "password2", "admin-role"); } else { return null; } } }
package com.itmuch.cloud.microserviceprovideruserwithauth.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { // 全部的請求,都須要通過HTTP basic認證 http .authorizeRequests() .anyRequest().authenticated() .and() .httpBasic(); } @Bean public PasswordEncoder passwordEncoder() { // 明文編碼器。這個一個不作任何操做的密碼編碼器,是Spring提供給咱們作明文測試的 return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(this.userDetailsService).passwordEncoder(this.passwordEncoder()); } }
修改Controller,在其中打印當前登陸的用戶信息
package com.itmuch.cloud.controller; import com.itmuch.cloud.repository.UserRepository; import com.itmuch.cloud.entity.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import java.util.Collection; @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> collections = user.getAuthorities(); for (GrantedAuthority ga: collections) { // 打印當前登陸用戶的信息 UserController.LOGGER.info("當前用戶是{}, 角色是{}", user.getUsername(), ga.getAuthority()); } } else { UserController.LOGGER.warn("ε=(´ο`*)))唉,出現問題了"); } User findOne = userRepository.findOne(id); return findOne; } }
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>microservice-consumer-movie-feign-manual</artifactId> <packaging>jar</packaging> <parent> <groupId>com.itmuch.cloud</groupId> <artifactId>microservice-spring-cloud</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> </dependencies> </project>
配置文件
spring: application: name: microservice-consumer-movie-feign-manual server: port: 7901 eureka: client: healthcheck: enabled: true serviceUrl: defaultZone: http://user:password123@localhost:8761/eureka instance: prefer-ip-address: true ribbon: eureka: enabled: true
實體類
package com.itmuch.cloud.entity; import java.math.BigDecimal; public class User { private Long id; private String username; private String name; private Short age; private BigDecimal balance; public Long getId() { return this.id; } public void setId(Long id) { this.id = id; } public String getUsername() { return this.username; } public void setUsername(String username) { this.username = username; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Short getAge() { return this.age; } public void setAge(Short age) { this.age = age; } public BigDecimal getBalance() { return this.balance; } public void setBalance(BigDecimal balance) { this.balance = balance; } }
Feign類
package com.itmuch.cloud.feign; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.itmuch.cloud.entity.User; public interface UserFeignClient { @RequestMapping(value = "/{id}", method = RequestMethod.GET) public User findById(@PathVariable("id") Long id); }
package com.itmuch.cloud.controller; import com.itmuch.cloud.feign.UserFeignClient; import com.itmuch.cloud.entity.User; import feign.Client; import feign.Contract; import feign.Feign; import feign.auth.BasicAuthRequestInterceptor; import feign.codec.Decoder; import feign.codec.Encoder; import org.springframework.cloud.netflix.feign.FeignClientsConfiguration; import org.springframework.context.annotation.Import; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @Import(FeignClientsConfiguration.class) // Spring Cloud爲Feign默認提供的配置類 @RestController public class MovieController { private UserFeignClient userUserFeignClient; private UserFeignClient adminUserFeignClient; 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-with-auth/"); this.adminUserFeignClient = Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract) .requestInterceptor(new BasicAuthRequestInterceptor("admin", "password2")) .target(UserFeignClient.class, "http://microservice-provider-user-with-auth/"); } @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); } }
圖文:
Feign還支持繼承,將一些公共操做弄到父接口,從而簡化開發
好比,先寫一個基礎接口:UserService.java
public interface UserService { @RequestMapping(method=RequestMethod.GET,value="/user/{id}") User getUser(@PathVariable("id") long id); }
服務提供者Controller:UserResource.java
@RestController public class UserResource implements UserService { //... }
服務消費者:UserClient.java
@FeignClient("users") public interface UserClient extends UserService { }
feign: compression: mime-types: text/xml,application/xml,application/json request: enable: true min-request-size: 2048 response: enable: true
Feign配置類
package com.itmuch.cloud.conf; import feign.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Created by 2YSP on 2018/7/18. */ @Configuration public class FeignLogConfiguration { /** * NONE:不記錄任何日誌(默認) * BASIC:僅記錄請求方法、URL、響應狀態代碼以及執行時間 * HEADERS:記錄BASIC級別的基礎上,記錄請求和響應的header * FULL:記錄請求和響應的header,body和元數據 * @return */ @Bean Logger.Level feignLoggerLevel(){ return Logger.Level.FULL; } }
修改Feign,使用指定配置類
package com.itmuch.cloud.feign; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.itmuch.cloud.conf.FeignLogConfiguration; import com.itmuch.cloud.entity.User; @FeignClient( name = "microservice-provider-user",configuration = FeignLogConfiguration.class) public interface UserFeignClient { @RequestMapping(value = "/simple/{id}", method = RequestMethod.GET) //@GetMapping("/simple/{id}") public User findById(@PathVariable("id") Long id); // 兩個坑:1. @GetMapping不支持 2. @PathVariable得設置value @RequestMapping(value = "/user", method = RequestMethod.POST) public User postUser(@RequestBody User user); // 該請求不會成功,只要參數是複雜對象,即便指定了是GET方法,feign依然會以POST方法進行發送請求。多是我沒找到相應的註解或使用方法錯誤。 // 如勘誤,請@lilizhou2008 eacdy0000@126.com @RequestMapping(value = "/get-user", method = RequestMethod.GET) public User getUser(User user); }
application.yml中添加以下內容,設置日誌級別,注意:Feign的日誌打印只會對DEBUG級別作出響應
spring: application: name: microservice-consumer-movie-feign server: port: 7901 eureka: client: healthcheck: enabled: true serviceUrl: defaultZone: http://user:password123@localhost:8761/eureka instance: prefer-ip-address: true logging: level: com.itmuch.cloud.feign.UserFeignClient: DEBUG
當咱們用Get請求多參數的URL的時候,好比:http://microservice-provider-user/get?id=1&username=zhangsan,可能會採起以下的方式
@FeignClient(name = "microservice-provider-user") public interface UserFeignClient { @RequestMapping(value = "/get",method = RequestMethod.GET) User get0(User user); }
正確處理方式一:使用@RequestParam註解
@RequestMapping(value = "/get",method = RequestMethod.GET) User get1(@RequestParam("id") Long id,@RequestParam("username") String username);
可是這種方法也有個缺點,若是參數比較多就要寫很長的參數列表。
正確處理方式二:使用map接收
@RequestMapping(value = "/get",method = RequestMethod.GET) User get2(Map<String,Object> map);
處理方式三:若是請求方式沒有限制的話,換成POST方式
@RequestMapping(value = "/get",method = RequestMethod.POST) User get3(User user);