乐趣区

关于dubbo-zookeeper:分布式电商项目九DubboZookeeper项目重构

SOA 思维

SOA 思维介绍

面向服务的架构 (SOA)是一个组件 模型 ,它将应用程序的不同 性能单元(称为服务)进行 拆分 ,并通过这些服务之间定义良好的 接口和协定 分割起来。接口是采纳中立的形式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在各种各样的零碎中的服务能够以一种对立和通用的形式进行交互。

RPC 介绍(调用模式的统称)

RPC 介绍

RPC(Remote Procedure Call)近程过程调用,简略的了解是一个节点申请另一个节点提供的服务
本地过程调用:如果须要将本地 student 对象的 age+1,能够实现一个 addAge()办法,将 student 对象传入,对年龄进行更新之后返回即可,本地办法调用的函数体通过函数指针来指定。
近程过程调用:addAge 办法在其余的服务器中, 如果须要调用则必须通过近程的形式告诉其余服务器帮我实现业务调用.

总结: 利用第三方的服务器, 帮我实现业务调用的过程.
了解: 分布式环境中 业务调用简直都是 RPC 的.

微服务

什么是微服务

阐明:

  1. 为了升高代码的耦合性, 将我的项目进行了拆分.依照功能模块拆分为若干个我的项目. 该我的项目称之为服务.(分布式思维).
  2. 如果采纳微服务的构造, 要求服务器如果呈现了故障应该 实现自动化的故障的迁徙(高可用 HA)

现有服务剖析

阐明: 因为 nginx 负载平衡 / 反向代理都须要人为的配置, 并且呈现了问题不能及时的实现故障的迁徙, 所以须要降级为微服务的架构的设计.

微服务架构设计


实现步骤:

  1. 服务提供者启动时,. 将本人的信息注册到注册核心中.
  2. 注册核心承受到了用户的申请之后, 更新服务列表信息.
  3. 当消费者启动时, 首先会链接注册核心, 获取服务列表数据.
  4. 注册核心将本人的服务列表信息同步给客户端(消费者)
  5. 消费者接管到服务列表数据之后, 将信息保留到本人的本地. 不便下次调用
  6. 当消费者接管到用户的申请时, 依据本人服务列表的信息进行负载平衡的操作, 抉择其中一个服务的提供者, 依据 IP:PORT 进行 RPC 调用.
  7. 当服务提供者宕机时, 注册核心会有心跳检测机制, 如果查看宕机, 则更新本地的服务列表数据, 并且全网播送告诉所有的消费者更新服务列表.

Zookeeper

Zookeeper 介绍

ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 Hbase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的性能包含:配置保护、域名服务、分布式同步、组服务等。
ZooKeeper 的指标就是封装好简单易出错的要害服务,将简略易用的接口和性能高效、性能稳固的零碎提供给用户。
ZooKeeper 蕴含一个简略的原语集, 提供 Java 和 C 的接口。
ZooKeeper 代码版本中,提供了分布式独享锁、选举、队列的接口,代码在 zookeeper-3.4.3srcrecipes。其中散布锁和队列有 Java 和 C 两个版本,选举只有 Java 版本。

总结:Zookeeper 负责服务的协调调度. 当客户端发动申请时, 返回正确的服务器地址.

Zookeeper 下载

网址: http://zookeeper.apache.org/releases.html.

ZK 装置

先装置 JDK(jdk1.8)

1. 上传安装文件


解压目录:

tar -xvf zookeeper-3.4.8.tar.gz

2. 批改配置文件

在 zk 根目录下创立文件夹 data/log

mkdir data log

3. 跳入 conf 目录中批改配置文件

复制配置文件并且批改名称

cp zoo_sample.cfg zoo.cfg

4. 启动 zk

跳转到 bin 目录中 zk 启动敞开命令如下.

sh zkServer.sh start 或者 ./zkServer.sh start
sh zkServer.sh stop
sh zkServer.sh status

Zookeeper 集群装置

1. 筹备文件夹

在 zookeeper 根目录中创立新的文件夹 zkCluster.

创立 zk1/zk2/zk3 文件夹.

在每个文件夹里创立 data/log 文件夹.

mkdir {zk1,zk2,zk3}/{data,log}

2. 在每个文件夹里创立 data/log 文件夹.

mkdir {zk1,zk2,zk3}/{data,log}

3. 别离在 zk1/zk2/zk3 中的 data 文件夹中创立新的文件 myid. 其中的内容顺次为 1 /2/3, 与 zk 节点号对应.

4. 在 config 目录中将 zoo_sample.cfg 复制为 zoo1.cfg 之后批改配置文件.

将 zoo_sample.cfg 复制为 zoo1.cfg 之后批改配置文件.

5. 批改 zoo1.cfg

配置实现后将 zoo1.cfg 复制 2 份. 之后须要批改对应的文件夹目录. 和不同的端口即可.

6.ZK 集群测试

通过上面的命令启动 zk 集群.

sh zkServer.sh start zoo1.cfg
sh zkServer.sh stop  zoo1.cfg
sh zkServer.sh status zoo1.cfg

查看主从关系, 从机状况阐明

查看主从关系, 主机状况阐明

对于 zookeeper 集群阐明

Zookeeper 集群中 leader 负责监控集群状态,follower 次要负责客户端链接获取服务列表信息. 同时参加投票.

为什么集群个别都是奇数个?

公式: 存活的节点 > N/2

公式解读:(zookeeper 集群服务器数 – 宕机的服务器数量)> 集群数 /2
集群的概念即实现高可用,如果宕机一台就生效也不能称为集群,所以判断集群的根据是当有一台服务器宕机 公式仍然成立即为集群
容许最大宕机数:放弃公式成立的集群数与宕机数,否则集群解体

常识: 最小的集群的单位 3 台.
例子:
1 个节点是否搭建集群? 1-1 > 1/2 假的 1 个节点不能搭建集群
2 个节点是否搭建集群? 2-1 > 2/2 假的 2 个节点不能搭建集群
3 个节点是否搭建集群? 3-1 > 3/2 真的 3 个节点能搭建集群
4 个节点是否搭建集群? 4-1 > 4/2 真的 4 个节点能搭建集群

3 个节点最多容许宕机 1 台, 否则集群解体.
4 个节点最多容许宕机 1 台, 否则集群解体.

搭建奇数台和偶数台其实都能够, 然而从容灾性的角度思考, 发现奇数和偶数的成果雷同,. 所以搭建奇数台.

ZK 集群选举规定

阐明: zk 集群选举采纳最大值 (myid) 优先的算法实现 , 如果集群中没有主机, 则开始选举(超半数即可), 如果有主机, 则选举完结.
考题: 1 2 3 4 5 6 7 顺次启动时
问题 1: 谁当主机? 4 当主机
问题 2: 谁永远不能入选主机? 1,2,3

Dubbo 框架

Dubbo 介绍

Apache Dubbo |ˈdʌbəʊ| 提供了六大外围能力:面向接口代理的高性能 RPC 调用,智能容错和负载平衡,服务主动注册和发现,高度可扩大能力,运行期流量调度,可视化的服务治理与运维。

调用原理图:

Dubbo 入门案例

批改配置文件内容

1). 批改 SpringBoot 的版本

2). 批改模块名称 改为 dubbo-jt-demo-interface

导入 maven 我的项目


批改俩个生产者的驱动为 com.mysql.cj.jdbc.Driver

测试成果

创立接口

1). 定义接口

2). 定义接口代码

创立服务生产者

定义生产者的实现类

package com.jt.dubbo.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;

import com.alibaba.dubbo.config.annotation.Service;
import com.jt.dubbo.mapper.UserMapper;
import com.jt.dubbo.pojo.User;
@Service(timeout=3000)    // 3 秒超时 外部实现了 rpc
//@org.springframework.stereotype.Service// 将对象交给 spring 容器治理
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Override
    public List<User> findAll() {System.out.println("我是第一个服务的提供者");
        return userMapper.selectList(null);
    }
    
    @Override
    public void saveUser(User user) {userMapper.insert(user);
    }
}

提供者配置文件

server:
  port: 9000   #定义端口

spring:
  datasource:
    #引入 druid 数据源
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: root

#对于 Dubbo 配置   
dubbo:
  scan:
    basePackages: com.jt    #指定 dubbo 的包门路
  application:              #利用名称
    name: provider-user     #一个接口对应一个服务名称
  registry:
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
  protocol:  #指定协定
    name: dubbo  #应用 dubbo 协定(tcp-ip)  web-controller 间接调用 sso-Service
    port: 20880  #每一个服务都有本人特定的端口 不能反复.

      
mybatis-plus:
  type-aliases-package: com.jt.dubbo.pojo       #配置别名包门路
  mapper-locations: classpath:/mybatis/mappers/*.xml  #增加 mapper 映射文件
  configuration:
    map-underscore-to-camel-case: true                #开启驼峰映射规定

服务消费者

编辑 Controller

package com.jt.dubbo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.dubbo.config.annotation.Reference;
import com.jt.dubbo.pojo.User;
import com.jt.dubbo.service.UserService;

@RestController
public class UserController {
    
    // 利用 dubbo 的形式为接口创立代理对象 利用 rpc 调用
    //@Reference(loadbalance="random") // 负载平衡的随机算法(如果不写参数,默认也是 random)//@Reference(loadbalance="roundrobin") // 轮循算法
    //@Reference(loadbalance="consistenthash") // 一致性 hash 的算法(参数全小写)// 调用近程服务就像调用本人的服务一样的简略!!!
    @Reference(loadbalance="leastactive") // 筛选压力最小的服务器
    private UserService userService; 
    
    /**
     * Dubbo 框架调用特点: 近程 RPC 调用就像调用本人本地服务一样简略
     * @return
     */
    @RequestMapping("/findAll")
    public List<User> findAll(){
        
        // 近程调用时传递的对象数据必须序列化.
        return userService.findAll();}
    
    @RequestMapping("/saveUser/{name}/{age}/{sex}")
    public String saveUser(User user) {userService.saveUser(user);
        return "用户入库胜利!!!";
    }
}

编辑 YML 配置文件

server:
  port: 9001
dubbo:
  scan:
    basePackages: com.jt
  application:
    name: consumer-user   #定义消费者名称
  registry:               #注册核心地址
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.12

消费者测试

Dubbo 高可用测试

测试需要

1). 测试当服务器宕机, 用户拜访是否受影响. 用户拜访不受影响. zk 心跳检测机制
2). 测试当 zk 集群宕机, 用户拜访是否受影响. 不受影响 消费者在本地有服务列表数据, 本人保护.
3). 测试是否有负载平衡的成果 用户拜访有负载平衡的成果

Dubbo 负载平衡

负载平衡形式

1. 服务端负载平衡 (集中式负载平衡)
阐明: 用户拜访服务器时不分明实在的服务器到底是谁, 由负载平衡服务器动静动静治理.
典型代表: NGINX
个别 nginx 服务器做反向代理应用, 负载平衡只是提供的性能.

2. 客户端负载平衡
阐明: 采纳微服务架构时, 当消费者拜访服务提供者时, 因为框架外部曾经实现了负载平衡的策略, 所以消费者拜访提供者时曾经实现了负载平衡的机制. 所以将所有的压力均衡到了各个消费者中.

负载平衡 - 随机算法

默认条件下就是随机算法

负载平衡 - 轮询算法


负载平衡 - 一致性 hash


负载平衡 - 起码拜访


重构京淘我的项目

导入 jar 包

 <!-- 引入 dubbo 配置 -->
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>

重构接口我的项目

阐明: 在 jt-common 中增加接口文件.

重构 JT-SSO(生产者)

编辑 Service 实现类

编辑 YML 配置文件

server:
  port: 8093
  servlet:
    context-path: /    #在根目录中公布  缺省值.
spring:
  datasource:
    #引入 druid 数据源
    #type: com.alibaba.druid.pool.DruidDataSource
    #driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: root

  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
#mybatis-plush 配置
mybatis-plus:
  type-aliases-package: com.jt.pojo
  mapper-locations: classpath:/mybatis/mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true

logging:
  level: 
    com.jt.mapper: debug

#对于 Dubbo 配置
dubbo:
  scan:
    basePackages: com.jt    #指定 dubbo 的包门路
  application:              #利用名称
    name: provider-user     #一个接口对应一个服务名称
  registry:
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
  protocol:  #指定协定
    name: dubbo  #应用 dubbo 协定(tcp-ip)  web-controller 间接调用 sso-Service
    port: 20880  #每一个服务都有本人特定的端口 不能反复. 

重构服务消费者

编辑 UserController

编辑 YML 配置文件

server:
  port: 8092    
spring:     #定义 springmvc 视图解析器
  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
      
#配置 dubbo 消费者
dubbo:
  scan:
    basePackages: com.jt
  application:
    name: consumer-user   #定义消费者名称
  registry:               #注册核心地址
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183 

用户模块实现

用户注册实现

页面剖析

1). 页面 url 地址

2. 页面提交参数

3. 页面 JS 剖析

编辑 UserController

 /**
     * 实现用户的注册操作
     * url 地址: http://www.jt.com/user/doRegister
     *          Request Method: POST
     * 申请参数:
     *          password: admin123
     *          username: admin123123123
     *          phone: 13111112225
     * 返回值类型:
     *          SysResult 对象
     */
    @RequestMapping("/doRegister")
    @ResponseBody
    public SysResult saveUser(User user){

        // 利用 dubbo 进行 RPC 调用
        dubboUserService.saveUser(user);
        return SysResult.success();}

编辑 UserService

package com.jt.service;

import com.alibaba.dubbo.config.annotation.Service;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;

@Service(timeout = 3000)
public class DubboUserServiceImpl implements DubboUserService{

    @Autowired
    private UserMapper userMapper;

    @Override
    public void saveUser(User user) {
        // 明码采纳 md5 形式进行加密解决
        String password = user.getPassword();
        String md5Pass = DigestUtils.md5DigestAsHex(password.getBytes());
        user.setEmail(user.getPhone()).setPassword(md5Pass);
        userMapper.insert(user);
    }
}

页面成果展示

用户登录

单点登录业务实现

单点登录 (SingleSignOn,SSO),就是通过用户的一次性甄别登录。当用户在身份认证服务器上登录一次当前,即可取得拜访单点登录零碎中其余关联系统和应用软件的权限,同时这种实现是不须要管理员对用户的登录状态或其余信息进行批改的,这意味着在多个利用零碎中, 用户只需一次登录就能够拜访所有相互信任的利用零碎。这种形式缩小了由登录产生的工夫耗费,辅助了用户治理,是目前比拟风行的.


实现步骤:
1. 用户输出用户名和明码之后点击登录按钮开始进行登录操作.
2.JT-WEB 向 JT-SSO 发送申请, 实现数据校验
3. 当 JT-SSO 获取数据信息之后, 实现用户的校验, 如果校验通过则将用户信息转化为 json. 并且动静生成 UUID. 将数据保留到 redis 中. 并且返回值 uuid.
如果校验不存在时, 间接返回 ” 不存在 ” 即可.
4.JT-SSO 将数据返回给 JT-WEB 服务器.
5. 如果登录胜利, 则将用户 UUID 保留到客户端的 cookie 中.

页面 URL 剖析

1).url 申请

2).url 参数

3). 页面 JS 剖析

编辑 UserController

 /**
     * 业务: 实现用户登录操作
     * url 地址: http://www.jt.com/user/doLogin?r=0.35842191622936337
     * 参数:
     *          username: admin123
     *          password: admin123456
     * 返回值:   SysResult 对象
     *
     * 业务具体实现:
     *      1. 校验用户名和明码是否正确
     *      2. 判断返回值后果是否为 null 用户名和明码有误 返回 201 状态码
     *      3. 如果返回值后果不为 null  uuid 保留到 cookie 中即可.
     *
     *  Cookie 常识介绍:
     *      1.cookie.setPath("/")  根目录无效
     *      url1:  www.jt.com/addUser
     *      url2:  www.jt.com/user/addUser
     *
     *      2. cookie.setDomain("域名地址");  cookie 在哪个域名中共享
     *      例子 1:   cookie.setDomain("www.jt.com");
     *               只有在 www.jt.com 的域名中无效
     *
     *               cookie.setDomain("jt.com");
     *               只有在 jt.com 结尾的域名中无效
     *
     */
    @RequestMapping("/doLogin")
    @ResponseBody
    public SysResult doLogin(User user, HttpServletResponse response){String uuid = dubboUserService.doLogin(user);
        if(StringUtils.isEmpty(uuid)){return SysResult.fail();
        }
        // 将 uuid 保留到 Cookie 中
        Cookie cookie = new Cookie("JT_TICKET",uuid);
        cookie.setMaxAge(30*24*60*60);  // 让 cookie 30 天无效
        cookie.setPath("/");            //cookie 在哪个 url 门路失效
        cookie.setDomain("jt.com");     // 设定 cookie 共享
        response.addCookie(cookie);

        return SysResult.success();}

编辑 UserService

 /**
     * 1. 依据用户名和明码查问后端服务器数据
     * 2. 将明码加密解决
     * @param user
     * @return
     */
    @Override
    public String doLogin(User user) {String md5Pass = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
        user.setPassword(md5Pass);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);//u/ p 不能
        // 依据对象中不为空的属性, 充当 where 条件.
        User userDB = userMapper.selectOne(queryWrapper);
        if(userDB == null){
            // 依据用户名和明码谬误
            return null;
        }
        // 开始进行单点登录业务操作
        String uuid = UUID.randomUUID()
                          .toString()
                          .replace("-", "");
        userDB.setPassword("123456 你信不?");   // 去除无效信息.
        String userJSON = ObjectMapperUtil.toJSON(userDB);
        jedisCluster.setex(uuid, 30*24*60*60, userJSON);

        return uuid;
    }

页面成果展示

用户信息回显

用户信息回显业务需要

思路: 用户通过 TICKET 信息, 利用 JSONP 的形式动静获取近程的服务器数据信息. 之后将数据返回之后 回显数据.

用户 URL 申请

页面 JS 剖析

编辑 JT-SSO 的 UserController

 /**
     * 业务阐明:
     *   通过跨域申请形式, 获取用户的 JSON 数据.
     *   1.url 地址:  http://sso.jt.com/user/query/efd321aec0ca4cd6a319b49bd0bed2db?callback=jsonp1605775149414&_=1605775149460
     *   2. 申请参数:  ticket 信息
     *   3. 返回值:   SysResult 对象 (userJSON)
     *   需要: 通过 ticket 信息获取 user JSON 串
     */
    @RequestMapping("/query/{ticket}")
    public JSONPObject findUserByTicket(@PathVariable String ticket,String callback){String userJSON = jedisCluster.get(ticket);
        if(StringUtils.isEmpty(userJSON)){return new JSONPObject(callback, SysResult.fail());
        }else{return new JSONPObject(callback, SysResult.success(userJSON));
        }
    }

页面成果展示

用户登出操作

退出业务逻辑

当用户点击退出操作时, 应该 重定向 到零碎首页. 同时删除 redis 信息 /Cookie 信息.

编辑 UserController

 /**
     * 实现用户退出操作
     * url 地址:http://www.jt.com/user/logout.html
     * 参数:   没有参数
     * 返回值:  String 重定向到零碎首页
     * 业务:
     *      1. 删除 redis   K-V   获取 ticket 信息
     *      2. 删除 cookie
     */
    @RequestMapping("/logout")
    public String logout(HttpServletRequest request,HttpServletResponse response){
        //1. 获取 Cookie 中的 JT_TICKET 值
        Cookie[] cookies = request.getCookies();
        if(cookies != null && cookies.length>0){for (Cookie cookie : cookies){if(cookie.getName().equals("JT_TICKET")){String ticket = cookie.getValue();
                    //redis 删除 ticket 信息
                    jedisCluster.del(ticket);
                    cookie.setMaxAge(0);    // 0 示意立刻删除
                    // 规定 cookie 如果须要操作, 必须严格定义
                    cookie.setPath("/");
                    cookie.setDomain("jt.com");
                    response.addCookie(cookie);
                }
            }
        }

        return "redirect:/";
    }

商品信息展示

业务需要阐明

当用户点击商品时应该跳转到商品的展示页面, 在页面中应该展示 2 局部数据.item 数据 /itemDesc 数据. item.jsp 页面
数据取值形式:
1. 获取 item 信息 ${item.title}
2. 获取 ItemDesc 数据 ${itemDesc.itemDesc}

重构 JT-MANAGE

编辑 ItemService

编辑 YML 配置

server:
  port: 8091
  servlet:
    context-path: /    #在根目录中公布  缺省值.
spring:
  datasource:
    #引入 druid 数据源
    #type: com.alibaba.druid.pool.DruidDataSource
    #driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: root

  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
#mybatis-plush 配置
mybatis-plus:
  type-aliases-package: com.jt.pojo
  mapper-locations: classpath:/mybatis/mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true

logging:
  level: 
    com.jt.mapper: debug

#配置 manage Dubbo 服务
dubbo:
  scan:
    basePackages: com.jt    #指定 dubbo 的包门路
  application:              #利用名称
    name: provider-item     #一个接口对应一个服务名称
  registry:
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
  protocol:  #指定协定
    name: dubbo  #应用 dubbo 协定(tcp-ip)  web-controller 间接调用 sso-Service
    port: 20881  #每一个服务都有本人特定的端口 不能反复.

实现页面跳转

页面 URL 剖析

编辑 ItemController

package com.jt.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.jt.service.DubboItemService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.xml.ws.RequestWrapper;

@Controller
public class ItemController {@Reference(check = false)
    private DubboItemService itemService;

    /**
     * 实现商品的展示
     * url:     http://www.jt.com/items/562379.html
     * 参数:     562379 商品 ID 号
     * 返回值:   item.jsp
     * 页面取值:    item 对象 /itemDesc 对象
     *          {item.id/title}
     */
    @RequestMapping("/items/{itemId}")
    public String findItemById(@PathVariable Long itemId, Model model){Item item = itemService.findItemById(itemId);
        ItemDesc itemDesc = itemService.findItemDescById(itemId);
        model.addAttribute("item",item);
        model.addAttribute("itemDesc",itemDesc);
        return "item";
    }

}

编辑 ItemService

package com.jt.web.service;

import com.alibaba.dubbo.config.annotation.Service;
import com.jt.mapper.ItemDescMapper;
import com.jt.mapper.ItemMapper;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.jt.service.DubboItemService;
import org.springframework.beans.factory.annotation.Autowired;

@Service(timeout = 3000)
public class DubboItemServiceImpl implements DubboItemService {

    @Autowired
    private ItemMapper itemMapper;
    @Autowired
    private ItemDescMapper itemDescMapper;

    @Override
    public Item findItemById(Long itemId) {return itemMapper.selectById(itemId);
    }

    @Override
    public ItemDesc findItemDescById(Long itemId) {return itemDescMapper.selectById(itemId);
    }
}

页面成果展示

退出移动版