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=123456

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss

spring.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;

    @Override
    public 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中的内容如下:
 

package com.edu.mall.product;

import com.edu.mall.product.bean.Product;
import com.edu.mall.product.mapper.ProductMapper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(App.class, args);
        ProductMapper productMapper = run.getBean(ProductMapper.class);
        Product product = new Product();
        product.setPname("java入门到精通");
        product.setPrice(10.0);
        product.setType("计算机");
        productMapper.add(product);
        run.close();
    }

}

点击运行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;

    @Override
    public 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 {

    @Autowired
    private 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;

    @Override
    public 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

执行成功

746c3e046be6872ecb8f7920228cce69b01.jpg

方法二

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值