建立spring父模塊
- 刪除不必要的src目錄
- 父模塊中的pom.xml中添加相應的依賴以及插件、遠程倉庫地址
1 <!-- 項目的打包類型, 即項目的發布形式, 默認為 jar. 對於聚合項目的父模塊來說, 必須指定為 pom --> 2 <packaging>pom</packaging> 3 4 <name>spring-cloud-home-page</name> 5 <description>Project For VastHomepage SpringCloud</description> 6 7 <parent> 8 <groupId>org.springframework.boot</groupId> 9 <artifactId>spring-boot-starter-parent</artifactId> 10 <version>2.1.4.RELEASE</version> 11 </parent> 12 13 <properties> 14 <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version> 15 </properties> 16 17 <dependencies> 18 <!-- lombok 工具通過在代碼編譯時期動態的將注解替換為具體的代碼, 19 IDEA 需要添加 lombok 插件 --> 20 <dependency> 21 <groupId>org.projectlombok</groupId> 22 <artifactId>lombok</artifactId> 23 <version>1.16.18</version> 24 </dependency> 25 <dependency> 26 <groupId>org.springframework.boot</groupId> 27 <artifactId>spring-boot-starter-test</artifactId> 28 <scope>test</scope> 29 </dependency> 30 </dependencies> 31 32 <!-- 標識 SpringCloud 的版本 --> 33 <dependencyManagement> 34 <dependencies> 35 <dependency> 36 <groupId>org.springframework.cloud</groupId> 37 <artifactId>spring-cloud-dependencies</artifactId> 38 <version>${spring-cloud.version}</version> 39 <type>pom</type> 40 <scope>import</scope> 41 </dependency> 42 </dependencies> 43 </dependencyManagement> 44 45 <!-- 配置遠程倉庫 --> 46 <repositories> 47 <repository> 48 <id>spring-milestones</id> 49 <name>Spring Milestones</name> 50 <url>https://repo.spring.io/milestone</url> 51 <snapshots> 52 <enabled>false</enabled> 53 </snapshots> 54 </repository> 55 </repositories>
創建 Eureka注冊服務子模塊
- pom.xml中引入必要的依賴以及插件
<!-- 模塊名及描述信息 -->
<name>spring-cloud-eureka</name>
<description>Spring Cloud Eureka</description>
<!-- eureka server: 提供服務發現與服務注冊 -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<!--
SpringBoot的Maven插件, 能夠以Maven的方式為應用提供SpringBoot的支持,可以將
SpringBoot應用打包為可執行的jar或war文件, 然后以通常的方式運行SpringBoot應用
-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- 資源文件夾(resources)中創建application.yml
spring: application: name: spring-cloud-eureka server: port: 8000 eureka: instance: hostname: localhost client: fetch-registry: false register-with-eureka: false service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
- 啟動類上,加上兩個注解@EnableEurekaServer、@SpringBootApplication
啟動
方式一:Teriminal窗口
/** 進入項目目錄 */
cd 項目名
/** 將項目打包成jar */
mvn clean package -Dmaven.test.skip=true -U
/** 進入target目錄 */
cd target
/** 用jar命令啟動項目 */
java -jar 項目名.jar
方式二:
直接運行application,啟動即可
上面是Eureka的單節點啟動方式,公司中一般為了防止Eureka單節點故障,下面介紹Eureka的多節點偽集群配置方式
- 在原項目資源文件夾下創建bootstrap.yml,注釋掉application.yml中的配置,因為在springBoot項目中會先加載bootstrap.yml
- 修改Windows中C:\Windows\System32\drivers\etc路徑下的hosts文件
127.0.0.1 server1 127.0.0.1 server2 127.0.0.1 server3
bootstrap.yml:
spring: application: name: spring-cloud-eureka profiles: server1 server: port: 8000 eureka: instance: hostname: server1 prefer-ip-address: false client: service-url: defaultZone: http://server2:8001/eureka/,http://server3:8002/eureka/ --- spring: application: name: spring-cloud-eureka profiles: server2 server: port: 8001 eureka: instance: hostname: server2 prefer-ip-address: false client: service-url: defaultZone: http://server1:8000/eureka/,http://server3:8002/eureka/ --- spring: application: name: spring-cloud-eureka profiles: server3 server: port: 8002 eureka: instance: hostname: server3 prefer-ip-address: false client: service-url: defaultZone: http://server1:8000/eureka/,http://server2:8001/eureka/
以上配置完成后,在以上啟動方式一的基礎上,在啟動時需加上一個命令,以表示啟動哪個Eureka節點
java -jar 項目名.jar --spring.profiles.active=server1 #標識啟動Eureka的server1的服務,以此類推
啟動完成后可在瀏覽器中訪問
http://127.0.0.1:8000/
父模塊中創建zuul網關服務
pom.xml中引入相關依賴、插件
<!-- 模塊名及描述信息 --> <name>spring-cloud-zuul</name> <description>Spring Cloud Gateway</description> <dependencies> <!-- Eureka 客戶端, 客戶端向 Eureka Server 注冊的時候會提供一系列的元數據信息, 例如: 主機, 端口, 健康檢查url等 Eureka Server 接受每個客戶端發送的心跳信息, 如果在某個配置的超時時間內未接收到心跳信息, 實例會被從注冊列表中移除 --> <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> <!-- apache 工具類 --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>1.3.2</version> </dependency> </dependencies> <!-- SpringBoot的Maven插件, 能夠以Maven的方式為應用提供SpringBoot的支持,可以將 SpringBoot應用打包為可執行的jar或war文件, 然后以通常的方式運行SpringBoot應用 --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
資源文件夾中創建application.yml
spring: application: name: spring-cloud-zuul server: port: 9000 eureka: client: service-url: defaultZone: http://server1:8000/eureka zuul: prefix: /vast routes: course: path: /course/** serviceId: spring-cloud-course-service strip-prefix: false user: path: /user/** serviceId: spring-cloud-user-service strip-prefix: false
啟動類添加兩個注解
@SpringCloudApplication
@EnableZuulProxy
注意:這兩個注解和Eureka中啟動類中聲明的注解是不一樣的
創建兩個過濾類,來計算不同URL請求到響應時所需要的時間
PreFilter.class
package com.vast.zuul.filters; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.stereotype.Component; @Component public class PreFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext currentContext = RequestContext.getCurrentContext(); currentContext.set("startTime", System.currentTimeMillis()); return null; } }
AccessLogFilter.class
package com.vast.zuul.filters; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; @Slf4j @Component public class AccessLogFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.POST_TYPE; } @Override public int filterOrder() { return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext currentContext = RequestContext.getCurrentContext(); Long startTime = (Long) currentContext.get("startTime"); HttpServletRequest request = currentContext.getRequest(); String requestURI = request.getRequestURI(); Long duration = System.currentTimeMillis() - startTime; log.info("uri:{},duration:{}ms", requestURI, duration / 100); return null; } }
在父模塊下再創建一個子模塊服務,作為另外幾個服務的父模塊(spring-cloud-service)
- 刪掉不需要的src文件夾
- 在pom.xml中添加
<packaging>pom</packaging>
- 在該模塊下,建立公共服務spring-cloud-common
在服務於服務之間,會有一些公共使用的類或方法,避免重復創建以及維護困難,建立公共模塊
- 在pom.xml引入一些依賴
<dependencies> <!-- JSON 處理工具 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.31</version> </dependency> <!-- apache 提供的一些工具類 --> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.9</version> </dependency> </dependencies>
- 在src目錄下創建需要用到的公共類
在當前父模塊(spring-cloud-service)中創建一個課程服務模塊(spring-cloud-course-service)
- 在pom.xml中引入相關依賴以及插件
<dependencies> <!-- 引入 Web 功能 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Eureka 客戶端, 客戶端向 Eureka Server 注冊的時候會提供一系列的元數據信息, 例如: 主機, 端口, 健康檢查url等 Eureka Server 接受每個客戶端發送的心跳信息, 如果在某個配置的超時時間內未接收到心跳信息, 實例會被從注冊列表中移除 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- Java Persistence API, ORM 規范 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- MySQL 驅動, 注意, 這個需要與 MySQL 版本對應 --> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.6</version> </dependency> <!-- 通用模塊 --> <dependency> <groupId>com.vast.spring-cloud</groupId> <artifactId>spring-cloud-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> <!-- SpringBoot的Maven插件, 能夠以Maven的方式為應用提供SpringBoot的支持,可以將 SpringBoot應用打包為可執行的jar或war文件, 然后以通常的方式運行SpringBoot應用 --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
- 創建application.yml
server: port: 7001 servlet: context-path: /course spring: application: name: spring-cloud-course-service jpa: show-sql: true hibernate: ddl-auto: none properties: hibernate.format_sql: true open-in-view: false datasource: url: jdbc:mysql://127.0.0.1:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8 username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver tomcat: max-active: 4 min-idle: 2 initial-size: 2 eureka: client: service-url: defaultZone: http://server1:8000/eureka/
- 在啟動類中添加三個注解
@SpringBootApplication
/** 向Eureka中注冊服務 */
@EnableEurekaClient
/** 用JPA來管理數據 */
@EnableJpaAuditing
- 建立實體類
package com.vast.entry; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import javax.persistence.*; import java.util.Date; @Data
/* 表明該實體類中的setter方法是private的,需要通過Builder進行build() **/ @Builder @Entity @NoArgsConstructor @AllArgsConstructor
/** 用來通過注解自動向數據庫中插入時間 */ @EntityListeners(AuditingEntityListener.class) @Table(name = "t_course") public class CoursePo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") /** 自增 id */ private Long id; @Column(name = "course_name", nullable = false) /** 課程名稱 */ private String courseName; @Column(name = "course_type", nullable = false) /** 課程類型 */ private Integer courseType; @Column(name = "course_icon", nullable = false) /** 課程圖標 */ private String courseIcon; @Column(name = "course_intro", nullable = false) /** 課程介紹 */ private String courseIntro; @CreatedDate @Column(name = "create_time") /** 創建時間 */ private Date createTime;
/** Basic用來表明是列,默認的,不用寫也是可以的 */ @Basic @LastModifiedDate @Column(name = "update_time") /** 更新時間 */ private Date updateTime; }
- 創建Dao
package com.vast.dao; import com.vast.entry.CoursePo; import org.springframework.data.jpa.repository.JpaRepository; /** Jpa中有許多已經配置好的方法,以及查詢規則,只要按照其指定的規則來即可快速操作數據庫中的數據 */ public interface CourseDao extends JpaRepository<CoursePo, Long> { }
- 課程實現類 CourseImpl
package com.vast.service.impl; import com.vast.service.ICourseService; import com.vast.common.entry.Course; import com.vast.common.entry.RequestContent; import com.vast.dao.CourseDao; import com.vast.entry.CoursePo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @Service public class CourseImpl implements ICourseService { @Autowired private CourseDao courseDao; @Override public Course getCourseById(Long id) { Optional<CoursePo> course = courseDao.findById(id);
/** java8特性,Lambda表達式,判斷實體類是否為空,防止拋出空指針異常 */ return builderCoursePo(course.orElseGet(() -> CoursePo.invalid())); } @Override public List<Course> getCourseListByIds(RequestContent requestContent) { List<Long> ids = requestContent.getIds(); if (CollectionUtils.isEmpty(ids)) { return Collections.emptyList(); } List<CoursePo> courseList = courseDao.findAllById(ids);
/** java8 特性 */ return courseList.stream().map(this::builderCoursePo) .collect(Collectors.toList()); } /** * @param course * @return * @Description 構造課程信息
* Builder使用方法 */ public Course builderCoursePo(CoursePo course) { return Course.builder() .courseIcon(course.getCourseIcon()) .courseIntro(course.getCourseIntro()) .courseName(course.getCourseName()) .courseType(course.getCourseType() == 0 ? "SpringCloud" : "SSM") .build(); } }
測試類
- 啟動類中添加注解
@SpringBootApplication
- 測試類中添加注解
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {TestCourseApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.NONE)
在當前父模塊(spring-cloud-service)中創建一個課程服務模塊(spring-cloud-user-service)
- 在pom.xml中引入相關依賴、插件
<dependencies> <!-- 引入 Web 功能 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Eureka 客戶端, 客戶端向 Eureka Server 注冊的時候會提供一系列的元數據信息, 例如: 主機, 端口, 健康檢查url等 Eureka Server 接受每個客戶端發送的心跳信息, 如果在某個配置的超時時間內未接收到心跳信息, 實例會被從注冊列表中移除 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- 引入 Feign, 可以以聲明的方式調用微服務 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- 引入服務容錯 Hystrix 的依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <!-- Java Persistence API, ORM 規范 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- MySQL 驅動, 注意, 這個需要與 MySQL 版本對應 --> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.6</version> </dependency> <!-- 通用模塊 --> <dependency> <groupId>com.vast.spring-cloud</groupId> <artifactId>spring-cloud-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.jetbrains</groupId> <artifactId>annotations</artifactId> <version>RELEASE</version> <scope>compile</scope> </dependency> </dependencies> <!-- SpringBoot的Maven插件, 能夠以Maven的方式為應用提供SpringBoot的支持,可以將 SpringBoot應用打包為可執行的jar或war文件, 然后以通常的方式運行SpringBoot應用 --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
- 在資源文件夾創建application.yml
server: port: 7000 servlet: context-path: /user spring: main: allow-bean-definition-overriding: true application: name: spring-cloud-user-service jpa: show-sql: true hibernate: ddl-auto: none properties: hibernate.format_sql: true open-in-view: false datasource: url: jdbc:mysql://127.0.0.1:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8 username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver tomcat: max-active: 4 min-idle: 2 initial-size: 2 eureka: client: service-url: defaultZone: http://server1:8000/eureka/ feign: hystrix: enabled: true
- 在啟動類添加注解
/** 支持Feign調用 */
@EnableFeignClients
/** 支持服務熔斷降級 */
@EnableCircuitBreaker
/** 支持JPA */
@EnableJpaAuditing
/** 支持想Eureka注冊服務 */
@EnableEurekaClient
@SpringBootApplication
- 實現類UserInfoServiceImpl
package com.vast.service.impl; import com.vast.client.ICourseFeignClient; import com.vast.common.entry.Course; import com.vast.common.entry.RequestContent; import com.vast.common.entry.User; import com.vast.dao.UserCourseDao; import com.vast.dao.UserDao; import com.vast.entity.UserCourseInfo; import com.vast.entity.UserInfo; import com.vast.service.IUserService; import com.vast.vo.CreateUserRequestVo; import com.vast.vo.UserCourseInfoVo; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @Slf4j @Service public class UserInfoServiceImpl implements IUserService { private final UserDao userDao; private final UserCourseDao userCourseDao; private final ICourseFeignClient iCourseFeignClient; @Autowired public UserInfoServiceImpl(UserDao userDao, UserCourseDao userCourseDao, ICourseFeignClient iCourseFeignClient) { this.userDao = userDao; this.userCourseDao = userCourseDao; this.iCourseFeignClient = iCourseFeignClient; } @Override public User getUserInfoById(Long id) { Optional<UserInfo> userInfoById = userDao.findById(id);
/** 判斷對象是否為空 */ if (!userInfoById.isPresent()) { return new User().invalidate(); } return builderUserInfo(userInfoById.get()); } @Override public User createUserInfo(CreateUserRequestVo createUserRequestVo) { /** 判斷傳的參數是否為空 */ if (!createUserRequestVo.validate()) { return User.invalidate(); } /** 判斷該用戶在用戶表中已存在 */ UserInfo userInfoByUsername = userDao.findByUsername(createUserRequestVo.getUsername()); if (null != userInfoByUsername) { return User.invalidate(); } UserInfo saveUserInfo = userDao.save(new UserInfo(createUserRequestVo.getUsername(), createUserRequestVo.getEmail())); return new User(saveUserInfo.getId(), saveUserInfo.getUsername(), saveUserInfo.getEmail()); } @Override public UserCourseInfoVo getUserCourseInfoById(Long id) { /** 根據用戶id判斷是否有該用戶 */ Optional<UserInfo> userInfoById = userDao.findById(id); if (!userInfoById.isPresent()) { return UserCourseInfoVo.invalidate(); } /** 獲取對象 */ UserInfo userInfo = userInfoById.get(); User user = new User(userInfo.getId(), userInfo.getUsername(), userInfo.getEmail()); /** 根據用戶id判斷用戶課程表中是否有該對應的信息 */ List<UserCourseInfo> allUserCourseInfoByUserId = userCourseDao.findAllByUserId(id); if (CollectionUtils.isEmpty(allUserCourseInfoByUserId)) { return new UserCourseInfoVo(Collections.emptyList(), user); } /** 如果用戶id在用戶表、用戶課程表中都存在,則用feign客戶端調用課程服務,獲取對應 * 的課程信息,並將用戶、課程信息進行返回 * */ List<Course> coursesByIds = iCourseFeignClient.getCoursesByIds(new RequestContent(allUserCourseInfoByUserId.stream() .map(UserCourseInfo::getCourseId).collect(Collectors.toList()))); return new UserCourseInfoVo(coursesByIds, user); } private User builderUserInfo(UserInfo userInfo) { return User.builder() .id(userInfo.getId()) .email(userInfo.getEmail()) .username(userInfo.getUsername()) .build(); } }
- Feign調用客戶端
package com.vast.client; import com.vast.common.entry.Course; import com.vast.common.entry.RequestContent; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; import java.util.List;
/** 1.FeignClient表明是Feign調用;
* 2.value表明是調用哪個服務實例;
* 3.fallback是表明熔斷時調用哪個類;
* 4.@RequestMapping中的請求方式以及URL和所調用的服務的控制層中的保持一致
*/ @FeignClient(value = "spring-cloud-course-service", fallback = CourseHystrixClient.class) public interface ICourseFeignClient { @RequestMapping(value = "/get/course/", method = RequestMethod.GET) public Course getCourseInfoById(Long id); @RequestMapping(value = "/get/courses", method = RequestMethod.POST) public List<Course> getCoursesByIds(@RequestBody RequestContent requestContent); }
- Hystrix
package com.vast.client; import com.vast.common.entry.Course; import com.vast.common.entry.RequestContent; import org.springframework.stereotype.Component; import java.util.Collections; import java.util.List;
/** 1.@Component聲明該類是spring的bean實例;
* 2.實現Feign客戶端接口方法;
* 3.進行熔斷處理;
*/ @Component public class CourseHystrixClient implements ICourseFeignClient { @Override public Course getCourseInfoById(Long id) { return Course.inValidEntry(); } @Override public List<Course> getCoursesByIds(RequestContent requestContent) { return Collections.emptyList(); } }
測試類
- 啟動類中添加注解
@EnableFeignClients(basePackages = {"com.vast"})
@SpringBootApplication
- 測試類中添加注解
@RunWith(SpringRunner.class)
@SpringBootTest(classes = CourseApplication.class)
效果圖