springboot教程(三)
擼了今年阿里、頭條和美團的面試,我有一個重要發現.......>>>
springboot微服務
新建項目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,內容如下:
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中執行上面的sql語句。
然后在resources下新建application.properties,內容如下:
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 可以將返回的時間格式化,否則會返回類似2019-06-04T22:06:56.000+0000的時間格式
spring.jackson.time-zone=GMT-5 數據庫的時間與springboot返回的時間不一致,需要修改時區
新建包com.edu.mall.product,新建bean包,并且建立product.java,內容如下:
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,內容如下:
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(); }測試mybatis是否配置成功,App.java中的內容如下:
 ?
點擊運行App.java 可以成功插入到mysql數據庫中。說明mybatis已經集成成功了。
將上面的App.java中的測試關閉。
新建web包,然后新建Respose.java 內容如下:
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,內容如下
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 服務*/ @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);}}運行App.java,使用postman測試運行成功。
新建項目mallweb
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-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>同時將mallproduct中的Product.java和Response.java拷貝到這個項目中,新建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());} }把原先的一個大系統,拆分成小的系統
????每個小系統分別開發,測試,維護。
調用方式:
? ? 服務提供的是什么服務?rest(http),web service,rpc
????? ? rest方式可以使用RestTemplate, httpclient
?springboot 服務注冊與發現
web端調用服務的方式常用的有兩種:
? ? 1. Nginx,將服務地址配置在Nginx,由web端連接Nginx做代理,Nginx做負載均衡,但是這種方式是靜態方式,每部署一臺就需要在Nginx配置上。
? ? 2. 注冊中心,服務方(微服務),調用方。首先服務提供方將服務提供到注冊中心,然后調用方從注冊中心拿到地址,通常服務提供方把自己的地址(ip:port)提交到注冊中心,調用方從注冊中心獲取ip和端口號,獲取之后,就可以直接與服務方連接調用。好處就是服務是可以動態添加和刪除。如果獲取到多個ip和端口,可以使用負載均衡算法選擇其中的一個。還有一個好處就是,調用方只需要知道注冊中心的地址,不需要服務端的地址。只需要維護一個ip地址就可以了。
如何實現注冊中心?
本節依然使用上面的項目mallproduct和mallweb
zookeeper,consul,etcd,redis,通常使用這幾種來作為注冊中心。
使用zookeeper來作為注冊中心。使用curator框架使用zookeeper,curator對zookeeper進行了封裝。
首先加載curator依賴:
? ? 服務注冊方添加依賴:在mall-Product/pom.xml中:
????
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-x-discovery-server</artifactId><version>4.2.0</version></dependency>? ? 服務發現方添加依賴:在mall-web/pom.xml中
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-x-discovery</artifactId><version>4.2.0</version></dependency>服務的注冊?
常見的注冊中心:zookeeper, consul, etcd, redis
 服務提供方,需要在服務啟動的時候,把服務的信息(ip,端口)注冊到注冊中心(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運行App.java,控制臺顯示service register success,說明服務注冊成功。
查看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節點,查看soa下面的服務:
[zk: localhost:2181(CONNECTED) 2] ls /soa [product] [zk: localhost:2181(CONNECTED) 3可以看到product服務的名字。繼續查看:
[zk: localhost:2181(CONNECTED) 3] ls /soa/product [f63b4b19-e313-419a-acba-5aab6e08fc25] [zk: localhost:2181(CONNECTED) 4]查看詳細信息:
[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]服務的發現
在項目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說明可以發現服務。
調用服務,修改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);});} }輸出結果如下:
{"code":"200","msg":"OK","data":{"pid":1,"pname":"python入門","type":"計算機類","price":133.0,"createTime":"2019-06-04 17:06:56"}}說明調用成功。
這種情況下,每調用一次就往注冊中心查一次,一般在正式的環境中不這樣做,一般會從緩存中拿取。
服務發現
 ?在進行服務調用的時候,需要先從注冊中心獲取到服務的地址,然后根據獲取到的服務地址進行調用
?目前我們只獲取到一個服務,如果我們要獲取到多個服務如何做?
將mall-product復制一份,改名為mall-product2
修改mall-product2的端口號為9090。同時在ServiceRegister.java中修改端口號為9090:
ServiceInstance<Object> instance = ServiceInstance.builder().name("product").address("192.168.170.132").port(9090).build();運行mall-product和mall-product2,然后可以看到有兩個服務啟動:
[zk: localhost:2181(CONNECTED) 22] ls /soa/product [19568e15-9a90-4174-953f-f00124bc9595, 59eae9c2-525c-455e-839a-d3a2284bcf19]然后運行mall-web項目的Client.java,輸出結果如下:
{"code":"200","msg":"OK","data":{"pid":1,"pname":"python入門","type":"計算機類","price":133.0,"createTime":"2019-06-04 17:06:56"}} {"code":"200","msg":"OK","data":{"pid":1,"pname":"python入門","type":"計算機類","price":133.0,"createTime":"2019-06-04 17:06:56"}}輸出了兩遍,我們需要根據算法做一些選擇。隨機或者輪訓。新建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;/*** 服務發現* 在進行服務調用的時候,需要先從注冊中心獲取到服務的地址,然后根據獲取到的服務地址進行調用*/ 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);} }這樣當多個服務端提供服務時,可以輪訓進行服務提供,當某個服務down掉后不影響。
springboot打包運行
方法一
在項目路徑下執行:
mvn clean package可以將項目打包成jar包,但是依賴沒有加進去。
使用命令
mvn clean package dependency:copy-dependencies可以將依賴包拷貝到target/dependency路徑下,然后將mall-product-1.0-SNAPSHOT.jar也拷貝到target/dependency下,在target路徑下執行:
java -Djava.ext.dirs=dependency com.edu.mall.product.App執行成功
方法二
總結
以上是生活随笔為你收集整理的springboot教程(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: springboot 跨域配置cors
 - 下一篇: zookeeper教程