学习了《Spring Cloud与Docker微服务架构实战》这本书,想要记录一下学习历程,同时把总结的经验以及自己补充的知识记录下来,于是就有了这篇文章。
本文及后续的内容使用Spring Cloud的版本为Edgware.SR6,Spring Boot的版本为1.5.x,因为在Edgware之后的版本,Spring Boot也升级为2.x版本的了。为了保证能够顺利进行书上的练习,因为选用了Edgware最新的版本。
首先创建一个父项目micro-service-spring-cloud-study,用于管理其他子项目依赖的版本,注意父项目的打包方式为pom,其中标签内的是即将创建的生产者和消费者子项目,子项目从父项目中获取依赖的jar包及属性信息。
以下为父pom文件pom.xml
4.0.0
com.fanfan.cloud
micro-service-spring-cloud-study
0.0.1-SNAPSHOT
pom
micro-service-provider-user
micro-service-consumer-movie
org.springframework.boot
spring-boot-starter-parent
1.5.4.RELEASE
UTF-8
UTF-8
1.8
org.springframework.cloud
spring-cloud-dependencies
Edgware.SR6
pom
import
org.springframework.boot
spring-boot-configuration-processor
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-actuator
ch.qos.logback
logback-core
compile
1.1.11
ch.qos.logback
logback-classic
compile
1.1.11
创建生成者项目micro-service-provider-user,由于书中使用的是内置的h2数据库,因此需要建表的schema.sql文件和插入数据的data.sql文件,application.yml为配置文件,配置数据库等信息,项目结构如下图所示。
4.0.0
com.test.cloud
micro-service-provider-user
0.0.1-SNAPSHOT
jar
com.fanfan.cloud
micro-service-spring-cloud-study
0.0.1-SNAPSHOT
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-web
com.h2database
h2
runtime
com.alibaba
fastjson
1.2.29
org.projectlombok
lombok
1.16.10
provider-user
org.springframework.boot
spring-boot-maven-plugin
drop table user if exists;
create table user(id bigint generated by default as IDENTITY,
username varchar(40),
name varchar(20),
age int(3),
balance decimal(10,2),
primary key(id));
insert into user(id,username,name,age,balance) VALUES (1,‘user1’,‘张三’,20,100.00);
insert into user(id,username,name,age,balance) VALUES (2,‘user2’,‘李四’,30,200.00);
insert into user(id,username,name,age,balance) VALUES (3,‘user3’,‘二毛’,40,300.00);
insert into user(id,username,name,age,balance) VALUES (4,‘user4’,‘大毛’,50,400.00);
server:
port: 8010
spring:
jpa:
generate-ddl: false
show-sql: true
hibernate:
ddl-auto: none
datasource: # 指定数据源
platform: h2 # 指定数据源类型
schema: classpath:schema.sql # 指定h2数据库的建表脚本
data: classpath:data.sql # 指定h2数据库的数据脚本
application:
name: provider #名称要小写 指定应用名
logging:
level:
root: INFO # 配置日志级别,让hibernate打印执行的SQL
org.hibernate: INFO
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
org.hibernate.type.descriptor.sql.BasicExtractor: TRACE
com.spring.cloud.test: DEBUG
%date [%thread] %-5level %logger{50}:%L - %msg%n
${logDir}/service.log
${logDir}/history/service.%d{yyyy-MM-dd}.log
30
%date [%thread] %-5level %logger{50}:%L - %msg%n
注意@Data是lombok的注解,可以节省代码量,@Entity是JPA的注解。
@Entity
@Data
public class User implements Serializable{
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
/**注意不要写成userName, 否则会报找不到user_name字段错误*/
@Column
private String username;
@Column
private String name;
@Column
private Integer age;
@Column
private BigDecimal balance;
}
ORM框架使用的是JPA,继承的JpaRepository中有默认的方法。
@Repository
public interface UserRepository extends JpaRepository {
}
因为代码比较简单所以省略了Service层,实际开发中不可这样写。
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("/simple/{id}")
public User findById(@PathVariable Long id) {
return this.userRepository.findOne(id);
}
@PostMapping("/user")
public User postUser(@RequestBody User user) {
return user;
}
@GetMapping("/user")
public User getUser(User user) {
return user;
}
@PostMapping("/save")
public User saveUser(@RequestBody User user) {
return userRepository.save(user);
}
}
@SpringBootApplication
public class ProviderUserApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderUserApplication.class, args);
}
}
以上步骤结束后即可启动ProviderUserApplication了。
代码编写完成后不要忽略单元测试,可以到Dao层和Controller层进行单元测试,其中Controller层的单元测试是模拟浏览器发出请求,因为是生产者需要确认接口是否可用,因此建议写单元测试,当然也可以使用PostMan或JMeter进行测试,此处不做介绍。
Dao层单元测试
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional // 事务回滚,不污染数据
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
public void testSave() {
User user = new User();
user.setName("王五");
user.setAge(25);
user.setUsername("user5");
user.setBalance(new BigDecimal(500.00));
User result = userRepository.save(user);
System.err.println(result);
}
@Test
public void testFindOne() {
User result = userRepository.findOne(4L);
System.err.println(result);
}
}
Controller层单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@Transactional
@WebAppConfiguration
@ContextConfiguration
public class UserControllerTest {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext wac;
@Autowired
MockHttpServletRequest request;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
public void testSaveUser() {
try {
User user = new User();
user.setName("王五");
user.setAge(25);
user.setUsername("user5");
user.setBalance(new BigDecimal(500.00));
String requestJson = JSONObject.toJSONString(user);
String responseString = mockMvc.perform(
MockMvcRequestBuilders.post("/user/save")
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE).content(requestJson))
.andExpect(status().isOk())
.andDo(print())
.andReturn().getResponse().getContentAsString();
System.err.println(responseString);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testGetUser() {
String responseString = null;
try {
responseString = mockMvc.perform(get("/user/simple/1")
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(status().isOk())
.andDo(print())
.andReturn().getResponse().getContentAsString();
} catch (Exception e) {
e.printStackTrace();
}
System.err.println(responseString);
}
}
需要注意单元测试的事务回滚,这就是@Transactional注解的作用。
创建消费者项目micro-service-consumer-movie,使用RestTemplate调用micro-service-provider-user生产者提供的接口。
server:
port: 8020
spring:
application:
name: cunsumer-movie
%date [%thread] %-5level %logger{50}:%L - %msg%n
${logDir}/service.log
${logDir}/history/service.%d{yyyy-MM-dd}.log
30
%date [%thread] %-5level %logger{50}:%L - %msg%n
注意配置文件的默认名为logback-spring.xml
@Data
public class User implements Serializable{
private Long id;
private String username;
private String name;
private String age;
private BigDecimal balance;
}
实体类同生产者,因为movie项目不直接操作user表,因此此处无需@Entity注解,@Data注解要注入lombok的引入
@RestController
public class MovieController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/movie/{id}")
public User findById(@PathVariable Long id) {
return restTemplate.getForObject("http://localhost:8010/user/simple/" + id, User.class);
}
@PostMapping("/movie/save")
public User save(User user) {
return restTemplate.postForObject("http://localhost:8010/user/save", user, User.class);
}
}
注意需要在启动类或者单独的配置类中配置RestTemplate,下面为在启动类中配置的。
@SpringBootApplication
public class ConsumerMovieApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerMovieApplication.class, args);
}
}
消费者和生产者编写完成后,分别启动生产者和消费者,两个都启动成功后,使用postman测试消费者的接口,结果如下。
Spring Boot Actuator提供了很多监控端点,如/health、/shutdown等,/health端点提供基本的应用程序健康信息,/shutdown默认是不启动用的,如需启动需在application.yml中添加如下配置。
endpoints:
shutdown:
enabled: true
在消费者micro-service-consumer-movie中配置,配置后重启消费者的服务,分别看一下/health和/shutdown的结果。
如上图所示,/health只有一个status,/shutdown则直接报无权限。
因为默认情况下,所有敏感的HTTP端点都是安全的,只有具有ACTUATOR角色的用户 可以访问它们。
可以在application.yml中添加配置,将management.security.enabled 设为false,将通知Spring Boot跳过额外的角色检查。
management:
security:
enabled: false
重启micro-service-consumer-movie查看postman请求结果为:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190610175328249.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTM0MDgxODg=,size_16,color_FFFFFF,t_70
可以在application.yml中添加如下配置,重启micro-service-consumer-movie查看postman请求结果。
info:
app:
name: @project.artifactId@
encoding: @project.build.sourceEncoding@
java:
source: @java.version@
target: @java.version@
本节学习完成后,将进行Eureka服务发现组件的学习。