前一陣子一直在忙着做項目,最近有空,把項目中用到的SpringBoot微服務相關架構再梳理一遍。
主要分爲以下幾步:
(1)搭建SpringBootService,這裏是各個微服務的業務邏輯。
(2)搭建SpringBootEureka,用來發現服務。
(3)搭建SpringBootConfig,用於動態維護配置文件。
(4)搭建SpringBootConsumer,這個不是必須的,只有當一個更大的業務需要調用其他多個微服務Service時才需要搭建。
(5)搭建SpringBootZuul,用於路由控制,其中加入了Hystrix,用於熔斷控制。
(6)使用Jenkins進行代碼的自動化部署。
這裏沒有用到Ribbon負載均衡,因爲我們是在防火牆進行了負載均衡的配置。
一. SpringBootService的搭建
1. 創建SpringBoot項目
1.1. New Project -> Maven -> Next
1.2. 輸入GroupId,ArtifactId -> Next -> Finish
2. SpringBoot項目的相關配置
2.1. 在pom.xml中加入SpringBoot相關的Jar包
<?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.sun</groupId>
<artifactId>springbootService</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</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>
<!-- 添加這個依賴之後就可以創建一個web應用程序。starter poms部分可以引入所有需要在實際項目中使用的依賴。
spring-boot-starter-web依賴包含所有的spring-core, spring-web, spring-webmvc,嵌入的Tomcat server和其他web應用相關的庫。 -->
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.7</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2. 在resources中添加application.properties,這個文件用來進行項目的相關配置
spring.application.name=springbootService
server.port=6000
3. 項目結構和代碼
3.1. 項目目錄結構
其中,common目錄下是一些共通代碼,我這裏直接拿過來用了。
sun下面的controller提供對外接口,entity定義實體類。
ComsumerClientApplication在sun目錄下,是SpringBoot項目的啓動文件。
3.2. 相關Java代碼
(1)ComsumerClientApplication.java
package sun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication
@ServletComponentScan
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
}
(2)ServiceController.java
package sun.controller;
import common.entity.RestfulResult;
import common.utils.CommUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import sun.entity.ServiceInfo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@RequestMapping(value = "service")
public class ServiceController {
@RequestMapping(value = "hello")
public void login(HttpServletRequest request, HttpServletResponse response,
@RequestBody ServiceInfo serviceInfo) {
RestfulResult restfulResult = new RestfulResult();
try {
restfulResult.setData("Welcome " + serviceInfo.getName());
} catch (Exception e) {
e.printStackTrace();
}
CommUtils.printDataJason(response, restfulResult);
}
}
(3)ServiceInfo.java
package sun.entity;
public class ServiceInfo {
private static final long serialVersionUID = 1L;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
(4)RestfulResult.java
package common.entity;
public class RestfulResult {
private String result = "Success";
private String message;
private Object data; // 返回數據
private int cntPage; // page數
private long cntData; // 返回數據總數
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public int getCntPage() {
return cntPage;
}
public void setCntPage(int cntPage) {
this.cntPage = cntPage;
}
public long getCntData() {
return cntData;
}
public void setCntData(long cntData) {
this.cntData = cntData;
}
}
(5)CommonUtils.java
package common.utils;
import javax.servlet.http.HttpServletResponse;
public class CommUtils {
// JSON格式化
public static String printDataJason(HttpServletResponse response,
Object item) {
try {
JsonUtils.renderString(response, item);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 隨機生成6位隨機驗證碼
*
*/
public static String createRandomVcode(int len) {
// 驗證碼
String vcode = "";
for (int i = 0; i < len; i++) {
vcode = vcode + (int) (Math.random() * 9);
}
return vcode;
}
}
(6)JsonUtils.java
package common.utils;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JsonUtils {
public JsonUtils() {
}
public static String renderString(HttpServletResponse response, Object object) {
return renderString(response, JsonMapper.toJsonString(object), "application/json");
}
public static String renderString(HttpServletResponse response, String string, String type) {
try {
response.setContentType(type);
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
return null;
} catch (IOException var4) {
return null;
}
}
}
(7)JsonMapper.java
package common.utils;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.util.JSONPObject;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.TimeZone;
public class JsonMapper extends ObjectMapper {
private static final long serialVersionUID = 1L;
private static Logger logger = LoggerFactory.getLogger(JsonMapper.class);
private static JsonMapper mapper;
public JsonMapper() {
this(Include.NON_EMPTY);
}
public JsonMapper(Include include) {
if (include != null) {
this.setSerializationInclusion(include);
}
this.enableSimple();
this.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
this.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeString("");
}
});
this.registerModule((new SimpleModule()).addSerializer(String.class, new JsonSerializer<String>() {
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeString(StringEscapeUtils.unescapeHtml4(value));
}
}));
this.setTimeZone(TimeZone.getDefault());
}
public static JsonMapper getInstance() {
if (mapper == null) {
mapper = (new JsonMapper()).enableSimple();
}
return mapper;
}
public static JsonMapper nonDefaultMapper() {
if (mapper == null) {
mapper = new JsonMapper(Include.NON_DEFAULT);
}
return mapper;
}
public String toJson(Object object) {
try {
return this.writeValueAsString(object);
} catch (IOException var3) {
logger.warn("write to json string error:" + object, var3);
return null;
}
}
public <T> T fromJson(String jsonString, Class<T> clazz) {
if (StringUtils.isEmpty(jsonString)) {
return null;
} else {
try {
return this.readValue(jsonString, clazz);
} catch (IOException var4) {
logger.warn("parse json string error:" + jsonString, var4);
return null;
}
}
}
public <T> T fromJson(String jsonString, JavaType javaType) {
if (StringUtils.isEmpty(jsonString)) {
return null;
} else {
try {
return this.readValue(jsonString, javaType);
} catch (IOException var4) {
logger.warn("parse json string error:" + jsonString, var4);
return null;
}
}
}
public JavaType createCollectionType(Class<?> collectionClass, Class... elementClasses) {
return this.getTypeFactory().constructParametricType(collectionClass, elementClasses);
}
public <T> T update(String jsonString, T object) {
try {
return this.readerForUpdating(object).readValue(jsonString);
} catch (JsonProcessingException var4) {
logger.warn("update json string:" + jsonString + " to object:" + object + " error.", var4);
} catch (IOException var5) {
logger.warn("update json string:" + jsonString + " to object:" + object + " error.", var5);
}
return null;
}
public String toJsonP(String functionName, Object object) {
return this.toJson(new JSONPObject(functionName, object));
}
public JsonMapper enableEnumUseToString() {
this.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
this.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
return this;
}
public JsonMapper enableJaxbAnnotation() {
JaxbAnnotationModule module = new JaxbAnnotationModule();
this.registerModule(module);
return this;
}
public JsonMapper enableSimple() {
this.configure(Feature.ALLOW_SINGLE_QUOTES, true);
this.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
return this;
}
public ObjectMapper getMapper() {
return this;
}
public static String toJsonString(Object object) {
return getInstance().toJson(object);
}
public static Object fromJsonString(String jsonString, Class<?> clazz) {
return getInstance().fromJson(jsonString, clazz);
}
}
4. 項目啓動和測試
4.1. 運行Debug,啓動ConsumerClientApplication.java
"C:\Program Files\Java\jdk1.8.0_66\bin\java" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:54364,suspend=y,server=n -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=54362 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=localhost -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:D:\ideaIU-2018.1.win\lib\rt\debugger-agent.jar=file:/C:/Users/Administrator/AppData/Local/Temp/capture607016.props -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_66\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\rt.jar;D:\workspace20160509\springbootService\target\classes;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter-web\1.5.8.RELEASE\spring-boot-starter-web-1.5.8.RELEASE.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter\1.5.8.RELEASE\spring-boot-starter-1.5.8.RELEASE.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot\1.5.8.RELEASE\spring-boot-1.5.8.RELEASE.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\1.5.8.RELEASE\spring-boot-autoconfigure-1.5.8.RELEASE.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter-logging\1.5.8.RELEASE\spring-boot-starter-logging-1.5.8.RELEASE.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-classic\1.1.11\logback-classic-1.1.11.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-core\1.1.11\logback-core-1.1.11.jar;C:\Users\Administrator\.m2\repository\org\slf4j\jcl-over-slf4j\1.7.25\jcl-over-slf4j-1.7.25.jar;C:\Users\Administrator\.m2\repository\org\slf4j\jul-to-slf4j\1.7.25\jul-to-slf4j-1.7.25.jar;C:\Users\Administrator\.m2\repository\org\slf4j\log4j-over-slf4j\1.7.25\log4j-over-slf4j-1.7.25.jar;C:\Users\Administrator\.m2\repository\org\yaml\snakeyaml\1.17\snakeyaml-1.17.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter-tomcat\1.5.8.RELEASE\spring-boot-starter-tomcat-1.5.8.RELEASE.jar;C:\Users\Administrator\.m2\repository\org\apache\tomcat\embed\tomcat-embed-core\8.5.23\tomcat-embed-core-8.5.23.jar;C:\Users\Administrator\.m2\repository\org\apache\tomcat\tomcat-annotations-api\8.5.23\tomcat-annotations-api-8.5.23.jar;C:\Users\Administrator\.m2\repository\org\apache\tomcat\embed\tomcat-embed-el\8.5.23\tomcat-embed-el-8.5.23.jar;C:\Users\Administrator\.m2\repository\org\apache\tomcat\embed\tomcat-embed-websocket\8.5.23\tomcat-embed-websocket-8.5.23.jar;C:\Users\Administrator\.m2\repository\org\hibernate\hibernate-validator\5.3.5.Final\hibernate-validator-5.3.5.Final.jar;C:\Users\Administrator\.m2\repository\javax\validation\validation-api\1.1.0.Final\validation-api-1.1.0.Final.jar;C:\Users\Administrator\.m2\repository\org\jboss\logging\jboss-logging\3.3.1.Final\jboss-logging-3.3.1.Final.jar;C:\Users\Administrator\.m2\repository\com\fasterxml\classmate\1.3.4\classmate-1.3.4.jar;C:\Users\Administrator\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.8.10\jackson-databind-2.8.10.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-web\4.3.12.RELEASE\spring-web-4.3.12.RELEASE.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-aop\4.3.12.RELEASE\spring-aop-4.3.12.RELEASE.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-beans\4.3.12.RELEASE\spring-beans-4.3.12.RELEASE.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context\4.3.12.RELEASE\spring-context-4.3.12.RELEASE.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-webmvc\4.3.12.RELEASE\spring-webmvc-4.3.12.RELEASE.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-expression\4.3.12.RELEASE\spring-expression-4.3.12.RELEASE.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-core\4.3.12.RELEASE\spring-core-4.3.12.RELEASE.jar;C:\Users\Administrator\.m2\repository\org\apache\commons\commons-lang3\3.4\commons-lang3-3.4.jar;C:\Users\Administrator\.m2\repository\com\alibaba\fastjson\1.2.7\fastjson-1.2.7.jar;C:\Users\Administrator\.m2\repository\com\fasterxml\jackson\module\jackson-module-jaxb-annotations\2.8.10\jackson-module-jaxb-annotations-2.8.10.jar;C:\Users\Administrator\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.8.10\jackson-core-2.8.10.jar;C:\Users\Administrator\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.8.0\jackson-annotations-2.8.0.jar;D:\ideaIU-2018.1.win\lib\idea_rt.jar" sun.ConsumerClientApplication
Connected to the target VM, address: '127.0.0.1:54364', transport: 'socket'
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.8.RELEASE)
2019-07-09 14:17:10.710 INFO 923048 --- [ main] sun.ConsumerClientApplication : Starting ConsumerClientApplication on PC-Sun with PID 923048 (D:\workspace20160509\springbootService\target\classes started by Administrator in D:\workspace20160509\springbootPService)
2019-07-09 14:17:10.763 INFO 923048 --- [ main] sun.ConsumerClientApplication : No active profile set, falling back to default profiles: default
2019-07-09 14:17:11.754 INFO 923048 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5db6b9cd: startup date [Tue Jul 09 14:17:11 CST 2019]; root of context hierarchy
2019-07-09 14:17:22.203 INFO 923048 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 6000 (http)
2019-07-09 14:17:22.285 INFO 923048 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2019-07-09 14:17:22.312 INFO 923048 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.23
2019-07-09 14:17:23.143 INFO 923048 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-07-09 14:17:23.145 INFO 923048 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 11433 ms
2019-07-09 14:17:24.072 INFO 923048 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2019-07-09 14:17:24.089 INFO 923048 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2019-07-09 14:17:24.091 INFO 923048 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2019-07-09 14:17:24.092 INFO 923048 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2019-07-09 14:17:24.092 INFO 923048 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2019-07-09 14:17:25.239 INFO 923048 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5db6b9cd: startup date [Tue Jul 09 14:17:11 CST 2019]; root of context hierarchy
2019-07-09 14:17:25.376 INFO 923048 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/service/hello]}" onto public void sun.controller.ServiceController.login(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,sun.entity.ServiceInfo)
2019-07-09 14:17:25.380 INFO 923048 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2019-07-09 14:17:25.381 INFO 923048 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2019-07-09 14:17:25.440 INFO 923048 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2019-07-09 14:17:25.440 INFO 923048 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2019-07-09 14:17:25.520 INFO 923048 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2019-07-09 14:17:25.896 INFO 923048 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2019-07-09 14:17:26.094 INFO 923048 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 6000 (http)
2019-07-09 14:17:26.100 INFO 923048 --- [ main] sun.ConsumerClientApplication : Started ConsumerClientApplication in 17.811 seconds (JVM running for 28.604)
4.2. 調用localhost:6000/service/hello來測試
5. 項目打包和執行
5.1. 打包
Maven Projects -> package
執行結果:
[INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ springbootService ---
[INFO] Building jar: D:\workspace20160509\springbootService\target\springbootService-1.0-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:1.5.8.RELEASE:repackage (default) @ springbootService ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 26.847 s
[INFO] Finished at: 2019-07-09T14:36:54+08:00
[INFO] Final Memory: 29M/250M
[INFO] ------------------------------------------------------------------------
Process finished with exit code 0
找到jar包目錄,創建start.bat文件
start.bat內容:
java -jar springbootService-1.0-SNAPSHOT.jar
5.2. 執行start.bat啓動
二. 搭建Eureka
1. 創建Maven項目springbootEureka
同springbootService
2. 項目結構和代碼
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.sun</groupId>
<artifactId>springbootEureka</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.12.RELEASE</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>
<spring-cloud.version>Edgware.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
</dependencies>
</project>
application.properties
server.port=5000
eureka.instance.hostname=localhost
#是否向服務註冊中心註冊自己,默認爲true
eureka.client.register-with-eureka=false
#是否檢索服務
eureka.client.fetch-registry=true
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
ConsumerClientApplication.java
package sun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
}
3. 啓動Eureka
3.1. 運行Debug,啓動ConsumerClientApplication.java
3.2. 打開http://localhost:5000,表示正常啓動
4. 修改springbootService,使Eureka可以發現此服務
pom.xml中增加
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
application.properties中增加
eureka.client.service-url.defaultZone=http://localhost:5000/eureka/
ConsumerClientApplication.java中增加
@EnableDiscoveryClient
package sun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@ServletComponentScan
@EnableDiscoveryClient
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
}
啓動後,在Eureka中可以看到springbootService已註冊
三. 搭建SpringBootConfig配置中心
我這裏用的是SVN,在config目錄下創建文件springbootService-release.properties。
將springbootService的application.properties的內容複製進去。
爲了與原文件區分看效果,我這裏將server.port設爲6003(原來是6001)
spring.application.name=springbootService
server.port=6003
eureka.client.service-url.defaultZone=http://localhost:5000/eureka/
配置中心其實就是讀取SVN上的文件後,發送給其他服務,讓他們讀取。
1. 創建Maven項目springbootConfig
同springbootService
2. 項目結構和代碼
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.sun</groupId>
<artifactId>springbootConfig</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.12.RELEASE</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>
<spring-cloud.version>Edgware.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.tmatesoft.svnkit</groupId>
<artifactId>svnkit</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-test</artifactId>
<scope>test</scope>
</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>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
port: 6002
spring:
application:
name: springbootConfig
profiles:
active: subversion
cloud:
config:
server:
svn:
uri: https://192.168.3.97/svn/SourceCode/SMPH/Beats/trunk/test/config
#username: *****
#password: *****
default-label:
eureka:
client:
service-url:
defaultZone: http://localhost:5000/eureka/
instance:
preferIpAddress: true
instance-id: ${spring.cloud.client.ipAddress}:${server.port}
lease-expiration-duration-in-seconds: 30
lease-renewal-interval-in-seconds: 30
ConsumerClientApplication
package sun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
}
3. 啓動Config配置中心
3.1. Eureka中能夠看到Config配置中心已註冊
3.2. 打開http://localhost:6002/springbootService/release,能夠看到配置文件的內容
{"name":"springbootService","profiles":["release"],"label":null,"version":"523","state":null,"propertySources":[{"name":"https://192.168.3.97/svn/SourceCode/SMPH/Beats/trunk/test/config/springbootService-release.properties","source":{"server.port":"6003","eureka.client.service-url.defaultZone":"http://localhost:5000/eureka/","spring.application.name":"springbootService"}}]}
4. 修改springbootService,使它能從配置中心讀取配置文件
4.1. 在resources目錄下創建bootstrap.yml,用來配置讀取Config配置中心下的哪個文件
這裏對應localhost:6002/springbootService-release.properties文件
spring:
application:
name : springbootService
cloud:
config:
uri : http://localhost:6002/
profile : release
4.2. 修改pom.xml,增加
<!--Spring Cloud Config 客戶端依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
<!--Spring Boot Actuator,感應服務端變化-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
4.3. 在ConsumerClientApplication.java中增加
@RefreshScope //開啓配置更新功能
package sun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
@SpringBootApplication
@ServletComponentScan
@EnableDiscoveryClient
@RefreshScope //開啓配置更新功能
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
}
4.4. 啓動springbootService,在Eureka中可以看到,多了個端口是6003的服務。
4.5. 接口可以從6003訪問,原來的6001不能用了,說明配置中心的文件覆蓋了本地的application.properties
四. 搭建SpringBootConsumer,使用Feign來調用各個微服務
1.直接從springbootService複製一份,命名爲springbootConsumer
別忘了把配置裏面的名字都改掉
2. 項目結構和代碼
2.1. 在pom中添加Feign
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
2.2. 修改application.properties
spring.application.name=springbootConsumer
server.port=6004
eureka.client.service-url.defaultZone=http://localhost:5000/eureka/
2.3. 在ConsumerClientApplication.java中增加
@EnableFeignClients
package sun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
@SpringBootApplication
@ServletComponentScan
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
}
2.4. 在sun目錄下添加client目錄,並新建文件ServiceFeignClient
這裏是聲明接口,指向springbootService的接口
package sun.client;
import common.entity.RestfulResult;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import sun.entity.ServiceInfo;
@Component
@FeignClient(value = "springbootService") //這裏的value對應調用服務的spring.applicatoin.name
public interface ServiceFeignClient {
@RequestMapping(value = "/service/hello")
RestfulResult hello(@RequestBody ServiceInfo serviceInfo);
}
2.5. 修改ServiceController.java
package sun.controller;
import common.entity.RestfulResult;
import common.utils.CommUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import sun.client.ServiceFeignClient;
import sun.entity.ServiceInfo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
public class ServiceController {
@Autowired
ServiceFeignClient serviceFeignClient;
@RequestMapping("/consumerService")
public void consumerService(HttpServletRequest request, HttpServletResponse response,
@RequestBody ServiceInfo serviceInfo){
RestfulResult restfulResult = serviceFeignClient.hello(serviceInfo);
CommUtils.printDataJason(response, restfulResult);
}
}
3. 啓動springbootConsumer並測試接口
3.1. 啓動後,在Eureka中發現服務
3.2. 調用SpringBootConsumer的接口http://localhost:6004/consumerService
五. 搭建Zuul+Hystrix
Zuul對外提供統一的服務入口,主要是用作網址重定向。
還可以通過Filter實現過濾器。
配合Hystrix實現熔斷器,當服務宕機時可以做異常處理。
1. 創建Maven項目springbootZuul
同springbootService
2. 項目結構和代碼
pom.xml
這裏我把spring boot改成了2.0版本,spring cloud改成了Finchley.SR2。
<?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.sun</groupId>
<artifactId>springbootZuul</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!--<version>1.5.8.RELEASE</version>-->
<version>2.0.0.RELEASE</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>
<spring-cloud.version>Finchley.SR2</spring-cloud.version>
<!--<spring-cloud.version>Edgware.SR2</spring-cloud.version>-->
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!-- 配置hystrix所需依賴的包 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</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>
</plugin>
</plugins>
</build>
</project>
application.yml
spring:
application:
name : springbootZuul
server:
port : 6005
eureka:
client:
service-url:
defaultZone : http://localhost:5000/eureka/
zuul:
routes:
sbService :
path : /sbService/**
serviceId : springbootService
ConsumerClientApplication.java
package sun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
@RefreshScope
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
}
serviceFilter(過濾器,非必須)
這裏判斷了請求中是否帶token,如果沒有,則顯示"there is no request token"
package sun.filter;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
@Component
public class serviceFilter extends ZuulFilter {
private static Logger log=LoggerFactory.getLogger(serviceFilter.class);
@Override
public String filterType() {
return "pre"; // 定義filter的類型,有pre、route、post、error四種
}
@Override
public int filterOrder() {
return 0; // 定義filter的順序,數字越小表示順序越高,越先執行
}
@Override
public boolean shouldFilter() {
return true; // 表示是否需要執行該filter,true表示執行,false表示不執行
}
@Override
public Object run() {
// filter需要執行的具體操作
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = request.getParameter("token");
System.out.println(token);
if(token==null){
log.warn("there is no request token");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("there is no request token");
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
log.info("ok");
return null;
}
}
ServiceFallbackProvider(熔斷器,非必須)
當沒有服務時,顯示"Sorry, the service is unavailable now."
package sun.fallbackProvider;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
@Component
public class ServiceFallbackProvider implements FallbackProvider {
@Override
// 指定熔斷器功能應用於哪些路由的服務
public String getRoute() {
// 這裏只針對"springbootService"服務進行熔斷
// 如果需要針對所有服務熔斷,則return "*"
return "springbootService";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("route:"+route);
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "ok";
}
@Override
public void close() {
}
@Override
// 發生熔斷式,返回的信息
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("Sorry, the service is unavailable now.".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
3. 啓動springbootZuul並測試
3.1. 在Eureka上看到springbootZuul服務已啓動
3.2. 現在可以通過統一路由訪問服務了
3.3. 使用Filter,請求不帶token時的效果:
3.4. 使用Filter,請求帶token時的效果:
3.5. 使用Hystrix,關閉springbootService服務時的效果:
六. 自動化部署Jenkins
(未完待續)