springboot教程(三)
擼了今年阿里、頭條和美團(tuán)的面試,我有一個(gè)重要發(fā)現(xiàn).......>>>
springboot微服務(wù)
新建項(xiàng)目mallproduct
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.edu.mall.product</groupId><artifactId>mall-product</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.1.4.RELEASE</version><scope>import</scope><type>pom</type></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.5</version></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>LATEST</version></dependency></dependencies></project>在resources下新建product.sql,內(nèi)容如下:
CREATE database db_products default charset utf8; create table products (pid int not null primary key auto_increment, pname varchar (200), type varchar (50), price double, createTime timestamp )在mysql中執(zhí)行上面的sql語(yǔ)句。
然后在resources下新建application.properties,內(nèi)容如下:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://192.168.152.45:3306/db_products?useSSL=false&characterEncoding=utf8 spring.datasource.username=root spring.datasource.password=123456spring.jackson.date-format=yyyy-MM-dd HH:mm:ssspring.jackson.date-format=yyyy-MM-dd HH:mm:ss 可以將返回的時(shí)間格式化,否則會(huì)返回類似2019-06-04T22:06:56.000+0000的時(shí)間格式
spring.jackson.time-zone=GMT-5 數(shù)據(jù)庫(kù)的時(shí)間與springboot返回的時(shí)間不一致,需要修改時(shí)區(qū)
新建包c(diǎn)om.edu.mall.product,新建bean包,并且建立product.java,內(nèi)容如下:
package com.edu.mall.product.bean;import java.sql.Timestamp;public class Product {private Integer pid;private String pname;private String type;private Double price;private Timestamp createTime;@Overridepublic String toString() {return "Product{" +"pid=" + pid +", pname='" + pname + '\'' +", type='" + type + '\'' +", price=" + price +", createTime=" + createTime +'}';}public Integer getPid() {return pid;}public void setPid(Integer pid) {this.pid = pid;}public String getPname() {return pname;}public void setPname(String pname) {this.pname = pname;}public String getType() {return type;}public void setType(String type) {this.type = type;}public Double getPrice() {return price;}public void setPrice(Double price) {this.price = price;}public Timestamp getCreateTime() {return createTime;}public void setCreateTime(Timestamp createTime) {this.createTime = createTime;} }新建mapper包,在mapper包中,新建ProductMapper.java,內(nèi)容如下:
package com.edu.mall.product.mapper;import com.edu.mall.product.bean.Product; import org.apache.ibatis.annotations.*;import java.util.List;@Mapper public interface ProductMapper {@Insert("insert into products (pname,type,price) values (#{pname}, #{type}, #{price})")public Integer add(Product product);@Delete("delete from products where pid=#{arg1}")public Integer deleteById(Integer pid);@Update("update products set pname=#{pname}, type=#{type}, price=#{price} where pid=#{pid}")public Integer update(Product product);@Select("select * from products where pid=#{arg1}")public Product getById(Integer pid);@Select("select * from products order by pid desc")public List<Product> queryByList(); }測(cè)試mybatis是否配置成功,App.java中的內(nèi)容如下:
?
點(diǎn)擊運(yùn)行App.java 可以成功插入到mysql數(shù)據(jù)庫(kù)中。說明mybatis已經(jīng)集成成功了。
將上面的App.java中的測(cè)試關(guān)閉。
新建web包,然后新建Respose.java 內(nèi)容如下:
package com.edu.mall.product.web;public class Response {/*** 200 表示成功* 500 表示失敗*/private String code;private String msg;private Object data;@Overridepublic String toString() {return "Response{" +"code='" + code + '\'' +", msg='" + msg + '\'' +", data=" + data +'}';}public Response(String code, String msg) {this.code = code;this.msg = msg;}public Response(String code, String msg, Object data) {this.code = code;this.msg = msg;this.data = data;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public Object getData() {return data;}public void setData(Object data) {this.data = data;} }新建controller包,然后新建ProductController.java,內(nèi)容如下
package com.edu.mall.product.controller;import com.edu.mall.product.bean.Product; import com.edu.mall.product.mapper.ProductMapper; import com.edu.mall.product.web.Response; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*;import java.util.List;/*** product rest 服務(wù)*/ @RestController public class ProductController {@Autowiredprivate ProductMapper productMapper;@PostMapping("/soa/product/add")public Object add(Product product) {Integer res = productMapper.add(product);return res == 1 ? new Response("200", "OK") : new Response("500", "Fail");}@PutMapping("/soa/product/update")public Object update(Product product) {Integer res = productMapper.update(product);return res == 1 ? new Response("200", "OK") : new Response("500", "Fail");}@PostMapping("/soa/product/{id}")public Object get(@PathVariable("id") Integer id) {Product product = productMapper.getById(id);return new Response("200", "OK", product);}@DeleteMapping("/soa/product/{id}")public Object delete(@PathVariable("id") Integer id) {Integer res = productMapper.deleteById(id);return res == 1 ? new Response("200", "OK") : new Response("500", "Fail");}@GetMapping("/soa/products")public Object list(Integer id) {List<Product> products = productMapper.queryByList();return new Response("200", "OK", products);}}運(yùn)行App.java,使用postman測(cè)試運(yùn)行成功。
新建項(xiàng)目mallweb
pom.xml內(nèi)容如下:
<?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.edu.mall.product</groupId><artifactId>mall-web</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencies><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.5</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.6.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.1.6.RELEASE</version></dependency></dependencies></project>同時(shí)將mallproduct中的Product.java和Response.java拷貝到這個(gè)項(xiàng)目中,新建App.java
package com.edu.mall.web;import com.google.gson.Gson; import org.springframework.web.client.RestTemplate; import web.Response;public class App {static String BASE_URL = "http://127.0.0.1:8080";public static void main(String[] args) {RestTemplate restTemplate = new RestTemplate();String body = restTemplate.getForObject(BASE_URL + "/soa/product/1", String.class);System.out.println(body);Response response = new Gson().fromJson(body, Response.class);System.out.println(response);System.out.println(response.getCode());System.out.println(response.getMsg());System.out.println(response.getData());} }把原先的一個(gè)大系統(tǒng),拆分成小的系統(tǒng)
????每個(gè)小系統(tǒng)分別開發(fā),測(cè)試,維護(hù)。
調(diào)用方式:
? ? 服務(wù)提供的是什么服務(wù)?rest(http),web service,rpc
????? ? rest方式可以使用RestTemplate, httpclient
?springboot 服務(wù)注冊(cè)與發(fā)現(xiàn)
web端調(diào)用服務(wù)的方式常用的有兩種:
? ? 1. Nginx,將服務(wù)地址配置在Nginx,由web端連接Nginx做代理,Nginx做負(fù)載均衡,但是這種方式是靜態(tài)方式,每部署一臺(tái)就需要在Nginx配置上。
? ? 2. 注冊(cè)中心,服務(wù)方(微服務(wù)),調(diào)用方。首先服務(wù)提供方將服務(wù)提供到注冊(cè)中心,然后調(diào)用方從注冊(cè)中心拿到地址,通常服務(wù)提供方把自己的地址(ip:port)提交到注冊(cè)中心,調(diào)用方從注冊(cè)中心獲取ip和端口號(hào),獲取之后,就可以直接與服務(wù)方連接調(diào)用。好處就是服務(wù)是可以動(dòng)態(tài)添加和刪除。如果獲取到多個(gè)ip和端口,可以使用負(fù)載均衡算法選擇其中的一個(gè)。還有一個(gè)好處就是,調(diào)用方只需要知道注冊(cè)中心的地址,不需要服務(wù)端的地址。只需要維護(hù)一個(gè)ip地址就可以了。
如何實(shí)現(xiàn)注冊(cè)中心?
本節(jié)依然使用上面的項(xiàng)目mallproduct和mallweb
zookeeper,consul,etcd,redis,通常使用這幾種來作為注冊(cè)中心。
使用zookeeper來作為注冊(cè)中心。使用curator框架使用zookeeper,curator對(duì)zookeeper進(jìn)行了封裝。
首先加載curator依賴:
? ? 服務(wù)注冊(cè)方添加依賴:在mall-Product/pom.xml中:
????
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-x-discovery-server</artifactId><version>4.2.0</version></dependency>? ? 服務(wù)發(fā)現(xiàn)方添加依賴:在mall-web/pom.xml中
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-x-discovery</artifactId><version>4.2.0</version></dependency>服務(wù)的注冊(cè)?
常見的注冊(cè)中心:zookeeper, consul, etcd, redis
服務(wù)提供方,需要在服務(wù)啟動(dòng)的時(shí)候,把服務(wù)的信息(ip,端口)注冊(cè)到注冊(cè)中心(zookeeper)
在mall-product中新建ServiceRegister.java
package com.edu.mall.product;import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.RetryOneTime; import org.apache.curator.x.discovery.ServiceDiscovery; import org.apache.curator.x.discovery.ServiceDiscoveryBuilder; import org.apache.curator.x.discovery.ServiceInstance; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component;@Component public class ServiceRegister implements ApplicationRunner {@Value("${zookeeper.address}")private String zkAddress;@Overridepublic void run(ApplicationArguments args) throws Exception {CuratorFramework client = CuratorFrameworkFactory.newClient(zkAddress, new RetryOneTime(1000));client.start();client.blockUntilConnected();ServiceInstance<Object> instance = ServiceInstance.builder().name("product").address("192.168.170.132").port(8080).build();ServiceDiscovery<Object> serviceDiscovery = ServiceDiscoveryBuilder.builder(Object.class).client(client).basePath("/soa").build();serviceDiscovery.registerService(instance);serviceDiscovery.start();System.out.println("service register success");} }在application.properties中添加配置:
zookeeper.address=192.168.152.45:2181運(yùn)行App.java,控制臺(tái)顯示service register success,說明服務(wù)注冊(cè)成功。
查看zookeeper,使用命令
iie4bu@hostdocker:~/apache-zookeeper-3.5.5-bin/bin$ ./zkCli.sh [zk: localhost:2181(CONNECTED) 0] ls / [soa, zookeeper] [zk: localhost:2181(CONNECTED) 1]可以看到有了soa節(jié)點(diǎn),查看soa下面的服務(wù):
[zk: localhost:2181(CONNECTED) 2] ls /soa [product] [zk: localhost:2181(CONNECTED) 3可以看到product服務(wù)的名字。繼續(xù)查看:
[zk: localhost:2181(CONNECTED) 3] ls /soa/product [f63b4b19-e313-419a-acba-5aab6e08fc25] [zk: localhost:2181(CONNECTED) 4]查看詳細(xì)信息:
[zk: localhost:2181(CONNECTED) 4] get /soa/product/f63b4b19-e313-419a-acba-5aab6e08fc25 {"name":"product","id":"f63b4b19-e313-419a-acba-5aab6e08fc25","address":"192.168.170.132","port":8080,"sslPort":null,"payload":null,"registrationTimeUTC":1560151924584,"serviceType":"DYNAMIC","uriSpec":null} [zk: localhost:2181(CONNECTED) 5]服務(wù)的發(fā)現(xiàn)
在項(xiàng)目mallweb中新建Client.java
package com.edu.mall.web;import com.google.gson.Gson; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.RetryOneTime; import org.apache.curator.x.discovery.ServiceDiscovery; import org.apache.curator.x.discovery.ServiceDiscoveryBuilder; import org.apache.curator.x.discovery.ServiceInstance; import org.springframework.web.client.RestTemplate; import web.Response;import java.util.Collection;public class Client {public static void main(String[] args) throws Exception {CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.152.45:2181", new RetryOneTime(1000));client.start();client.blockUntilConnected();RestTemplate restTemplate = new RestTemplate();ServiceDiscovery<Object> serviceDiscovery = ServiceDiscoveryBuilder.builder(Object.class).client(client).basePath("/soa").build();Collection<ServiceInstance<Object>> products = serviceDiscovery.queryForInstances("product");products.forEach((instance) -> {System.out.println(instance.getAddress());System.out.println(instance.getPort());});} }輸出如下:
192.168.170.132 8080說明可以發(fā)現(xiàn)服務(wù)。
調(diào)用服務(wù),修改Client.java
package com.edu.mall.web;import com.google.gson.Gson; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.RetryOneTime; import org.apache.curator.x.discovery.ServiceDiscovery; import org.apache.curator.x.discovery.ServiceDiscoveryBuilder; import org.apache.curator.x.discovery.ServiceInstance; import org.springframework.web.client.RestTemplate; import web.Response;import java.util.Collection;public class Client {public static void main(String[] args) throws Exception {CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.152.45:2181", new RetryOneTime(1000));client.start();client.blockUntilConnected();ServiceDiscovery<Object> serviceDiscovery = ServiceDiscoveryBuilder.builder(Object.class).client(client).basePath("/soa").build();Collection<ServiceInstance<Object>> products = serviceDiscovery.queryForInstances("product");products.forEach((instance) -> {RestTemplate restTemplate = new RestTemplate();String body = restTemplate.getForObject("http://" + instance.getAddress() + ":" + instance.getPort() +"/soa/product/1", String.class);System.out.println(body);Response response = new Gson().fromJson(body, Response.class);});} }輸出結(jié)果如下:
{"code":"200","msg":"OK","data":{"pid":1,"pname":"python入門","type":"計(jì)算機(jī)類","price":133.0,"createTime":"2019-06-04 17:06:56"}}說明調(diào)用成功。
這種情況下,每調(diào)用一次就往注冊(cè)中心查一次,一般在正式的環(huán)境中不這樣做,一般會(huì)從緩存中拿取。
服務(wù)發(fā)現(xiàn)
?在進(jìn)行服務(wù)調(diào)用的時(shí)候,需要先從注冊(cè)中心獲取到服務(wù)的地址,然后根據(jù)獲取到的服務(wù)地址進(jìn)行調(diào)用
?目前我們只獲取到一個(gè)服務(wù),如果我們要獲取到多個(gè)服務(wù)如何做?
將mall-product復(fù)制一份,改名為mall-product2
修改mall-product2的端口號(hào)為9090。同時(shí)在ServiceRegister.java中修改端口號(hào)為9090:
ServiceInstance<Object> instance = ServiceInstance.builder().name("product").address("192.168.170.132").port(9090).build();運(yùn)行mall-product和mall-product2,然后可以看到有兩個(gè)服務(wù)啟動(dòng):
[zk: localhost:2181(CONNECTED) 22] ls /soa/product [19568e15-9a90-4174-953f-f00124bc9595, 59eae9c2-525c-455e-839a-d3a2284bcf19]然后運(yùn)行mall-web項(xiàng)目的Client.java,輸出結(jié)果如下:
{"code":"200","msg":"OK","data":{"pid":1,"pname":"python入門","type":"計(jì)算機(jī)類","price":133.0,"createTime":"2019-06-04 17:06:56"}} {"code":"200","msg":"OK","data":{"pid":1,"pname":"python入門","type":"計(jì)算機(jī)類","price":133.0,"createTime":"2019-06-04 17:06:56"}}輸出了兩遍,我們需要根據(jù)算法做一些選擇。隨機(jī)或者輪訓(xùn)。新建LoadBalance.java
package com.edu.mall.web;import java.util.List;public class LoadBalance {private List<String> services;private int index = 0;public LoadBalance(List<String> services) {this.services = services;}public String choose() {String service = services.get(index);index++;if (index >= services.size()) {index = 0;}return service;} }然后修改Client.java
package com.edu.mall.web;import com.google.gson.Gson; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.RetryOneTime; import org.apache.curator.x.discovery.ServiceDiscovery; import org.apache.curator.x.discovery.ServiceDiscoveryBuilder; import org.apache.curator.x.discovery.ServiceInstance; import org.springframework.web.client.RestTemplate; import web.Response;import java.util.ArrayList; import java.util.Collection; import java.util.List;/*** 服務(wù)發(fā)現(xiàn)* 在進(jìn)行服務(wù)調(diào)用的時(shí)候,需要先從注冊(cè)中心獲取到服務(wù)的地址,然后根據(jù)獲取到的服務(wù)地址進(jìn)行調(diào)用*/ public class Client {public static void main(String[] args) throws Exception {CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.152.45:2181", new RetryOneTime(1000));client.start();client.blockUntilConnected();ServiceDiscovery<Object> serviceDiscovery = ServiceDiscoveryBuilder.builder(Object.class).client(client).basePath("/soa").build();Collection<ServiceInstance<Object>> products = serviceDiscovery.queryForInstances("product");final List<String> services = new ArrayList<>();products.forEach((instance) -> {services.add(instance.getAddress() + ":" + instance.getPort());});LoadBalance loadBalance = new LoadBalance(services);RestTemplate restTemplate = new RestTemplate();String body = restTemplate.getForObject("http://" + loadBalance.choose() + "/soa/product/1", String.class);System.out.println(body);Response response = new Gson().fromJson(body, Response.class);} }這樣當(dāng)多個(gè)服務(wù)端提供服務(wù)時(shí),可以輪訓(xùn)進(jìn)行服務(wù)提供,當(dāng)某個(gè)服務(wù)down掉后不影響。
springboot打包運(yùn)行
方法一
在項(xiàng)目路徑下執(zhí)行:
mvn clean package可以將項(xiàng)目打包成jar包,但是依賴沒有加進(jìn)去。
使用命令
mvn clean package dependency:copy-dependencies可以將依賴包拷貝到target/dependency路徑下,然后將mall-product-1.0-SNAPSHOT.jar也拷貝到target/dependency下,在target路徑下執(zhí)行:
java -Djava.ext.dirs=dependency com.edu.mall.product.App執(zhí)行成功
方法二
總結(jié)
以上是生活随笔為你收集整理的springboot教程(三)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot 跨域配置cors
- 下一篇: zookeeper教程