第十六节 SpringBoot实现微服务

亮子 2021-05-11 13:57:00 21494 0 0 0

1、什么是微服务

维基上对其定义为:一种软件开发技术- 面向服务的体系结构(SOA)架构样式的一种变体,将应用程序构造为一组**松散耦合**的服务。在微服务体系结构中,服务是细粒度的,协议是轻量级的。

微服务(或微服务架构)是一种云原生架构方法,其中单个应用程序由许多松散耦合且可独立部署的较小组件或服务组成。这些服务通常具有以下特征:

  • 有自己的堆栈,包括数据库和数据模型;
  • 通过REST API,事件流和消息代理的组合相互通信;
  • 和它们是按业务能力组织的,分隔服务的线通常称为有界上下文。

尽管有关微服务的许多讨论都围绕体系结构定义和特征展开,但它们的价值可以通过相当简单的业务和组织收益更普遍地理解:

  • 可以更轻松地更新代码。
  • 团队可以为不同的组件使用不同的堆栈。
  • 组件可以彼此独立地进行缩放,从而减少了因必须缩放整个应用程序而产生的浪费和成本,因为单个功能可能面临过多的负载。

微服务是一种用于构建应用的架构方案。微服务架构有别于更为传统的单体式方案,可将应用拆分成多个核心功能。每个功能都被称为一项服务,可以单独构建和部署,这意味着各项服务在工作(和出现故障)时不会相互影响。

图片alt

2、微服务的优点

  • 微服务是松藕合的,无论是在开发阶段或部署阶段都是独立的。
  • 能够快速响应, 局部修改容易, 一个服务出现问题不会影响整个应用。
  • 易于和第三方应用系统集成, 支持使用不同的语言开发, 允许你利用融合最新技术。
  • 每个微服务都很小,足够内聚,足够小,代码容易理解。团队能够更关注自己的工作成果, 聚焦指定的业务功能或业务需求。
  • 开发简单、开发效率提高,一个服务可能就是专一的只干一件事, 能够被小团队单独开发,这个小团队可以是 2 到 5 人的开发人员组成。

3、微服务的缺点

  • 微服务架构带来过多的运维操作, 可能需要团队具备一定的 DevOps 技巧.
  • 分布式系统由于服务较多,所以管理比较繁琐。如果微服务数量达到一定级别,则可能复杂到难以管理。因为分布部署跟踪问题还是有些难度的。

4、SpringBoot如何实现微服务

提起微服务,绝大多数都是前后端分离的项目,前端要么是Vue,要么是小程序或者APP啥的,前后端通过http协议来传递数据。而在SpringBoot项目中,只许通过两个注解,就可以实现最简单的微服务:

1)、@RestController注解

@RestController 注解和 @Controller 类似,都是放到controller的类上,用来标识接口的返回值类型。那么两者的区别是什么呢?

  • 如果只是使用@RestController注解Controller,则Controller中的方法无法返回jsp页面,或者html,配置的视图解析器 InternalResourceViewResolver不起作用,返回的内容就是Return 里的内容,在SpringBoot项目中,会把返回值自动转换为json字符串。

  • 如果需要返回到指定页面,则需要用 @Controller配合视图解析器InternalResourceViewResolver才行。如果需要返回JSON,XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。

示例代码:

package com.shenmazong.demohello.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;

@Controller
public class IndexController {

    @GetMapping(value = "/")
    public String index() {
        return "index";
    }

    @GetMapping(value = "/user")
    @ResponseBody
    public Object getUser() {
        HashMap<String, Object> userMap = new HashMap<>();

        userMap.put("name", "武松");
        userMap.put("age", 35);

        return userMap;
    }
}

2)、@ResponseBody、

当controller类上,已经使用@Controller注解,但你写的接口还需要返回json或者xml数据怎么办?这是就需要在方法上加@ResponseBody注解。

示例代码:

package com.shenmazong.demohello.controller;


import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
public class UserController {

    @GetMapping(value = "/user")
    public Object getUser() {
        HashMap<String, Object> userMap = new HashMap<>();

        userMap.put("name", "武松");
        userMap.put("age", 35);

        return userMap;
    }
}

5、SpringBoot返回XML格式数据

SpringBoot实现的微服务默认都是json格式,而且默认序列化的json库是jackson库。但有时候,客户需要xml格式的数据,那么该怎么实现呢?

1)、引入依赖

		<!--xml格式-->
		<dependency>
			<groupId>com.fasterxml.jackson.dataformat</groupId>
			<artifactId>jackson-dataformat-xml</artifactId>
		</dependency>

2)、接口代码

package com.shenmazong.demohello.controller;


import com.shenmazong.demohello.pojo.TbUser;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
public class UserController {

    @GetMapping(value = "/json",produces = MediaType.APPLICATION_JSON_VALUE)
    public Object getUserJson() {

        TbUser tbUser = new TbUser();
        tbUser.setUserName("武松");
        tbUser.setUserAge(35);

        return tbUser;
    }

    @GetMapping(value = "/xml",produces = MediaType.APPLICATION_XML_VALUE)
    public Object getUserXml() {

        TbUser tbUser = new TbUser();
        tbUser.setUserName("武松");
        tbUser.setUserAge(35);

        return tbUser;
    }
}

3)、运行效果

图片alt

图片alt

4)、JAXB相关的重要Annotation

  • @XmlRootElement:表示映射到根目录标签元素
  • @XmlElement:表示映射到子元素
  • @XmlAttribute:表示映射到属性
  • @XmlElementWrapper :表示类型是集合元素的子元素的上层目录
package com.shenmazong.demohello.pojo;

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlCData;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.Data;

@Data
@JacksonXmlRootElement(localName = "user")
public class TbUser {
    private String userName;
    private Integer userAge;

    @JacksonXmlCData
    @JacksonXmlProperty(localName = "Content")
    private String content;
}

@JacksonXmlRootElement注解中有localName属性,该属性如果不设置,那么生成的XML最外面就是
,而不是

@JacksonXmlCData注解是为了生成

<![CDATA[text]]>
这样的数据,如果你不需要,可以去掉

@JacksonXmlProperty注解通常可以不需要,但是如果你想要你的xml节点名字,首字母大写。比如例子中的Content,那么必须加这个注解,并且注解的localName填上你想要的节点名字。最重要的是!实体类原来的属性content必须首字母小写!否则会被识别成两个不同的属性。

有了上面的配置,在Controller返回实体类的时候,就会像转换Json一样,将实体类转换为xml对象了。有时候你的浏览器并不能识别xml,是因为你返回的content-type不是xml,可以通过修改@RequestMapping注解的produces属性来修改:

运行后的效果如下:

图片alt

5)、xml自动转换为实体

    @RequestMapping(value = "/postxml", method = RequestMethod.POST, consumes = { "text/xml" }, produces = {
            "application/xml" })
    @ResponseBody
    public TbUser post(@RequestBody TbUser user) {
        return user;
    }

测试代码:

package com.shenmazong.demohello;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DemoHelloApplicationTests {

	@Test
	void contextLoads() {
	}

	@Test
	public void testPost() throws Exception {
		// 直接字符串拼接
		StringBuilder builder = new StringBuilder();
		// xml数据存储
		builder.append("<user>\n" +
				"<userName>武松</userName>\n" +
				"<userAge>35</userAge>\n" +
				"<Content>\n" +
				"<![CDATA[ 武松醉打蒋门神 ]]>\n" +
				"</Content>\n" +
				"</user>");
		String data = builder.toString();
		System.out.println(data);
		String url = "http://localhost:8080/postxml";
		CloseableHttpClient httpClient = HttpClients.createDefault();
		HttpPost httpPost = new HttpPost(url);
		httpPost.setEntity(new StringEntity(data, "text/xml", "utf-8"));
		CloseableHttpResponse response = httpClient.execute(httpPost);
		HttpEntity entity = response.getEntity();
		String content = EntityUtils.toString(entity);
		System.out.println("content:" + content);
	}
}

上面的测试函数需要在pom文件中添加下面的依赖:

		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<version>4.5.2</version>
		</dependency>

6)、完整接口代码

package com.shenmazong.demohello.controller;


import com.shenmazong.demohello.pojo.TbUser;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;

@RestController
public class UserController {

    @GetMapping(value = "/json",produces = MediaType.APPLICATION_JSON_VALUE)
    public Object getUserJson() {

        TbUser tbUser = new TbUser();
        tbUser.setUserName("武松");
        tbUser.setUserAge(35);
        tbUser.setContent("武松醉打蒋门神");

        return tbUser;
    }

    @GetMapping(value = "/xml",produces = MediaType.APPLICATION_XML_VALUE)
    public Object getUserXml() {

        TbUser tbUser = new TbUser();
        tbUser.setUserName("武松");
        tbUser.setUserAge(35);
        tbUser.setContent("武松醉打蒋门神");

        return tbUser;
    }

    @RequestMapping(value = "/postxml", method = RequestMethod.POST, consumes = { "text/xml" }, produces = {
            "application/xml" })
    @ResponseBody
    public TbUser post(@RequestBody TbUser user) {
        return user;
    }
}