SpringCloud微服务系列笔记(1)-SpringBoot简单入门

来源:互联网 发布:storm1.0java源码 编辑:程序博客网 时间:2024/05/23 23:39

一、Spring Boot简介

引用官方的一段介绍

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”. We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applicationsneed very little Spring configuration.”

说白了,就是一个可以用很简单的方式和很少量的配置创建一个生产级别应用的东西,相信各位都用过spring系列框架,比如Spring,SpringMVC等,在搭建一个常规的WEB应用时,一方面得考虑maven中各个组件的版本,另外一方面又需要做大量的配置,比如配置各种Bean等,但很多时候这些配置都是很冗余、很模板化的东西,基于这种情况spring boot横空出世,它的理念是“约定优于配置”,所谓约定优于配置,就是很多模板配置它都帮你配好了,就像我们提前约定好了,你无需再配置,在没有特殊需求的情况下,用它的自动配置,要优于你自己去配置。在下面我就不赘述Spring Boot,简单讲解一下使用。(PS:在学习SpringBoot之前,需要一定的Spring基础,因为SpringBoot本质上还是Spring,若不先学习Spring及SpringMVC,后期遇到问题会不知所措)。


二、Spring Boot与Spring Cloud的关系

Spring Boot的特点是配置简单,开发迅速,从本文的第三章中也可以体现出来,搭建一个Web服务仅需数行代码,甚至一行配置都不需要,这不就正好符合微服务架构中的“微”吗?微服务架构的核心思想就是将一整个大的系统拆分为多个微小的服务,注意,这里多个微小的服务并不是简单的拆分为多个项目,然后通过引入各自的Jar包来集成一个大系统,而是拆分为多个可各自进行迭代、部署的服务,从而实现最大程度的解耦。在这样的架构下,一个微服务的粒度势必不会太粗,可能它仅仅只是实现对某个表的增删查改,但如果还要做大量的配置,不就会增加开发难度?假如一个系统由成百上千个微服务组成,难道服务都要去创建和维护一套相似的配置?当然,使用Spring Boot的好处还有许多,这里就不一一阐述,大家可以在后期的使用中慢慢体会。

另外,Spring Cloud的各个组件并不是Spring全部从头实现出来的,而是将多家公司的框架组件,比如Netflix的Eureka框架等,封装成Spring Boot应用,并通过自动配置简化了手动配置,对于开发者来说,基本上Spring Boot的配置套路都可以套用到Spring Cloud的各个组件,从而无需花费时间学习各个框架的配置方法。


三、Spring Boot之HelloWorld

3.1 项目说明

这个是最简单的入门demo项目,零配置,数行代码即可搭建一个web项目,并且该项目内嵌Tomcat,无需部署即可像普通java应用一样直接运行。

3.2 创建项目

使用IDE的“Spring Starter Project”功能即可快速进行创建。点击菜单的New,选择“Spring Starter Project”,再点击下一步,如下图
Spring Starter Project
点击Next,选择需要添加的组件,在这里我们只是简单的演示一个web应用,所以选择“Web”即可,如下图:
添加组件
点击Finsh,稍等片刻,可以看到已经创建好一个应用,并生成了一个Main类,如下图:
Main类

3.3 编写代码

我们简单编写一个Hello控制器,功能很简单,访问该控制器时返回一个map对象序列化后的JSON字符串。代码如下:

package com.czh.demo;import java.util.HashMap;import java.util.Map;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;//RestController注解表明该控制器是一个Rest风格控制器,方法返回的对象会序列化成JSON@RestControllerpublic class HelloController {    //GetMapping注解表明该方法只处理来自路径为"/hello"的GET请求    @GetMapping("/hello")    public Map helloWorld() {        Map hello = new HashMap();        hello.put("SpringBoot", "HelloWorld");        return hello;    }}

3.4 运行项目

由于SpringBoot默认内嵌了一个Tomcat容器,所以只要右键Run Java Application运行main方法即可。
运行项目
通过控制台我们可以看到,此时内嵌的Tomcat已经启动并监听了8080端口,我们在浏览器输入此地址试试。
web界面
由此,我们最基本的HelloWorld运行成功。


四、Spring Boot之增删查改

4.1 需求分析

目前的需求是需要一个简单的通讯录登记系统进行信息登记,登记的信息包括姓名、出生日期、电话、微信号。目前需要新增、修改、删除和查询联系,其中查询联系人是通过姓名进行查询,登记联系人名字不能重复。另外这个项目需要开发速度快、部署简单、性能强大。

4.2 技术选型

下面来设计一个稍复杂的Spring Boot项目-通讯录项目,存储的信息包括姓名、出生日期、联系电话、微信号。这个项目技术选型如下:

  • 使用Spring Data JPA作为持久层框架
  • 使用Tomcat Jdbc作为数据库连接池
  • 使用Mysql作为数据库
  • 使用Slf4j、Logback作为日志记录组件
  • 使用Spring Aop记录全局请求日志
  • 使用SimpleCache缓存提升性能
  • 使用Ajax与后台交互,实现简单的增删查改

4.3 数据库设计

这个表结构很简单,按需要存储的字段设计即可。

CREATE TABLE `contacts` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `name` varchar(255) NOT NULL COMMENT '姓名',  `birthday` date NOT NULL COMMENT '生日',  `phone` varchar(255) NOT NULL COMMENT '联系电话',  `wechat` varchar(255) NOT NULL COMMENT '微信号',  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

4.4 创建项目

由于我们用到JPA及MySQL,所以新建项目时需要引入相应的组件,而Spring Boot由于内嵌了Tomcat,故无需再引入Tomcat jdbc。如下图:
创建项目

4.5 编写代码

下面我们进入编码阶段,整个架构就是常规的MVC。

4.5.1 编写实体层

创建一个与通讯录表(contacts)对应的实体类(Contact),需要注意的是默认数据库表名是实体名一致,若不一致,需要加上@Table注解,还有其它注解具体看注释。

package com.czh.contacts.entity;import java.util.Date;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.Table;import org.hibernate.validator.constraints.NotBlank;import org.springframework.format.annotation.DateTimeFormat;import com.fasterxml.jackson.annotation.JsonFormat;/** *  * @author caizh * * 通讯录表对应的实体类 */@Entity@Table(name="contacts")public class Contact {    /**     * 主键     */    //表明该字段对应数据库的主键字段    @Id    //表明该字段为自增长字段    @GeneratedValue(strategy=GenerationType.IDENTITY)    private Long id;    /**     * 姓名      */    //表明该属性对应数据库表中的列名    @Column(name = "name")    @NotBlank(message="姓名不能为空")    private String name;    /**     * 生日      */    @Column(name = "birthday")    //此注解是规定了当入参时间字符串的格式    @DateTimeFormat(pattern = "yyyy-MM-dd")    //此注解是规定了返回的时间字符串的格式,注意必须要加这个时区,否则会出现序列化后的日期少一天问题    @JsonFormat(pattern = "yyyy-MM-dd",timezone="GMT+8")    private Date birthday;    /**     * 联系电话     */    @NotBlank(message="联系电话不能为空")    @Column(name = "phone")    private String phone;    /**     * 微信号      */    @NotBlank(message="微信号不能为空")    @Column(name = "wechat")    private String wechat;    public Long getId() {        return id;    }    public void setId(Long id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Date getBirthday() {        return birthday;    }    public void setBirthday(Date birthday) {        this.birthday = birthday;    }    public String getPhone() {        return phone;    }    public void setPhone(String phone) {        this.phone = phone;    }    public String getWechat() {        return wechat;    }    public void setWechat(String wechat) {        this.wechat = wechat;    }    @Override    public String toString() {        return "Contact [id=" + id + ", name=" + name + ", birthday=" + birthday + ", phone=" + phone + ", wechat="                + wechat + "]";    }}

还要封装一个统一的返回体(Response),方便前端调用处理,这个返回体比较简单,主要包括返回码、返回消息、返回体三个字段,如下:

package com.czh.contacts.entity;/** * 定义通用返回体 * @author caizh * */public class Response {    /**     * 成功代码     */    public static int SUCCESS = 1;    /**     * 失败代码     */    public static int FAILED = -1;    /**     * 空数据代码     */    public static int EMPTY = 0;    /**     * 返回码     */    private int rtncode;    /**     * 返回消息     */    private String message;    /**     * 返回体     */    private Object body;    public Response() {    }    public Response(int rtncode, String message, Object body) {        super();        this.rtncode = rtncode;        this.message = message;        this.body = body;    }    public Response(int rtncode, Object body) {        super();        this.rtncode = rtncode;        this.body = body;    }    public int getRtncode() {        return rtncode;    }    public void setRtncode(int rtncode) {        this.rtncode = rtncode;    }    public String getMessage() {        return message;    }    public void setMessage(String message) {        this.message = message;    }    public Object getBody() {        return body;    }    public void setBody(Object body) {        this.body = body;    }    @Override    public String toString() {        return "Response [返回码=" + rtncode + ", 返回消息=" + message + ", 返回内容=" + body + "]";    }}

4.5.2 编写持久层

由于使用的是Spring Data JPA,所以持久层只需要提供接口即可,无需提供实现,方法名相当于SQL语句,例如findByName即为根据name查找表,但怎么知道要查什么表呢?所以该接口需要继承JpaRepository这个接口,并传入表对应的实体类,即可指定该接口操作表对象。另外继承了JpaRepository接口后,也同时继承了一些基本的crud操作,例如保存实体、修改实体等。使用Spring Data JPA的一个好处是简单的语句无需手动编写,只需按规范定义好方法名即可,具体规范可上网查找,而复杂的语句可以在方法上添加注解即可实现。在这里我们创建一个通讯录表对应的持久类(ContactRepository)。

package com.czh.contacts.repository;import javax.transaction.Transactional;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;import com.czh.contacts.entity.Contact;/** * 通讯录类 * @author caizh * 此接口只要继承JpaRepository,并在泛型中传入数据库表实体类对应的类型即可直接使用最基本的增删查改,而无需再实现接口。 */@Repository//接口上需要加上事务注解,否则无法使用delete或update操作@Transactionalpublic interface ContactRepository extends JpaRepository<Contact, Long>{    /**     * 根据姓名查找记录     * @param name     * @return     */    public Contact findByName(String name);    /**     * 根据姓名删除记录     * @param name     * @return     */    public int deleteByName(String name);}

4.5.3 编写服务层

服务层就是我们最主要编写业务逻辑的地方了,同时可能也是需求变化影响最大的地方。根据面向接口编程的原则,在这里我设计了两层,一层接口层(service)与一层实现层(serviceimpl),对于外部调用一律使用接口而不使用其具体实现。
服务接口层目前仅提供一个通讯录服务接口(ContactService),里面包含三个方法,分别是根据姓名查找联系人、根据姓名删除联系人、新增或修改联系人,如下:

package com.czh.contacts.service;import com.czh.contacts.entity.Contact;/** * 通讯录服务接口 * @author caizh * */public interface ContactService {    /**     * 根据姓名查询联系人     * @param name     * @return     */    public Contact loadContact(String name);    /**     * 增加或修改联系人     * @param contact     * @return     */    public Contact saveOrUpdatedContact(Contact contact);    /**     * 删除联系人     * @param name     * @return     */    public int deleteContact(String name);}

服务实现则实现上述的接口,值得注意的是该实现需要添加@Service注解,这样才能被容器实例化,另外在查询方法中添加了@Cacheable注解,该注解作用是首次调用时,会将该方法的结果缓存起来,当下次调用就直接返回结果,而无需执行逻辑。但是总不能无论什么参数都返回同个结果,所以还需要我们定义key,在这里我定义成“LoadContact=#name”,其中#name是取出参数中的名为name的值,假如要取出对象中的值可以写#contact.name,而Cacheable注解里面value的作用是用于指定该缓存属于哪个缓存集,说白了,所谓缓存可以理解为若干个Map,Cacheable的value参数相当于定义map的名称,key则是这个map里面的key,存储的value则是方法的返回结果。在这个服务中,当首次调用查询方法并传入参数为”张三”时,框架会创建一个名称为“contact”的Map,同时将“LoadContact=张三”字符串作为key,把查询的返回结果作为value存入这个map,这样第二次调用并同样传入参数为”张三”时,会到”contact”这个map中寻找key为” LoadContact=张三”的value,找到的话就直接返回对象。
@CachePut的作用是无论调用多少次,都会真正执行方法,同时也会更新缓存,适用于新增或修改操作。
@CacheEvict的作用便是清除缓存,论调用多少次都会真正执行方法,使用于删除操作。

package com.czh.contacts.serviceimpl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;import com.czh.contacts.entity.Contact;import com.czh.contacts.repository.ContactRepository;import com.czh.contacts.service.ContactService;/** * 通讯录服务接口通用实现 * @author caizh * */@Servicepublic class ContactServiceImpl implements ContactService{    //使用此注解可以自动注入服务    @Autowired     private ContactRepository contactRepository;    @Override    @Cacheable(value = "contact",key = "'LoadContact='+#name")    public Contact loadContact(String name) {        Contact contact =  contactRepository.findByName(name);        return contact;    }    @Override    @CachePut(value = "contact",key = "'LoadContact='+#contact.name")    public Contact saveOrUpdatedContact(Contact contact) {        if(contact.getId() == null) {            //新增操作            if(contactRepository.findByName(contact.getName())!=null) {                return null;            }        }        return contactRepository.save(contact);    }    @Override    @CacheEvict(value = "contact",key = "'LoadContact='+#name")    public int deleteContact(String name) {        return contactRepository.deleteByName(name);    }}

4.5.4 编写控制层

控制层是用来接收请求、并调用服务来处理请求、最后封装结果返回的地方,这里简单用了一个Rest风格的控制器(ContactController)来处理对contact的一系列操作请求。

package com.czh.contacts.controller;import javax.validation.Valid;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.validation.BindingResult;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RestController;import com.czh.contacts.entity.Contact;import com.czh.contacts.entity.Response;import com.czh.contacts.service.ContactService;/** * 通讯录控制器(Rest风格) * @author caizh * */@RestController@RequestMapping("/contact")public class ContactController{    @Autowired    private ContactService contactService;    /**     * 根据姓名查询联系人     * @param name     * @return     */    @GetMapping("/name/{name}")    public Response searchContact(@PathVariable(name = "name",required = false) String name) {        Response response = new Response();        Contact contact = contactService.loadContact(name);        if(contact == null) {            response.setRtncode(Response.EMPTY);        }else {            response.setRtncode(Response.SUCCESS);            response.setBody(contact);        }        return response;    }    /**     * 增加或修改联系人     * @param contact     * @param bindingResult     * @return     */    @PostMapping    public Response saveOrUpdatedContact(@Valid Contact contact,BindingResult bindingResult) {        Response response = new Response();        if(bindingResult.hasErrors()) {            response.setRtncode(Response.FAILED);            response.setMessage(bindingResult.getFieldError().getDefaultMessage());        }else {            contact = contactService.saveOrUpdatedContact(contact);            if(contact == null) {                response.setRtncode(Response.EMPTY);            }else {                response.setRtncode(Response.SUCCESS);                response.setBody(contact);            }        }        return response;    }    /**     * 删除联系人     * @param name     * @return     */    @RequestMapping(method = RequestMethod.DELETE,value = "/name/{name}")    public Response delContact(@PathVariable(name = "name",required = false) String name) {        //该方法会返回删除多少记录        int result = contactService.deleteContact(name);        Response response = new Response();        if(result == 0) {            response.setRtncode(Response.EMPTY);        }else {            response.setRtncode(Response.SUCCESS);        }        return response;    }}

另外,在控制器我还定义了一个通用的异常处理器(CommonExceptionHandler),在这个处理器中会将所有的异常信息catch到,记录完相应的日志后,并其异常原因封装在返回体中返回,这样可以防止后台出现异常时连着前端也崩溃了。

package com.czh.contacts.controller;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestController;import com.czh.contacts.entity.Response;/** * 通用异常处理器 * @author caizh * */@ControllerAdvice@RestControllerpublic class CommonExceptionHandler {   private static final Logger logger = LoggerFactory.getLogger(CommonExceptionHandler.class);   /**    * 接收所有异常信息,并封装为统一的返回体    * @param e    * @return    */   @ExceptionHandler   public Response handlerException(Exception e) {       logger.error(e.getMessage(),e);       Response response = new Response();       response.setRtncode(Response.FAILED);       response.setMessage("系统出现异常,异常原因:" + e.getMessage());       return response;     }  }

4.5.5 编写切面层

切面层使用的是Spring AOP,目的是为了能够实现统一地在某个地方执行某个动作,最常用就是用来统一记录日志,比如在这里我需要记录所有控制器接收的请求,以及请求的参数、处理请求的时间、处理后返回的内容等,就可以用Spring AOP实现。

package com.czh.contacts.aop;import java.util.Arrays;import java.util.UUID;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;/** * 请求及响应日志记录 * @author caizh * */@Component//该注解定义了该类为一个切面类@Aspectpublic class RequestHandlerLogging {    private static final Logger logger = LoggerFactory.getLogger(RequestHandlerLogging.class);    /**     * 记录所有控制器日志     */    //@Pointcut注解定义了切入点,下面定义的意思是该切入点为com.czh.contacts.controller这个包下所有以Controller结尾的类中所有的方法(不限参数)    @Pointcut("execution(* com.czh.contacts.controller.*Controller.*(..))")    public void excudeController(){}    //该注解定义了该方法为环绕通知    @Around("excudeController()")    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{        //请求流水号        String  serialNumber = UUID.randomUUID().toString().replaceAll("-", "");        //传入的参数        Object[] args = proceedingJoinPoint.getArgs();        //请求开始时间        Long startTime = System.currentTimeMillis();        logger.info("控制器({})的方法({}) 接收到请求({}),请求参数:{}",proceedingJoinPoint.getTarget().getClass().getName(),                                                            proceedingJoinPoint.getSignature().getName(),                                                            serialNumber,                                                            Arrays.asList(args)                                                            );        //处理结果        Object result = null;        try {            //方法执行后的结果            result = proceedingJoinPoint.proceed();        } catch (Throwable e) {            logger.info("请求({})处理出现异常,异常原因:{}",serialNumber,e.getMessage());            throw e;        }        //计算耗时        Long processTime = System.currentTimeMillis() - startTime;        logger.info("请求({})处理完成,总耗时:{}ms 返回结果:{}",serialNumber,processTime,result.toString());        return result;    }}

4.5.6 编写配置文件

本项目的重点来了,就是配置文件,写过SSM或者SSH等的都应该清楚,假如要跑这么一个项目最少需要写多少配置或者多少JAVA CONFIG,基本上就是一个写了一大堆bean的xml和一个数据库的properties,那么我们这一个需要写什么配置呢?

spring:  datasource:    username: root    password: 123456    url: jdbc:mysql://localhost:3306/spring_cloud?useUnicode=true&characterEncoding=utf8

是的,全部只需要这三个数据库的配置就能跑起项目,甚至连DriverClassName都不用配置,至于xml配置文件是不需要的,这要得益于Spring Boot的自动配置,具体可以查阅官方文档。

4.5.7 编写前端页面

前端页面我就不贴代码了,很简单的HTML+Jquery,请求通过ajax实现,若想查阅源码可自行到我的github中下载。

4.6 运行项目

到这里,我们整个架子便搭建完毕,并且完成了一个通讯录功能。运行项目前先看一下整个项目结构:
项目结构
通过maven 的Install可以将项目打包成一个可运行的Fat Jar,如下图:
打包
下面我将它上传到我的服务器中运行,服务器是阿里云最低配置的ECS云主机,1核1G内存1M带宽,mysql也是用的附赠的最低配MYSQL,不过足够演示用了。上传后直接执行java -jar xxx.jar即可运行项目。
运行项目
运行后Web界面如下:
Web界面
输入联系人信息后点击新增,可以正常新增。
新增操作
点击查询按钮也能正常查出数据。
这里写图片描述

4.7 压力测试

在编写代码时,还记得我们在服务层添加了缓存注解吗?接下来我们来做验证,验证缓存是否生效,顺便进行一个简单的压力测试。
首先我们要禁用缓存后再进行压测,但目前项目的配置文件已经一并打包到jar包中,难道我们要手动打开jar包更改里面的配置文件才行吗?当然不是,Spring Boot也考虑到这一点,所以它设置了多个读取配置的渠道,而打包进去的配置文件优先级其实是比较低的,目前Spring Boot官方文档介绍的读取配置的渠道按照优先级排列如下:

  1. Devtools global settings properties on your home directory (~/.spring-boot-devtools.properties when devtools is active).
  2. @TestPropertySource annotations on your tests.
  3. @SpringBootTest#properties annotation attribute on your tests.
  4. Command line arguments.
  5. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  6. ServletConfig init parameters.
  7. ServletContext init parameters.
  8. JNDI attributes from java:comp/env.
  9. Java System properties (System.getProperties()).
  10. OS environment variables.
  11. A RandomValuePropertySource that only has properties in random.*.
  12. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).
  13. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).
  14. Application properties outside of your packaged jar (application.properties and YAML variants).
  15. Application properties packaged inside your jar (application.properties and YAML variants).
  16. @PropertySource annotations on your @Configuration classes.
  17. Default properties (specified using SpringApplication.setDefaultProperties)

在这里我们通过第四个配置渠道-命令行参数读取方式来禁用缓存,所以运行项目的命令改成如下:

java -jar springboot-contacts-0.0.1-SNAPSHOT.jar --spring.cache.type=none

这样启动项目后缓存就被禁用了,此时可以利用JMeter在本地电脑上向服务器发送请求进行压力测试,这里我用了50个线程不断向服务器发送查询请求,线程组配置如下:
线程组配置
在经过五分钟后,压力测试的结果如下:
未开启缓存
从图中可以看出服务器请求的吞吐量维持在8000个/分钟。
下面我们开启缓存后再进行压测,由于程序默认开启缓存,所以我们像平常一样启动程序即可,执行此命令:java -jar springboot-contacts-0.0.1-SNAPSHOT.jar
开启缓存后压测五分钟的结果如下:
开启缓存
从图中可以看出现在服务器的吞吐量已经达到22000个/分钟,大概比原来提升了两倍,同时响应速度也提升了一倍。证明此时缓存开启成功并发挥了不错的作用。
值得注意的是,本项目只是一个简单的项目,业务逻辑也很简单,查询语句以及数据库表数据(1条)也都非常简单,所以就算不开启缓存,服务器的吞吐量也不低。在这种情况下,开启缓存后性能仍能够达到两倍的提升,假如实际上表数据更大,业务逻辑更复杂,那么开启缓存后提升的效果将更明显更巨大。当然项目复杂的话,影响速度也可能在方方面面,不过良好的缓存设计总是不错的,加上SpringCache是低侵入度设计,可以让原有的逻辑无需任何改动即可实现缓存。


五、Spring Boot之补充说明

通过上面的例子大家也可以感受到Spring Boot的方便之处,但是可能会有许多问题,比如虽然它已经帮我们自动配置了很多东西,但有时候还是要做个性化配置,比如数据库连接池的最大连接数,初始连接数等,这些配置项叫什么?怎么配置?再比如里面默认帮我们选择了很多工具框架,但有时候我们也想自己选择甚至自己定义,比如数据库连接池,从上面可以看出默认使用Tomcat Jdbc连接池,假如我想使用阿里的Druid,应该怎么做?在这里告诉大家怎么配没用,毕竟每个人的需求不一样,我也不可能全部写出来,本着授人以鱼不如授人以渔原则,在这里我告诉大家,其实这些配置官方文档都有写。
https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#appendix
官方文档的这个章节里面包括了所有可以配置的项,根据大家各自的需求搜索一下,再将它配置到application.yml里面即可。另外使用Spring Boot有一个使用逻辑,当classpath里面存在什么组件就会使用什么组件,当存在相同功能组件,则按照优先级别来选择,举个例子,以数据库连接池来说,Spring Boot 1.5.8官方推荐连接池依次如下:

Production database connections can also be auto-configured by using a pooling DataSource. Spring Boot uses the following algorithm for choosing a specific implementation:
1. We prefer HikariCP for its performance and concurrency. If HkiariCP is available, we always choose it.
2. Otherwise, if the Tomcat pooling DataSource is available, we use it.
3. If neither HikariCP nor the Tomcat pooling datasource are available and if Commons DBCP2 is available, we use it.

顺序从高到低依次如下:HikariCP-Tomcat连接池-DBCP2
由于内嵌Tomcat已携带Tomcat连接池,所以默认会使用Tomcat连接池,我们只要在Pom.xml添加了HikariCP之后,虽然ClassPath下会同时存在tomcat连接池和HikariCP连接池,但由于HikariCP的默认级高,故会选择实例化它,如下图:
配置HikariCP
然后再启动项目,可以看到数据库连接池已经更换为HikariCP了。
启动项目
在官方文档中,有很多集成说明,常用的工具框架比如mongodb、rabbitmq等都有集成及配置说明,只要之前有Spring的基础,相信看懂配置是没问题的。


六、Spring Boot之最后总结

还是那句话,如果你有Spring、SpringMVC的基础,那么上手Spring Boot就很简单,更准确来讲,应该是解决问题更简单。就我目前的经验来看,越是简单的东西内部实现则越复杂,因为屏蔽了太多的细节,导致出问题排查会比较麻烦,但是只要基础扎实,这些都不是什么大问题。还有能多看看官方文档就看看官方文档,毕竟现在Spring Boot迭代地比较快,网上的一些帖子或经验有时候就过时不太适用了,举个例子,在Spring Boot 1.5.8之前的版本是将Tomcat连接池作为优先级最高的连接池,此时即使你在Pom.xml加入HikariCP依赖,运行时依然默认实例化Tomcat连接池,而不会更换成HikariCP,而网上的帖子解决该问题要么使用JAVA config手动配置HikariCP连接池的Bean,要么在Pom中排除默认的Tomcat连接池依赖,但在1.5.8版本中,HikariCP连接池的优先级已经提到最高级,所以只要在Pom中加入HikariCP的依赖即可,这一点估计大部分的帖子都未能提及。
本文中出现的所有项目我均上传到GitHub上供大家下载,地址请参见
SpringCloud微服务系列笔记(0)-笔记说明

原创粉丝点击