SpringCloud微服务完整实例
转自:https://blog.csdn.net/ittechnologyhome/article/details/73824784,侵删一微服务架构概述1.1 微服务特性以及优点每个服务可以独立运行在自己的进程里一系列独立运行的微服务(goods,order,pay,user,search…)共同构建了整个系统每个服务为独立的业务开发,一个微服务只关注某个特定的功能,例如...
转自:https://blog.csdn.net/ittechnologyhome/article/details/73824784,侵删
一微服务架构概述
1.1 微服务特性以及优点
- 每个服务可以独立运行在自己的进程里
- 一系列独立运行的微服务(goods,order,pay,user,search…)共同构建了整个系统
- 每个服务为独立的业务开发,一个微服务只关注某个特定的功能,例如用户管理,商品管理微服务
- 微服务之间通过一些轻量级的通信机制进行通讯,例如通过Restful API进行调用
- 技术栈不受限:可以使用不同的开发语言和数据存储技术
- 全自动的部署机制
- 按需伸缩:根据需求和应用场景,实现细粒度的水平扩展
1.2 微服务带来的挑战
- 运维要求较高
- 分布式的复杂性
- 接口调整成本较高
1.3 微服务设计原则
- 单一职责原则
- 服务自治原则
- 轻量级通讯机制
- 微服务粒度
1.4 微服务开发框架
- SpringCloud:众多组件构造完善的分布式系统
- Dubbo/Dubbox:关注服务治理
- Dropwizard:关注单个微服务开发
二 SpringCloud概述与开发环境
2.1 SpringCloud概述
SpringCloud是基于SpringBoot之上的用来快速构建微服务系统的工具集,拥有功能完善的轻量级微服务组件,例如服务治理(Eureka),声明式REST调用(Feign),客户端负载均衡(Ribbon),服务容错(Hystrix),服务网关(Zuul)以及服务配置(Spring Cloud Config),服务跟踪(Sleuth)等等。
官网链接:http://projects.spring.io/spring-cloud/
目前主流的版本为SpringBoot1.4.5.RELEAE和SpringCloudCamden.SR7,
Maven pom配置如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.5.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId></groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
2.2 开发环境
MacOS10.12+JDK8u131+IntelliJ IDEA2017.1.4
Tomcat8.5+Maven3.3.9+Git2.12+Firefox54
Spring4.3.9.RELEASE+SpringBoot1.4.5.RELEASE+SpringCloud Camden.SR7
三 工程机器模块说明
3.1 工程说明
工程首先自定义了Maven父工程,其中定义如下的公共组件:
<?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>
<!-- 自定义工程的maven坐标-->
<groupId>com.ekeyfund.springcloud</groupId>
<artifactId>springcloud-parent</artifactId>
<version>2.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<!-- 基于SpringBoot 1.4.5.RELEASE-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.5.RELEASE</version>
<relativePath/>
</parent>
<!-- 定义引用类库版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Camden.SR7</spring-cloud.version>
<springcloud-parent.version>2.0.0-SNAPSHOT</springcloud-parent.version>
<druid.version>1.0.31</druid.version>
<jackson.version>2.8.8</jackson.version>
<commons-lang3.version>3.5</commons-lang3.version>
<ehcache.version>3.1.4</ehcache.version>
<hibernate.version>5.0.12.Final</hibernate.version>
<servlet-api.version>3.1.0</servlet-api.version>
<commons-collection4.version>4.1</commons-collection4.version>
<springframework.oxm.version>4.3.9.RELEASE</springframework.oxm.version>
</properties>
<!-- 引入SpringCloud微服务常用组件-->
<modules>
<module>springcloud-eureka-server</module>
<module>springcloud-eureka-server-ha</module>
<module>springcloud-provider-user-service</module>
<module>springcloud-consumer-h5-ribbon-hystrix</module>
<module>springcloud-consumer-h5-feign</module>
<module>springcloud-api-gateway</module>
<module>springcloud-consumer-h5</module>
<module>springcloud-config-server</module>
</modules>
<dependencies>
<!-- 服务发现组件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</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-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet-api.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${commons-collection4.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>${ehcache.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${springframework.oxm.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
3.2 模块说明
模块则借助IntelliJ IDEA结合Spring Initializer自动生成工程结构,主要模块和说明如下:
模块名称 | 模块说明 | 访问地址 |
---|---|---|
springcloud-eureka-server | 分布式服务注册中心(单点) | http://127.0.0.1:9999 |
springcloud-eureka-server-ha | 分布式服务注册中心(高可用版本) | http://127.0.0.1:9998 http://127.0.0.1:9997 |
springcloud-provider-user-service | 用户服务提供者 | http://127.0.0.1:9996/list http://127.0.0.1:9995/list |
springcloud-consumer-h5 | 用户服务调用者,采用原始的RestTemplate调用 | http://127.0.0.1:9991/user/get/4 |
springcloud-consumer-h5-ribbon-hystrix | 用户服务调用者,采用ribbon做客户端负载均衡 | http://127.0.0.1:9994/springcloud-provider-user-service |
springcloud-consumer-h5-feign | feign声明式服务调用者 | http://127.0.0.1:9993/list |
springcloud-gateway | 网关服务 | http://127.0.0.1:9992/api-a/list |
springcloud-config-server | 配置中心 | 待定 |
四 使用SpringBoot实现服务提供者
所属maven模块:springcloud-provider-user-service
基于SpringBoot的Web和JPA模块实现Restful API的常用方法
4.1 entity
主要包含User,Role,Department三个实体
Role.java
package com.ekeyfund.springcloud.entity;
import javax.persistence.*;
import java.io.Serializable;
/**
* Role Entity
*
* @author Liuguanglei liuguanglei@ekeyfund.com
* @create 2017-06-下午2:36
*/
@Entity
@Table(name = "springboot_role")
public class Role implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id")
private Long id;
@Column(name = "role_name")
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return new org.apache.commons.lang3.builder.ToStringBuilder(this)
.append("id", id)
.append("name", name)
.toString();
}
}
Department.java
package com.ekeyfund.springcloud.entity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.io.Serializable;
/**
* Department Entity
*
* @author Liuguanglei liuguanglei@ekeyfund.com
* @create 2017-06-下午2:31
*/
@Entity
@Table(name = "springboot_department")
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Department implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "department_id")
private Long id;
@Column(name = "department_name")
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("id", id)
.append("name", name)
.toString();
}
}
User.java
package com.ekeyfund.springcloud.entity;
import com.fasterxml.jackson.annotation.JsonBackReference;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* User Entity
*
* @author Liuguanglei liuguanglei@ekeyfund.com
* @create 2017-06-下午2:32
*/
@Entity
@Table(name = "springboot_user")
public class User implements Serializable{
@Id
@Column(name = "user_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_name")
private String name;
@Column(name = "user_password")
private String password;
@Column(name = "user_create_date")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createDate;
@ManyToOne
@JoinColumn(name = "department_id")
@JsonBackReference
private Department department;
@ManyToMany(cascade = {},fetch = FetchType.EAGER)
@JoinTable(name = "springboot_user_role",joinColumns = {@JoinColumn(name="user_id")},
inverseJoinColumns = {@JoinColumn(name = "role_id")}
)
private List<Role> roleList;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public List<Role> getRoleList() {
return roleList;
}
public void setRoleList(List<Role> roleList) {
this.roleList = roleList;
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("id", id)
.append("name", name)
.append("password", password)
.append("createDate", createDate)
.append("department", department)
.append("roleList", roleList)
.toString();
}
}
4.2 数据访问Repository
主要包含UserRepository和DepartmentRepository
DepartmentRepository.java
package com.ekeyfund.springcloud.repository;
import com.ekeyfund.springcloud.entity.Department;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* Created by tony on 2017/6/19.
*/
@Repository
public interface DepartmentRepository extends JpaRepository<Department,Long> {
}
UserRepoistory.java
package com.ekeyfund.springcloud.repository;
import com.ekeyfund.springcloud.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* User Repository
*
* @author Liuguanglei liuguanglei@ekeyfund.com
* @create 2017-06-下午2:54
*/
@Repository
public interface UserRepository extends JpaRepository<User,Long>{
/**
* and
* @param id
* @param name
* @return
*/
User findByIdAndName(Long id, String name);
User findByNameAndPassword(String name, String password);
/**
* or
* @param id
* @param name
* @return
*/
User findByIdOrName(Long id, String name);
/**
* between
* @param start
* @param end
* @return
*/
List<User> findByCreateDateBetween(Date start, Date end);
/**
* lessThan
* @param start
* @return
*/
List<User> getByCreateDateLessThan(Date start);
/**
* Greater Than
* @param start
* @return
*/
List<User> findByCreateDateGreaterThan(Date start);
/**
* is null
* @return
*/
List<User> findByNameIsNull();
/**
* in
* @param nameList
* @return
*/
List<User> findByNameIn(Collection<String> nameList);
}
4.3 业务逻辑Service
主要包含UserService,DepartmentService及其实现
UserService.java
package com.ekeyfund.springcloud.service;
import com.ekeyfund.springcloud.entity.User;
import java.util.List;
/**
* Created by tony on 2017/6/19.
*/
public interface UserService {
/**
* 登录
* @param name
* @param password
* @return
*/
public User login(String name, String password);
/**
* 注册
* @param user
* @return
*/
public User register(User user);
/**
* 注销
* @param user
* @return
*/
void writeOff(User user);
/**
* 当前用户是否已经存在
* @param user
* @return
*/
boolean isExists(User user);
List<User> getAllUser();
User getUserById(Long id);
}
UserServiceImpl.java
package com.ekeyfund.springcloud.service.impl;
import com.ekeyfund.springcloud.entity.User;
import com.ekeyfund.springcloud.repository.UserRepository;
import com.ekeyfund.springcloud.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.List;
/**
* User Service Impl
*
* @author Liuguanglei liuguanglei@ekeyfund.com
* @create 2017-06-下午3:34
*/
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public User login(String name, String password) {
return userRepository.findByNameAndPassword(name,password);
}
@Override
public User register(User user) {
return userRepository.save(user);
}
@Override
public void writeOff(User user) {
userRepository.delete(user);
}
@Override
public boolean isExists(User user) {
return userRepository.findOne(user.getId())!=null?true:false;
}
@Override
public List<User> getAllUser() {
return userRepository.findAll();
}
@Override
public User getUserById(Long id) {
return userRepository.findOne(id);
}
}
DepartmentService.java
package com.ekeyfund.springcloud.service;
import com.ekeyfund.springcloud.entity.Department;
/**
* Created by tony on 2017/6/19.
*/
public interface DepartmentService {
Department saveDepartment(Department department);
Department getDepartmentById(Long id);
}
DepartmentServiceImpl.java
package com.ekeyfund.springcloud.service.impl;
import com.ekeyfund.springcloud.entity.Department;
import com.ekeyfund.springcloud.repository.DepartmentRepository;
import com.ekeyfund.springcloud.service.DepartmentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
/**
* Department Impl
*
* @author Liuguanglei liuguanglei@ekeyfund.com
* @create 2017-06-下午3:12
*/
@Transactional
@Service
public class DepartmentImpl implements DepartmentService {
@Autowired
private DepartmentRepository departmentRepository;
@Override
public Department saveDepartment(Department department) {
return departmentRepository.save(department);
}
@Override
public Department getDepartmentById(Long id) {
return departmentRepository.findOne(id);
}
}
4.4 Controller层
主要包含提供User完整的Restful API 的UserController
UserController.java
package com.ekeyfund.springcloud.controller;
import com.ekeyfund.springcloud.entity.User;
import com.ekeyfund.springcloud.service.UserService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
/**
* UserController
* Restful API
* @author Liuguanglei liuguanglei@ekeyfund.com
* @create 2017-06-下午11:24
*/
@RestController
public class UserController {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(UserController.class);
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private UserService userService;
@GetMapping(value = "/list")
public List<User> list(){
ServiceInstance instance=discoveryClient.getLocalServiceInstance();
LOGGER.info("call user/list service host is "+instance.getHost()+"service_id is "+instance.getServiceId());
return userService.getAllUser();
}
@GetMapping(value = "/login")
public User login( @RequestParam String name,@RequestParam String password){
User user=userService.login(name,password);
return user;
}
@PostMapping("/register")
public String register(@ModelAttribute User user){
User result =userService.register(user);
return result!=null?"success":"fail";
}
@GetMapping("/get/{id}")
public User get(@PathVariable Long id){
return userService.getUserById(id);
}
@PutMapping("/update/{id}")
public String update(@PathVariable Long id,@ModelAttribute User user){
User updatedUser =userService.getUserById(id);
updatedUser.setName(user.getName());
updatedUser.setPassword(user.getPassword());
updatedUser.setCreateDate(new Date());
User result= userService.register(updatedUser);
return result!=null?"success":"fail";
}
@DeleteMapping("/delete/{id}")
public String delete(@PathVariable Long id){
User user =new User();
user.setId(id);
userService.writeOff(user);
return "success";
}
}
4.5 Configuration
主要包含数据源Druid和JPA的配置
DruidConfiguation.java
package com.ekeyfund.springcloud.configuration;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
/**
* Druid Configuration
*
* @author Liuguanglei liuguanglei@ekeyfund.com
* @create 2017-06-下午5:48
*/
public class DruidConfiguration {
@Bean
public ServletRegistrationBean statViewServle(){
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
//白名单:
servletRegistrationBean.addInitParameter("allow","192.168.1.218,127.0.0.1");
//IP黑名单 (存在共同时,deny优先于allow) : 如果满足deny的即提示:Sorry, you are not permitted to view this page.
servletRegistrationBean.addInitParameter("deny","192.168.1.100");
//登录查看信息的账号密码.
servletRegistrationBean.addInitParameter("loginUsername","druid");
servletRegistrationBean.addInitParameter("loginPassword","12345678");
//是否能够重置数据.
servletRegistrationBean.addInitParameter("resetEnable","false");
return servletRegistrationBean;
}
@Bean
public FilterRegistrationBean statFilter(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
//添加过滤规则.
filterRegistrationBean.addUrlPatterns("/*");
//添加不需要忽略的格式信息.
filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
JPAPersistenceConfiguration
package com.ekeyfund.springcloud.configuration;
import com.alibaba.druid.pool.DruidDataSource;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Properties;
/**
* JPA Persistence Configuration
*
* @author Liuguanglei liuguanglei@ekeyfund.com
* @create 2017-06-上午11:26
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@EnableTransactionManagement(proxyTargetClass = true) //启用JPA的事务管理
@EnableJpaRepositories(basePackages = "com.ekeyfund.springcloud.repository" )//启用JPA资源库并指定资源库接口位置
@EntityScan(basePackages = "com.ekeyfund.springcloud.entity")//指定实体的位置
public class JPAPersistenceConfiguration {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(JPAPersistenceConfiguration.class);
/*******************数据库和连接池配置信息,读取application.properties文件的属性值****************************/
@Value("${spring.datasource.driver-class-name}")
private String driverClass;
@Value("${spring.datasource.username}")
private String userName;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.initialSize}")
private int initialSize;
@Value("${spring.datasource.minIdle}")
private int minIdle;
@Value("${spring.datasource.maxActive}")
private int maxActive;
@Value("${spring.datasource.maxWait}")
private long maxWait;
@Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
private long timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.minEvictableIdleTimeMillis}")
private long minEvictableIdleTimeMillis;
@Value("${spring.datasource.filters}")
private String filters;
@Value("${spring.datasource.connectionProperties}")
private String connectionProperties;
@Bean(name = "druidDataSource",initMethod = "init",destroyMethod = "close")
public DataSource dataSource(){
DruidDataSource druidDataSource =new DruidDataSource();
druidDataSource.setDriverClassName(driverClass);
druidDataSource.setUsername(userName);
druidDataSource.setPassword(password);
druidDataSource.setUrl(url);
druidDataSource.setInitialSize(initialSize);
druidDataSource.setMinIdle(minIdle);
druidDataSource.setMaxActive(maxActive);
druidDataSource.setMaxWait(maxWait);
druidDataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
druidDataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
druidDataSource.setConnectionProperties(connectionProperties);
try {
druidDataSource.setFilters(filters);
} catch (SQLException e) {
LOGGER.error("build datasoure exception ",e.getMessage());
}
return druidDataSource;
}
@Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource druidDataSource){
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean =new LocalContainerEntityManagerFactoryBean();
localContainerEntityManagerFactoryBean.setDataSource(druidDataSource);
localContainerEntityManagerFactoryBean.setPackagesToScan("com.ekeyfund.springcloud.entity");
localContainerEntityManagerFactoryBean.setJpaProperties(buildHibernateProperties());
localContainerEntityManagerFactoryBean.setJpaDialect(new HibernateJpaDialect());
localContainerEntityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter(){
{
setDatabase(org.springframework.orm.jpa.vendor.Database.MYSQL);
setDatabasePlatform("org.hibernate.dialect.MySQL5Dialect");
}
});
return localContainerEntityManagerFactoryBean;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource druidDataSource, EntityManagerFactory entityManagerFactory){
JpaTransactionManager jpaTransactionManager=new JpaTransactionManager();
jpaTransactionManager.setDataSource(druidDataSource);
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory);
return jpaTransactionManager;
}
@Bean
PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor(){
return new PersistenceExceptionTranslationPostProcessor();
}
protected Properties buildHibernateProperties(){
Properties hibernateProperties =new Properties();
hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
hibernateProperties.setProperty("hibernate.hbm2ddl.auto","update");
hibernateProperties.setProperty("hibernate.show_sql", "false");
hibernateProperties.setProperty("hibernate.use_sql_comments", "false");
hibernateProperties.setProperty("hibernate.format_sql", "true");
hibernateProperties.setProperty("hibernate.generate_statistics", "false");
hibernateProperties.setProperty("javax.persistence.validation.mode", "none");
//Audit History flags
hibernateProperties.setProperty("org.hibernate.envers.store_data_at_delete", "true");
hibernateProperties.setProperty("org.hibernate.envers.global_with_modified_flag", "true");
hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", "true");
hibernateProperties.setProperty("hibernate.cache.region.factory_class", "org.hibernate.cache.ehcache.EhCacheRegionFactory");
hibernateProperties.setProperty("hibernate.cache.use_query_cache", "true");
return hibernateProperties;
}
}
4.6 应用配置
主要包含springboot的application.properties,logback的logback-spring.xml以及缓存框架的ehcache.xml
application.propeties
##DataSource Config
##\u6570\u636e\u5e93\u8fde\u63a5\u6c60\u4fe1\u606f\u914d\u7f6e
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot?characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=guanglei
# \u4e0b\u9762\u4e3a\u8fde\u63a5\u6c60\u7684\u8865\u5145\u8bbe\u7f6e\uff0c\u5e94\u7528\u5230\u4e0a\u9762\u6240\u6709\u6570\u636e\u6e90\u4e2d
# \u521d\u59cb\u5316\u5927\u5c0f\uff0c\u6700\u5c0f\uff0c\u6700\u5927
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# \u914d\u7f6e\u83b7\u53d6\u8fde\u63a5\u7b49\u5f85\u8d85\u65f6\u7684\u65f6\u95f4
spring.datasource.maxWait=60000
# \u914d\u7f6e\u95f4\u9694\u591a\u4e45\u624d\u8fdb\u884c\u4e00\u6b21\u68c0\u6d4b\uff0c\u68c0\u6d4b\u9700\u8981\u5173\u95ed\u7684\u7a7a\u95f2\u8fde\u63a5\uff0c\u5355\u4f4d\u662f\u6beb\u79d2
spring.datasource.timeBetweenEvictionRunsMillis=60000
# \u914d\u7f6e\u4e00\u4e2a\u8fde\u63a5\u5728\u6c60\u4e2d\u6700\u5c0f\u751f\u5b58\u7684\u65f6\u95f4\uff0c\u5355\u4f4d\u662f\u6beb\u79d2
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# \u6253\u5f00PSCache\uff0c\u5e76\u4e14\u6307\u5b9a\u6bcf\u4e2a\u8fde\u63a5\u4e0aPSCache\u7684\u5927\u5c0f
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
# \u914d\u7f6e\u76d1\u63a7\u7edf\u8ba1\u62e6\u622a\u7684filters\uff0c\u53bb\u6389\u540e\u76d1\u63a7\u754c\u9762sql\u65e0\u6cd5\u7edf\u8ba1\uff0c'wall'\u7528\u4e8e\u9632\u706b\u5899
spring.datasource.filters=stat,wall,log4j
# \u901a\u8fc7connectProperties\u5c5e\u6027\u6765\u6253\u5f00mergeSql\u529f\u80fd\uff1b\u6162SQL\u8bb0\u5f55
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# \u5408\u5e76\u591a\u4e2aDruidDataSource\u7684\u76d1\u63a7\u6570\u636e
#spring.datasource.useGlobalDataSourceStat=true
# druid \u8bbf\u95ee\u5730\u5740 http://host:port/druid/index.html
##Log Config
logging.config=classpath:logback-spring.xml
## SpringData JPA Config
spring.jpa.database=mysql
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.generate-ddl=true
server.port=9996
ehcache.xml
<ehcache>
<!-- Sets the path to the directory where cache .data files are created.
If the path is a Java System Property it is replaced by
its value in the running VM.
The following properties are translated:
user.home - User's home directory
user.dir - User's current working directory
java.io.tmpdir - Default temp file path -->
<!--
指定一个目录:当 EHCache 把数据写到硬盘上时, 将把数据写到这个目录下.
-->
<diskStore path="tempDirectory"/>
<!--Default Cache configuration. These will applied to caches programmatically created through
the CacheManager.
The following attributes are required for defaultCache:
maxInMemory - Sets the maximum number of objects that will be created in memory
eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the element
is never expired.
timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
if the element is not eternal. Idle time is now - last accessed time
timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
if the element is not eternal. TTL is now - creation time
overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache
has reached the maxInMemory limit.
-->
<!--
设置缓存的默认数据过期策略
-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<!--
设定具体的命名缓存的数据过期策略。每个命名缓存代表一个缓存区域
缓存区域(region):一个具有名称的缓存块,可以给每一个缓存块设置不同的缓存策略。
如果没有设置任何的缓存区域,则所有被缓存的对象,都将使用默认的缓存策略。即:<defaultCache.../>
Hibernate 在不同的缓存区域保存不同的类/集合。
对于类而言,区域的名称是类名。如:com.ekeyfund.springboot.jpa.entity.User
对于集合而言,区域的名称是类名加属性名。如com.ekeyfund.springboot.jpa.entity.User.roleList
-->
<!--
name: 设置缓存的名字,它的取值为类的全限定名或类的集合的名字
maxElementsInMemory: 设置基于内存的缓存中可存放的对象最大数目
eternal: 设置对象是否为永久的, true表示永不过期,
此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false
timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。
当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。
timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。
如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于 timeToIdleSeconds 属性值
overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
-->
<cache name="com.ekeyfund.springcloud.entity.Department"
maxElementsInMemory="1"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
<cache name="com.ekeyfund.springcloud.entity.User"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/>
<cache name="com.ekeyfund.springcloud.entity.Role"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/>
</ehcache>
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--<include resource="org/springframework/boot/logging/logback/base.xml"/>-->
<!-- 项目的appid -->
<property name="APP_ID" value="SpringCloud-Provider-User-Service"/>
<property name="LOG_PATH" value="logs"></property>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
</encoder>
</appender>
<appender name="FILE_LOG"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
</filter>
<file>${LOG_PATH}/${APP_ID}/access.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_ID}/access.log.%d{yyyy-MM-dd}.zip
</fileNamePattern>
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE_DEBUG"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${LOG_PATH}/${APP_ID}/access_debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_ID}/access_debug.log.%d{yyyy-MM-dd}.zip
</fileNamePattern>
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE_INFO"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${LOG_PATH}/${APP_ID}/access_info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_ID}/access_info.log.%d{yyyy-MM-dd}.zip
</fileNamePattern>
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE_WARN"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${LOG_PATH}/${APP_ID}/access_warn.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_ID}/access_warn.log.%d{yyyy-MM-dd}.zip
</fileNamePattern>
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE_ERROR"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${LOG_PATH}/${APP_ID}/access_error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_ID}/access_error.log.%d{yyyy-MM-dd}.zip
</fileNamePattern>
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<appender name="ASYNC_LOG" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<appender-ref ref="FILE_LOG"/>
</appender>
<appender name="ASYNC_LOG" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<appender-ref ref="FILE_LOG"/>
</appender>
<appender name="ASYNC_LOG_DEBUG" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<appender-ref ref="FILE_DEBUG"/>
</appender>
<appender name="ASYNC_LOG_INFO" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<appender-ref ref="FILE_INFO"/>
</appender>
<appender name="ASYNC_LOG_WARN" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<appender-ref ref="FILE_WARN"/>
</appender>
<appender name="ASYNC_LOG_ERROR" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<appender-ref ref="FILE_ERROR"/>
</appender>
<root level="INFO">
<!-- appender referenced after it is defined -->
<appender-ref ref="STDOUT"/>
<appender-ref ref="ASYNC_LOG"/>
<appender-ref ref="ASYNC_LOG_DEBUG"/>
<appender-ref ref="ASYNC_LOG_INFO"/>
<appender-ref ref="ASYNC_LOG_WARN"/>
<appender-ref ref="ASYNC_LOG_ERROR"/>
</root>
<logger name="org.springframework" level="INFO"/>
</configuration>
4.7 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>springcloud-provider-user-service</artifactId>
<packaging>jar</packaging>
<name>springcloud-user-service</name>
<description>SpringCloud User Service Application</description>
<parent>
<groupId>com.ekeyfund.springcloud</groupId>
<artifactId>springcloud-parent</artifactId>
<version>2.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties>
<start-class>com.ekeyfund.springcloud.SpringcloudUserServiceMasterApplication</start-class>
</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>${start-class}</mainClass>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
4.8 启动类
package com.ekeyfund.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
//@EnableDiscoveryClient//激活Eureka中的DiscoveryClient实现(自动化配置,创建DiscoveryClient接口针对Eureka客户端的EurekaDiscoveryClient实例)
@SpringBootApplication
public class SpringcloudUserServiceMasterApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudUserServiceMasterApplication.class, args);
}
}
4.9 测试用例
package com.ekeyfund.springcloud;
import com.ekeyfund.springcloud.entity.Department;
import com.ekeyfund.springcloud.entity.User;
import com.ekeyfund.springcloud.service.DepartmentService;
import com.ekeyfund.springcloud.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Date;
@RunWith(SpringRunner.class)
@SpringBootTest
@ComponentScan("com.ekeyfund.springcloud")
public class SpringcloudUserServiceApplicationTests {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(SpringcloudUserServiceApplicationTests.class);
@Test
public void contextLoads() {
}
@Autowired
private DepartmentService departmentService;
@Autowired
private UserService userService;
@Test
public void testDepartmentService(){
Department department=new Department();
department.setName("dev");
Department result =departmentService.saveDepartment(department);
LOGGER.info("add result "+result);
Long id =1L;
result =departmentService.getDepartmentById(id);
LOGGER.info("get department "+result);
}
@Test
public void testUserRegister()throws Exception{
User user =new User();
user.setName("tony");
user.setPassword("666666");
user.setCreateDate(new Date());
Department department=departmentService.getDepartmentById(1L);
user.setDepartment(department);
User result =userService.register(user);
LOGGER.info("register result "+result);
}
@Test
public void testWriteOff()throws Exception{
User user =new User();
user.setName("tony");
user.setPassword("666666");
userService.writeOff(user);
}
@Test
public void testUserLogin()throws Exception{
User user =new User();
user.setName("tony");
user.setPassword("666666");
User result =userService.login(user.getName(),user.getPassword());
LOGGER.info("login "+result);
}
@Test
public void testUserIsExist()throws Exception{
User user =new User();
user.setId(4L);
boolean result =userService.isExists(user);
LOGGER.info("isExist "+result);
}
}
五 使用SpringBoot实现服务消费者
所属maven模块:springcloud-consumer-h5
5.1 entity
主要包含User,Department,Role
Role.java
package com.ekeyfund.springcloud.entity;
/**
* Role Entity
*
* @author Liuguanglei liuguanglei@ekeyfund.com
* @create 2017-06-下午2:36
*/
public class Role {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return new org.apache.commons.lang3.builder.ToStringBuilder(this)
.append("id", id)
.append("name", name)
.toString();
}
}
Department.java
package com.ekeyfund.springcloud.entity;
import org.apache.commons.lang3.builder.ToStringBuilder;
/**
* Department Entity
*
* @author Liuguanglei liuguanglei@ekeyfund.com
* @create 2017-06-下午2:31
*/
public class Department {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("id", id)
.append("name", name)
.toString();
}
}
User.java
package com.ekeyfund.springcloud.entity;
import com.fasterxml.jackson.annotation.JsonBackReference;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.util.List;
/**
* User Entity
*
* @author Liuguanglei liuguanglei@ekeyfund.com
* @create 2017-06-下午2:32
*/
public class User {
private Long id;
private String name;
private String password;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createDate;
@JsonBackReference
private Department department;
private List<Role> roleList;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public List<Role> getRoleList() {
return roleList;
}
public void setRoleList(List<Role> roleList) {
this.roleList = roleList;
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("id", id)
.append("name", name)
.append("password", password)
.append("createDate", createDate)
.append("department", department)
.append("roleList", roleList)
.toString();
}
}
5.2 Configuraiton
主要包含Spring MVC相关配置
MVCConfiguration.java
package com.ekeyfund.springcloud.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import java.util.ArrayList;
import java.util.List;
@Configuration
@EnableWebMvc
public class MVCConfiguration extends WebMvcConfigurerAdapter{
/**
*
* @return
*/
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(){
RequestMappingHandlerAdapter requestMappingHandlerAdapter=new RequestMappingHandlerAdapter();
List<HttpMessageConverter<?>> messageConverters =new ArrayList<>();
messageConverters.add(mappingJackson2HttpMessageConverter());
requestMappingHandlerAdapter.setMessageConverters(messageConverters);
return requestMappingHandlerAdapter;
}
//@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter =new MappingJackson2HttpMessageConverter();
List<MediaType> supportedMediaTypes =new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(supportedMediaTypes);
return mappingJackson2HttpMessageConverter;
}
@Bean
public RestTemplate restTemplate(){
RestTemplate restTemplate =new RestTemplate();
return restTemplate;
}
}
5.3 Controller
主要包含UserController
UserController.java
package com.ekeyfund.springcloud.controller;
import com.ekeyfund.springcloud.entity.User;
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 org.springframework.web.client.RestTemplate;
/**
* UserController
*
* @author tony 18601767221@163.com
* @create 2017-06-27-下午11:42
* @see
* @since JDK1.8u133
*/
@RestController
public class UserController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("user/get/{id}")
public User get(@PathVariable Long id){
return this.restTemplate.getForObject("http://127.0.0.1:9996/get/{1}",User.class,id);
}
}
5.4 应用配置
主要包含springboot的application.properties
application.properties
server.port=9991
server.tomcat.uri-encoding=UTF-8
5.5 启动类
package com.ekeyfund.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringcloudConsumerH5Application {
public static void main(String[] args) {
SpringApplication.run(SpringcloudConsumerH5Application.class, args);
}
}
5.6 测试服务调用
9991端口的服务消费者会调用9996端口的服务提供者接口。
通常在实际的开发中会遇到类似的情景:例如PC/H5/Android/IOS会去调用User模块的登录服务。
弊端:因为程序调用的IP和端口采用了硬编码,而IP和端口可能变更,因此难以维护。
而当服务提供者宕机后服务会不可用,后面会在系统中引入服务注册、发现组件,该组件主要是维护了一个服务注册表,服务提供者和消费者将服务注册到该注册表中,而服务注册和发现组件会通过发送心跳来判断服务是否可用。
六 使用Eureka实现服务发现组件
所属maven模块:springcloud-eureka-server
1 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>
<groupId>com.ekeyfund.springcloud</groupId>
<artifactId>springcloud-eureka-server</artifactId>
<packaging>jar</packaging>
<name>springcloud-eureka-server</name>
<description>SpringCloud Eureka Server </description>
<parent>
<groupId>com.ekeyfund.springcloud</groupId>
<artifactId>springcloud-parent</artifactId>
<version>2.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties>
<start-class>com.ekeyfund.springcloud.SpringcloudEurekaServerApplication</start-class>
</properties>
</project>
2 编写启动类,在启动类上添加@EnableEurekaServer注解,声明这是一个Eureka Server
package com.ekeyfund.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* 单点eureka server
*/
@EnableEurekaServer //启用Eureka Server
@SpringBootApplication
public class SpringcloudEurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudEurekaServerApplication.class, args);
}
}
3 在应用配置文件application.properties添加如下内容:
spring.application.name=springcloud-cureka-server
server.port=9999
eureka.instance.hostname=127.0.0.1
#定义注册中心的地址
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
#不向注册中心注册自己
eureka.client.register-with-eureka=false
#注册中心的职责就是去维护服务实例,不需要去检索服务
eureka.client.fetch-registry=false
eureka.instance.prefer-ip-address=true
4 测试访问Eureka Server
运行SpringcloudEurekaServerApplication,在浏览器输入 http://127.0.0.1:9999即可以访问Eureka Server
七 服务注册
所属maven模块 springcloud-provider-user-service
1 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>springcloud-provider-user-service</artifactId>
<packaging>jar</packaging>
<name>springcloud-user-service</name>
<description>SpringCloud User Service Application</description>
<parent>
<groupId>com.ekeyfund.springcloud</groupId>
<artifactId>springcloud-parent</artifactId>
<version>2.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties>
<start-class>com.ekeyfund.springcloud.SpringcloudUserServiceMasterApplication</start-class>
</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>
<!-- eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>${start-class}</mainClass>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2 在application.properties文件中添加以下配置:
#####Eureka Client Config#######
#设置服务名称
spring.application.name=springcloud-provider-user-service
#eureka 单实例配置
#eureka.client.service-url.defaultZone=http://127.0.0.1:9999/eureka
eureka.instance.prefer-ip-address=true
其中spring.application.name是指定注册到Eureka Server上的应用名称
eureka.instance.prefer-ip-address表示将自己的IP注册到Eureka Server
3 编写启动类,在启动类上添加@EnableDiscoveryClient注解,声明这是一个Eureka Client
package com.ekeyfund.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient//激活Eureka中的DiscoveryClient实现(自动化配置,创建DiscoveryClient接口针对Eureka客户端的EurekaDiscoveryClient实例)
@SpringBootApplication
public class SpringcloudUserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudUserServiceMasterApplication.class, args);
}
}
4 测试服务注册
首先运行SpringcloudEurekaServerApplication,启动Eureka Server,然后启动SpringcloudUserServiceApplication,启动Eureka Client。
访问Eureka Server,效果如下图:
八 Eureka Server 高可用实现
在生产环境中,如果Eureka Server所在的机器发生宕机,那么服务发现组件将会变得不可用,因此需要实现高可用。Eureka Server可以通过运行多个实例并相互注册的方式实现高可用部署,实例之间会彼此增量同步信息,从而保持所有节点的数据一致。
所属maven模块springcloud-eureka-server-ha
该模块使用了springboot的多环境配置特性来激活两个Eureka Server,因此准备了application.properties,application-master.properties和application-slave.properties三个配置文件
application.properties
spring.profiles.active=master
##使用ip地址的形式定义注册中心的地址
eureka.instance.prefer-ip-address=true
#禁用自我保护模式
#eureka.server.enable-self-preservation=false
###eureka server 启用安全验证
#security.user.name=tony
#security.user.password=666666
#security.basic.enabled=true
##服务续约任务的调用间隔时间默认为30s
#eureka.instance.lease-renewal-interval-in-seconds=3
###定义服务失效的时间默认是90s
#eureka.instance.lease-expiration-duration-in-seconds=540
application-master.properties
spring.application.name=springcloud-eureka-server-ha
server.port=9998
eureka.instance.hostname=127.0.0.1
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:9997/eureka/
application-slave.properties
spring.application.name=springcloud-eureka-server-ha
server.port=9997
eureka.instance.hostname=127.0.0.1
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:9998/eureka/
同时准备两个启动类用来启动两个Eureka Server
SpringcloudEurekaMasterServerApplication.java
package com.ekeyfund.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class SpringcloudEurekaMasterServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudEurekaMasterServerApplication.class, args);
}
}
SpringcloudEurekaSlaveServiceApplication.java
package com.ekeyfund.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class SpringcloudEurekaSlaveServiceApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudEurekaSlaveServiceApplication.class, args);
}
}
分别运行SpringcloudEurekaMasterServerApplication和SpringcloudEurekaSlaveServiceApplication后会发现两个注册中心已经完成相互注册
9998端口的Eureka Server
9997端口的Eureka Server
所属maven模块 springcloud-provider-user-service
服务提供者的服务注册只需要改变application.properties的注册地址即可
#####Eureka Client Config#######
#设置服务名称
spring.application.name=springcloud-provider-user-service
#指定服务注册中心的地址 ###高可用改造后可以加上多个注册中心的地址
eureka.client.service-url.defaultZone=http://127.0.0.1:9998/eureka/,http://127.0.0.1:9997/eureka/
eureka.instance.prefer-ip-address=true
#####Eureka Client Config#######
#指定服务提供者的端口
server.port=9996
刷新Eureka Server,查看高可用的Eureka Server
user服务同时注册在两个Eureka Server
九 Eureka Server实现安全验证
默认情况下Eureka Server允许匿名访问,这里创建一个需要登录后才能访问Eureka Server
在pom.xml中添加安全依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
然后在application.properties中添加http basic验证配置
##eureka server 启用安全验证
security.user.name=tony
security.user.password=666666
security.basic.enabled=true
运行SpringcloudEurekaMasterServerApplication或者SpringcloudEurekaSlaveServerApplication后,访问Eureka Server需要提供用户名和密码才能访问
将服务注册到需要认证的Eureka Server,只需要将eureka.client.serviceUrl.defaultZone配置为http://user:password@127.0.0.1:9998/eureka/,http://user:password@127.0.0.1:9997/eureka/即可
例如 高可用Eureka Server的注册地址变更为
master
spring.application.name=springcloud-eureka-server-ha
server.port=9998
eureka.instance.hostname=127.0.0.1
eureka.client.serviceUrl.defaultZone=http://tony:666666@${eureka.instance.hostname}:9997/eureka/
slave
spring.application.name=springcloud-eureka-server-ha
server.port=9997
eureka.instance.hostname=127.0.0.1
eureka.client.serviceUrl.defaultZone=http://tony:666666@${eureka.instance.hostname}:9998/eureka/
十 使用Ribbon实现客户端负载均衡
所属maven模块:springcloud-consumer-h5-ribbon-hystrix
Ribbon是Netflix发布的负载均衡器,有助于控制HTTP和TCP客户端的行为,为Ribbon配置服务提供者地址列表后,Ribbon可以基于负载均衡算法(例如轮询、随机)自动的帮助消费者去请求。
在SpringCloud中,当Ribbon与Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者地址列表,并基于负载均衡算法,请求其中一个服务提供者实例。
10.1 为服务消费者整合Ribbon
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>
<groupId>com.ekeyfund.springcloud</groupId>
<artifactId>springcloud-consumer-h5-ribbon-hystrix</artifactId>
<packaging>jar</packaging>
<name>springcloud-consumer-h5-ribbon-hystrix</name>
<description>Spring Cloud H5 Appliaction</description>
<parent>
<groupId>com.ekeyfund.springcloud</groupId>
<artifactId>springcloud-parent</artifactId>
<version>2.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties>
<start-class>com.ekeyfund.springcloud.SpringcloudH5RibbonHystrixApplication</start-class>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<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>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.8</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>${start-class}</mainClass>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
MVCConfiguration.java
package com.ekeyfund.springcloud.configuration;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* MVCConfiguration
*
* @author Liuguanglei 18601767221@163.com
* @create 2017-06-下午1:01
*/
@Configuration
@EnableWebMvc
public class MVCConfiguration extends WebMvcConfigurerAdapter{
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){
return new MappingJackson2HttpMessageConverter();
}
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter){
RequestMappingHandlerAdapter requestMappingHandlerAdapter=new RequestMappingHandlerAdapter();
List<HttpMessageConverter<?>> messageConverters=new ArrayList<>();
messageConverters.add(mappingJackson2HttpMessageConverter);
requestMappingHandlerAdapter.setMessageConverters(messageConverters);
return requestMappingHandlerAdapter;
}
@Bean
@LoadBalanced//开启客户端负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
相比之前的配置主要是在RestTemplate中添加了@LoadBalanced注解即可整合Ribbon实现客户端的负载均衡。
UserController.java
package com.ekeyfund.springcloud.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.ekeyfund.springcloud.entity.User;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* User Controller
*
* @author Liuguanglei 18601767221@163.com
* @create 2017-06-上午12:55
*/
@RestController
public class UserController {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(UserController.class);
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/login")
public User login(@RequestParam String name, @RequestParam String password){
LOGGER.info("call user service login method");
ResponseEntity<User> responseEntity =this.restTemplate.getForEntity("http://SPRINGCLOUD-PROVIDER-USER-SERVICE/login?name={1},password={2}",User.class,name,password);
return responseEntity.getBody();
}
@GetMapping("/list")
public List<User> list(){
User[] users=this.restTemplate.getForObject("http://SPRINGCLOUD-PROVIDER-USER-SERVICE/list",User[].class);
List<User> userList = Arrays.asList(users);
return userList;
}
@GetMapping("user/get/{id}")
public User get(@PathVariable Long id){
return this.restTemplate.getForObject("http://SPRINGCLOUD-PROVIDER-USER-SERVICE/get/id={1}",User.class,id);
}
/**
* ribbon负载均衡测试方法
*/
@GetMapping("/log-user-service-instance")
public void logUserServiceInstance(){
ServiceInstance serviceInstance=this.loadBalancerClient.choose("springcloud-provider-user-service");
LOGGER.info("serviceInstance info ---> serviceId is "+serviceInstance.getServiceId()+" host is "+serviceInstance.getHost()+"port is "+serviceInstance.getPort() );
}
}
从UserController中的login,get和list方法可以看出,我们将请求地址变更为http://SPRINGCLOUD-PROVIDER-USER-SERVICE,而SPRINGCLOUD-PROVIDER-USER-SERVICE是用户微服务的虚拟主机名,当Ribbon和Eureka配合使用时,会自动将虚拟主机名映射城微服务的网络地址。
在新增的logUserServiceInstance方法中可以通过LoadBalancerClient的API更加直观的获取当前选择的用户微服务节点。
启动如下服务:
访问地址:http://127.0.0.1:9994/log-user-service-instance
观察控制台输出:
2017-06-29 11:26:39.112 INFO 13474 — [nio-9994-exec-7] c.e.s.controller.UserController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9996
2017-06-29 11:26:41.000 INFO 13474 — [nio-9994-exec-8] c.e.s.controller.UserController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9995
2017-06-29 11:26:42.126 INFO 13474 — [nio-9994-exec-1] c.e.s.controller.UserController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9996
2017-06-29 11:26:48.926 INFO 13474 — [nio-9994-exec-4] c.e.s.controller.UserController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9995
可以看到此时请求会均匀分布到两个不同用户微服务节点上,说明已经实现了负载均衡。
application.properties
spring.application.name=springcloud-consumer-h5-ribbon-hystrix
server.port=9994
eureka.client.service-url.defaultZone=http://tony:666666@127.0.0.1:9998/eureka/,http://tony:666666@127.0.0.1:9997/eureka/
#eureka.instance.prefer-ip-address=true
##修改服务负载均衡规则为随机
springcloud-provier-user-service.ribbon.NFLoadBalanceRuleClassName=com.netflix.loadbalancer.RandomRule
十一 使用Feign实现声明式REST调用
Feign是Netflix公司开发的声明式、模板化的HTTP客户端,其主要用途是更加便捷、优雅的调用HTTP API,Spring Cloud在原有基础上使Feign支持SpringMVC注解,并且整合了Ribbon和Eureka。
所属maven模块 springcloud-consumer-h5-feign
之前的maven模块springcloud-consumer-h5-ribbon-hystrix是使用RestTemplate(负载均衡是使用ribbon实现)调用RESTful API。 如下的登录方法:
@GetMapping("/login")
public User login(@RequestParam String name, @RequestParam String password){
LOGGER.info("call user service login method");
ResponseEntity<User> responseEntity =this.restTemplate.getForEntity("http://SPRINGCLOUD-PROVIDER-USER-SERVICE/login?name={1},password={2}",User.class,name,password);
return responseEntity.getBody();
}
由代码可知通过拼接字符串的方式构造URL,而在其他业务场景中可能还会有更多的参数,如果还以这种方式构造URL,那么就会变得更低效,难以维护。
引入Netflix公司开发的Feign实现声明式的RESTful API 调用
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>
<groupId>com.ekeyfund.springcloud</groupId>
<artifactId>springcloud-consumer-h5-feign</artifactId>
<packaging>jar</packaging>
<name>springcloud-consumer-h5-feign</name>
<description>SpringCloud Feign H5 Application</description>
<parent>
<groupId>com.ekeyfund.springcloud</groupId>
<artifactId>springcloud-parent</artifactId>
<version>2.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties>
<start-class>com.ekeyfund.springcloud.SpringcloudFeignH5Application</start-class>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>${start-class}</mainClass>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
创建一个Feign接口,并添加@FeignClient注解
package com.ekeyfund.springcloud.feign;
import com.ekeyfund.springcloud.entity.User;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* User Feign Client
*
* @author tony 18601767221@163.com
* @create 2017-06-29-下午2:44
* @see
* @since JDK1.8u133
*/
@FeignClient(value = "springcloud-provider-user-service") //
public interface UserFeignClient {
@RequestMapping(value = "/list",method = RequestMethod.GET)
List<User> list();
@RequestMapping(value = "/login",method = RequestMethod.GET)
User login(@RequestParam("name") String name, @RequestParam("password") String password);
}
@FeignClient注解中的springcloud-provider-user-service是一个任意的客户端名称,用于创建Ribbon负载均衡器。
修改Controller,让其调用Feign接口
package com.ekeyfund.springcloud.controller;
import com.ekeyfund.springcloud.entity.User;
import com.ekeyfund.springcloud.feign.UserFeignClient;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* User Feign Controller
*
* @author Liuguanglei 18601767221@163.com
* @create 2017-06-下午1:50
*/
@RestController
public class UserFeignController {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(UserFeignController.class);
@Autowired
UserFeignClient userFeignClient;
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping(value = "/list")
public List<User> list(){
return userFeignClient.list();
}
@GetMapping("/login")
public User login(@RequestParam String name,@RequestParam String password){
return userFeignClient.login(name,password);
}
/**
* ribbon负载均衡测试方法
* springcloud 将feign和ribbon以及eureka进行了集成
*/
@GetMapping("/log-user-service-instance")
public void loguserserviceinstance(){
ServiceInstance serviceInstance=this.loadBalancerClient.choose("springcloud-provider-user-service");
LOGGER.info("serviceInstance info ---> serviceId is "+serviceInstance.getServiceId()+" host is "+serviceInstance.getHost()+"port is "+serviceInstance.getPort() );
}
}
启动类
package com.ekeyfund.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
@EnableDiscoveryClient
@EnableFeignClients //开启SpringCloud Feign的支持功能
@SpringBootApplication
public class SpringcloudFeignH5Application {
public static void main(String[] args) {
SpringApplication.run(SpringcloudFeignH5Application.class, args);
}
}
启动如下服务:
访问地址:http://127.0.0.1:9993/login?name=tony&password=666666
调用login接口返回的结果
访问http://127.0.0.1:9993/log-user-service-instance
查看控制台日志输出
2017-06-29 14:56:56.341 INFO 16173 — [nio-9993-exec-4] c.e.s.controller.UserFeignController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9995
2017-06-29 14:56:58.055 INFO 16173 — [nio-9993-exec-5] c.e.s.controller.UserFeignController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9996
2017-06-29 14:56:59.310 INFO 16173 — [nio-9993-exec-6] c.e.s.controller.UserFeignController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9995
2017-06-29 14:57:05.287 INFO 16173 — [nio-9993-exec-7] c.e.s.controller.UserFeignController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9996
2017-06-29 14:57:06.340 INFO 16173 — [nio-9993-exec-8] c.e.s.controller.UserFeignController : serviceInstance info —> serviceId is springcloud-provider-user-service host is 192.168.1.136port is 9995
以上结果说明不仅实现了声明式的Restful API调用,还实现了客户端的负载均衡
十二 使用Hystrix实现微服务的容错处理
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>
<groupId>com.ekeyfund.springcloud</groupId>
<artifactId>springcloud-consumer-h5-ribbon-hystrix</artifactId>
<packaging>jar</packaging>
<name>springcloud-consumer-h5-ribbon-hystrix</name>
<description>Spring Cloud H5 Appliaction</description>
<parent>
<groupId>com.ekeyfund.springcloud</groupId>
<artifactId>springcloud-parent</artifactId>
<version>2.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties>
<start-class>com.ekeyfund.springcloud.SpringcloudH5RibbonHystrixApplication</start-class>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<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>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.8</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>${start-class}</mainClass>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
启动类:
添加@EnableCircuitBreaker为项目启动断路器支持
package com.ekeyfund.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableCircuitBreaker //启动断路器支持
@EnableDiscoveryClient
@SpringBootApplication
public class SpringcloudH5RibbonHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudH5RibbonHystrixApplication.class, args);
}
}
修改UserController,让其中的list方法具备容错能力
package com.ekeyfund.springcloud.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.ekeyfund.springcloud.entity.User;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* User Controller
*
* @author Liuguanglei 18601767221@163.com
* @create 2017-06-上午12:55
*/
@RestController
public class UserController {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(UserController.class);
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/login")
public User login(@RequestParam String name, @RequestParam String password){
LOGGER.info("call user service login method");
ResponseEntity<User> responseEntity =this.restTemplate.getForEntity("http://SPRINGCLOUD-PROVIDER-USER-SERVICE/login?name={1},password={2}",User.class,name,password);
return responseEntity.getBody();
}
@HystrixCommand(fallbackMethod = "listFallback",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "50000"),
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "10000")
},
threadPoolProperties = {
@HystrixProperty(name = "coreSize",value = "1"),
@HystrixProperty(name="maxQueueSize",value = "20")
}
)
@GetMapping("/list")
public List<User> list(){
User[] users=this.restTemplate.getForObject("http://SPRINGCLOUD-PROVIDER-USER-SERVICE/list",User[].class);
List<User> userList = Arrays.asList(users);
return userList;
}
/**
* 当list方法所在的服务不可用时,会调用此方法
* @return
*/
public List<User> listFallback(){
User user =new User();
user.setName("admin");
List<User> userList=new ArrayList<>();
userList.add(user);
return userList;
}
@GetMapping("user/get/{id}")
public User get(@PathVariable Long id){
return this.restTemplate.getForObject("http://SPRINGCLOUD-PROVIDER-USER-SERVICE/get/id={1}",User.class,id);
}
/**
* ribbon负载均衡测试方法
*/
@GetMapping("/log-user-service-instance")
public void loguserserviceinstance(){
ServiceInstance serviceInstance=this.loadBalancerClient.choose("springcloud-provider-user-service");
LOGGER.info("serviceInstance info ---> serviceId is "+serviceInstance.getServiceId()+" host is "+serviceInstance.getHost()+"port is "+serviceInstance.getPort() );
}
}
由代码可知,为list方法添加一个回退方法listFallback,该方法与list方法具有相同的参数和返回值类型。
在list方法上,使用注解@HystrixCommand的fallBackMethod属性,指定回退方法是listFallback。
启动如下服务:
访问地址:http://127.0.0.1:9994/list
当服务状态可用时的返回结果
关掉两个springcloud-provider-user-service进程后
再次访问http://127.0.0.1:9994/list
返回默认用户信息
更多推荐
所有评论(0)