ramostear.comramostear.com 谭朝红的技术分享博客

格言 编程是一门技术,也是一门艺术 !

设计模式-建造者模式

设计模式-建造者模式

设计模式三十六计之建造者模式(Builder)

1. 设计意图

将复杂对象的构造与其表示分离,以便相同的构造过程可以创建不同的表示。
建造者模式类图

2. 演示案例

假设我们需要创建一个用户对象,用户对象的属性有身份证号码、姓名、年龄、性别、族别、地址。最简单的方式是定义一个包含这六个属性的构造函数来完成对象的创建,但是,你想在构建的过程中只想包含其中的一个或者几个属性的时候,问题来了,没有与之对应的构造函数存在,也不可能提前定义多构造函数来覆盖这种动态传参的构造需求。这种类型的需求场景就需要建造者模式了(Builder)。

简而言之

建造者模式的要领是允许你创建不同风格的对象,同时避免构造函数被污染。当一个对象可能有N中风格存在或者对象的创建要涉及到多个步骤的时候适用于建造者模式

维基百科

构建器模式是一种对象创建软件设计模式,旨在寻找伸缩构造器反模式的解决方案。

在开始给出具体的代码前,我们先对比一下常规的一种设计方式。就上述的案例场景,我们可能会给出如下的一个构造函数:

  public User(String id,String name,int age,int gender,String nation,String address){
    //setter...
  }

在这种设计方式下,当用户属性改变时,构造函数参数的数量可能会很快失去控制,并且很难理解构造函数的参数排列,另外,如果用户属性继续增加,构造函数的参数列表将持续增长。这被称为伸缩构造反模式。

3. 程序示例

针对上述提到的问题,最合适的解决方案时使用建造者模式,首先我们需要创建一个User类:


public class User {

  private String id;    //身份证号码
  private String name;  //姓名
  private int age = 0;  //年龄
  private int gender = 0;//0:保密,1:男性,2:女性
  private String nation;  //族别
  private String address; //地址

  private User(Builder builder){
    this.id = builder.id;
    this.name = builder.name;
    this.age = builder.age;
    this.gender = builder.gender;
    this.nation = builder.nation;
    this.address = builder.address;
  }

  //getter...
  //setter...
  //toString...
}

然后我们需要创建一个Bulder类:

public static class Builder{

  private String id;    //身份证号码
  private String name;  //姓名
  private int age = 0;  //年龄
  private int gender = 0;//0:保密,1:男性,2:女性
  private String nation = "none";  //族别
  private String address = "none"; //地址

  /**
   * 假定建造时必须提供身份证号码和姓名
  **/
  public Builder(String id,String name){
    if(id == null || name == null){
      throw new IllegalArgumentException("id and name can not be null");
    }
    this.id = id;
    this.name = name;
  }

  public Builder withAge(int age){
    this.age = age;
    return this;
  }

  public Builder withGender(int gender){
    this.gender = gender;
    return this;
  }

  public Builder withNation(String nation){
    this.nation = nation;
    return this;
  }

  public Builder withAddress(String address){
    this.address = address;
    return this;
  }

  public User build(){
    return new User(this);
  }


}

说明:这里我们使用了一个静态内部类 Builder 来实现一个建造器

最后我们可以这样来创建一个用户对象:


public class TestUserBuilder{

  public static void main(String[] args){

    User zhangSan = new User.Builder("13579","张三").withAge(22).withGender(1).build();

    User wangWu = new User.Builder("24680","王五").withAge(30).withGender(1).withNation("汉族").build();

    User liLei = new User.Builder("123456","李蕾").withAge(18).withGender(2).withNation("苗族")
                         .withAddress("贵州省黔西南布依族苗族自治州").build();

    System.out.println(zhangSan.toString());
    System.out.println(wangWu.toString());
    System.out.println(liLei.toString());
  }
}

输入结果

id:13579,name:张三,age:22,gender:1,nation:none,address:none
id:24680,name:王五,age:30,gender:1,nation:汉族,address:none
id:123456,name:李蕾,age:18,gender:2,name:苗族,address:贵州省黔西南布依族苗族自治州

4.使用场景

满足一下需求的时候推荐使用建造者模式:

  1. 创建复杂对象的算法应该独立于组成对象的各个部分以及它们的组装方式。
  2. 构造过程必须允许对构造的对象进行不同的表示

5.建造者模式应用例子

6.总结

建造者模式使得对象内部的属性可以独立变化,使用者不必知道对象内部的组成细节,每个建造器相对独立,与其他的建造器无关。建造者模式的应用让对象创建过程更加灵活和易于控制。建造者模式也有相应的弊端,它使得对象的创建过程暴露给外界,让整个对象的 “加工工艺” 变得不透明。

参考

设计模式_工厂方法

设计模式_工厂方法

设计模式三十六计之工厂方法(Factory Method)

解释:工厂方法模式是简单工厂模式的衍生,解决了简单工厂模式的破坏高内聚责任分配原则问题,完全实现了“开-闭”原则和可扩展,其核心创建一个产品对象的工厂接口,将具体的实例化操作推迟到产品子工厂中。工厂类不再负责实例化产品类,它仅仅负责具体子工厂类必须实现的接口。

1. 结构和原理

工厂方法模式是在简单工厂模式的基础上进行了抽象,它包含了一个抽象的Factory类,该类不再负责具体产品类的实例化,而只是负责定义产品生实例化的规范,具体的实例化工作交给子工厂类去完成。

2. 实现工厂方法模式

Product.java

public interface Product {
    public String start();
}

Apple.java

public class Apple implements Product{
    @Override
    public String start() {
        return "iPhone X start...";
    }
}

Huawei.java

public class Huawei implements Product{
    @Override
    public String start() {
        return "P20 Pro start...";
    }
}

Factory.java

  public abstract class Factory{
    abstract public Product createMehod();
    public Product doing(){
      Product product = createMehod();
    }
  }

AppleFactory.java

  public class AppleFactory extends Factory{
    public Product createMehod(){
      return new Apple();
    }
  }

HuaweiFactory.java

  public class HuaweiFactory extends Factory{
    public Product createMehod(){
      return new HuaWei();
    }
  }

Customer.java

public class Customer {

    public static void main(String[] args) {

        AppleFactory appleFactory = new AppleFactory();

        HuaweiFactory huaweiFactory = new HuaweiFactory();

        Apple iPhoneX = appleFactory.createMehod();
        Huawei P20Pro = huaweiFactory.createMehod();

        System.out.println(iPhoneX.start());

        System.out.println(P20Pro.start());
    }
}

控制台输出:

iPhone X start...
P20 Pro start...

3. 总结

优点

  • 符合开闭原则:新添加一种新产品时,只需要增加具体的产品类和相应的工厂子类
  • 符合单一职责原则:每个工厂子类之负责创建对应的产品,工厂类只负责定义生成产品的规范
  • 没有静态工厂方法:可实现扩展,体现了多态性。

缺点

  • 一个工厂子类只能生产一种具体的产品
  • 更换产品时,需要更换生产产品的具体工厂
  • 实现的复杂度增加,系统总类的数量成对增加

适用场景

  • 当一个类希望通过其子类来指定创建对象时
  • 当一个类不需要知道它所需的对象的类时
  • 将创建对象的任务委派给多个工厂子类中的一个来完成时
设计模式_简单工厂模式

设计模式_简单工厂模式

设计模式三十六计之简单工厂模式(Simple Factory)

解释:工厂模式的目的在于当实例化某个对象时,用户不需要知道它的内部细节,只需要知道创建对象的入口(接口)

1. 结构和原理

简单工厂模式的原理是把某些类的实例化工作交由一个独立的类来完成,这个独立的类就称为简单工厂类,这种设计模式就是简单的工厂模式。
在简单工厂模式中,让简单工厂类来决定使用哪一个具体的子类来实例化当前的对象,这样做的好处客户类(获取对象的类)和子类(需要实例化的类)之间实现解耦合,客户类不需要知道子类中的细节以及选择哪一个类来实例化;当子类发生改变时,客户类也不需要进行任何的修改,所有的繁杂工作都将由工厂类负责。

2. 实现简单的工厂模式

  public interface Product{
      public String doing();
  }
  public class Apple implements Product{
    @Override
    public String doing(){
      return "iPhone X";
    }
  }
  public class Huawei implements Product{
    @Override
    public String doing(){
      return "P20 Pro"
    }
  }
  public class OPPO implements Product{
    @Override
    public String doing(){
      return "R17 Pro"
    }
  }
  public class SimpleFactory{

    public static final int APPLE = 1;
    public static final int HUAWEI = 2;
    public static final int OPPO = 3;

    public Product createProduct(int type){
        if(type == APPLE){
          return new Apple();
        }else if(type = HUAWEI){
          return new Huawei();
        }else{
          return new OPPO();
        }
    }
  }
  public class Customer{
    public static void main(String[] args){
      SimpleFactory factory = new SimpleFactory();
      Product phone1 = factory.createdProduct(SimpleFactory.APPLE);
      Product phone2 = factory.createdProduct(SimpleFactory.HUAWEI);
      Product phone3 = factory.createdProduct(SimpleFactory.OPPO);

      System.out.println(phone1.doing());
      System.out.println(phone2.doing());
      System.out.println(phone3.doing());
    }
  }

控制台输出:

IPhone X
P20 Pro
R17 Pro

3. 总结

优点

在简单工厂模式中,工厂类(SimpleFactory)是整个模式的关键,它包含了必要的逻辑判断,通过获取外界给定的信息来决定应该创建哪一个具体类的对象。通过使用工厂模式,客户端类可以创建具体的产品子类中解放出来,客户端类只需要知道如何消费对象就可以了,而不必要去知道究竟是哪一个类实例化了以及该类的内部细节是什么。

缺点

简单工厂模式也存在它的缺陷,由于工程类(SimpleFactory)集中了所有类实例化对象的逻辑,违反了 高内聚 的责任分配原则,将全部创建逻辑集中到一个类中;它所能创建的类只能提前设置好,如果后续有新的子类加入,则需要重新修改工厂类。

适用场景

  • 工厂类负责实例化的对象较少的情况下
  • 客户端类只传入工厂类的参数,而不需要干预产品子类实例化过程的情况下
  • 由于简单工厂模式破坏了高内聚责任分配原则,建议在小范围内使用
Spring Boot 2.0 概述

Spring Boot 2.0 概述

Spring Boot 2.0 概述

Spring Boot 是由Pivotal团队基于Java开发的一个开源框架,可用于构建独立的、可部署的生产环境下的Spring应用。

1 . 什么是微服务?

在介绍Spring Boot之前,我们先聊一聊微服务的概念。Micro Service是一种允许开发人员独立开发和部署服务的一种体系架构。每个服务实例都独立部署和运行,它们具有自身的操作流程。简单来说,微服务就是将传统的庞大的业务应用程序按照某种业务规则(轻量级模型)进行拆分,拆分后,每个服务都有自己的服务上线文边界,以达到服务区域内的自治,同时又可以通过消息总线与其他服务模块进行通信,协同完成某一个业务操作流程。

1.2. 微服务的好处

微服务为开发人员带来了以下的一些优势:

  • 易于部署
  • 简单的可扩展性
  • 与容器兼容
  • 最低限度的硬件配置
  • 开发时间短

2. 什么是Spring Boot?

Spring Boot 为Java开发人员提供了一个优秀的统一平台,开发人员可以利用Spring Boot 开发出一个生产级别的可独立运行的Spring应用程序。通过Spring Boot,开发人员可以使用最少的配置,完成复杂的Spring应用程序设置。

2.1. Spring Boot 的优势

使用Spring Boot进行开发,将具备如下的一些优势:

  • 使得Spring应用程序易于理解和开发
  • 提高开发效率
  • 缩短了软件交付的时间

2.2. Spring Boot 的设计目标

Spring Boot的出现,其追求的目标如下:

  • 弱化使用XML配置文件的方式对Spring应用程序进行设置
  • 以更简单易懂的方式开发生产级别的Spring应用程序
  • 以更短的时间交付可独立运行的Spring应用程序
  • 降低Spring 框架的学习门槛

3. 为什么选择Spring Boot?

要不要选择一门技术入手,需要分析它的用户群体数量、性能指标、普适性和学习成本。就Spring Boot本身而言,其自身具备如下的几个优点:

  • 提供了一种更为优雅和易懂的方式来配置Java Bean、XML和数据库事务
  • 提供了强大的批处理和REST Endpoint管理能力
  • 提供了强大的自动装配能力,减少人工干预的次数
  • 采用注解的方式开发应用程序
  • 极大的简化了依赖管理工作
  • 提供了嵌入式的Servlet容器,如Tomcat 、Jetty等

4. Spring Boot的工作方式

Spring Boot 通过 @EnableAutoConfiguration 注解将存在于项目类路径上的依赖项自动装配到应用程序中,无需其他的人工干预。使用@SpringBootApplication 注解标注一个包含main方法的类,此类就成为Spring Boot应用程序的主入口。@ComponentScan 注解将会触发Spring Boot自动扫描项目中包含的所有组件。

5 . Spring Boot Starter

对于一个大型的项目而言、最让开发人员头疼的事情是如何管理整个项目中的依赖关系和与之对应的版本问题。Spring Boot提供了一组特定的依赖项来帮助开发人员解决此问题。例如,如果要在Spring Boot项目中使用JPA来访问数据库,则只需要在项目中引入spring-boot-starter-data-jpa 依赖项即可。

几乎所有的Spring Boot Starter都遵循相同的命名模式:“spring-boot-starter-*” ,其中的“*”表示一种应用程序的类型名称

下面我们列举几个常用的Spring Boot Starter加以说明:

  • spring-boot-starter-web : 用于依赖Spring web
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • spring-boot-starter-thymeleaf : 用于依赖Thymeleaf模板引擎

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
  • spring-boot-starter-test : 用于依赖单元测试

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    

6 . 自动装配

Spring Boot Auto Configuration会自动将添加到项目中的依赖JAR包完成相应的配置。例如,如果你在pom.xml中引入了MySQL的依赖包,在不添加任何配置的情况下,Spring Boot会自动配置内置数据库。如果需要开启自动装配功能,需要在项目主类上添加@EnableAutoConfiguration注解或者@SpringBootApplication注解:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

@EnableAutoConfiguration
public class Application{
    public static void main(String[] args){
        SpringApplication.run(Application.class);
    }
}

7. Spring Boot 主应用程序

一个Spring Boot的主应用程序需包含两个部分:

  • @SpringBootApplication 注解:用于标注一个类为Spring Boot应用的启动类
  • main 方法:Spring Boot应用程序的主入口

@SpringBootApplication是一个组合注解,它包含了自动装配、组件扫描和Spring Boot配置。我们也可以单独使用@EnableAutoConfiguration@ComponentScan@SpringBootConfiguration 注解来标注主类。

使用@SpringBootApplication进行标注:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application{
    public static void main(Stirng[] args){
        SpringApplication.run(Application.class);
    }   
}

使用@EnableAutoConfiguration@ComponentScan@SpringBootConfiguration 进行标注:

@EnableAutoConfiguration
@ComponentScan
@SpringBootConfiguration
public class Application{
    public static void main(String[] args){
        SpringApplication.run(Application.class);
    }
}

8. 组件扫描

Spring Boot应用程序在程序初始化阶段完成组件扫描的工作。使用@ComponentScan对主类进行标注,将开组件扫描功能:

@ComponentScan
public class Application{
    public static void main(String[] args){
        SpringApplication.run(Application.class);
    }
}
设计模式-原型模式(Prototype)

设计模式-原型模式(Prototype)

设计模式三十六计之原型模式(Prototype)

1. 设计思想

将一个对象作为指定的原型实例,并通过克隆此原型来创建新的对象。
原型模式类图

2. 演示案例

关于克隆,大家都知道克隆羊‘克隆羊多利’ ,本章节我们就以‘克隆羊多利’ 来演示原型模式

简而言之

原型模式就是克隆现有的对象来创建新的对象

百度百科:

原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。

原型模式允许你创建现有对象的副本并更具实际需要进行修改,而不是从头开始创建新的对象并进行设置。

3. 代码示例

在Java中,一个对象只需要实现Cloneable接口,覆写clone方法,此clone方法名可以自定义,就可以完成原型模式的设计。
首先我们创建一个Sheep类并实现Cloneable接口:

 public class Sheep implements Cloneable{
   private String name;

   public Sheep(String name){
     this.name = name;
   }

   public void setName(String name){
     this.name = name;
   }

   public String getName(){
     return this.name;
   }

   /**
    * 覆写clone方法
   **/
   @Override
   public Sheep clone() throws CloneNotSupportedException{
     return new Sheep(name); //此处是关键
   }

 }

然后我们可以通过如下的方式来克隆一个对象并进行修改:

  public class TestPrototype{
    public static void main(String[] args){
      Sheep oldSheep = new Sheep("oldSheep");
      System.out.println(oldSheep.getName());

      //根据需要对oldSheep进行克隆和修改
      Sheep cloneSheep = oldSheep.clone();
      cloneSheep.setName("cloneSheep");

      System.out.println(cloneSheep.getName());
    }
  }

输入内容:

  oldSheep
  cloneSheep

4. 适用范围

  • 当系统独立于其他产品的创建、组合和表示方式的时候
  • 当要在运行时指定实例化的类时,例如动态加载类
  • 当需要避免重复创建结构相似的工厂类的时候
  • 当一个类的实例可以有几个不同的状态组合时,创建相应数量的原型对象并进行克隆的方式比手动实例化类要简单得多。
  • 当需要降低系统开销时。克隆对象的成本要比创建对象低很多

5. 浅克隆与深克隆

原型模式涉及到 浅克隆深克隆 的概念 。首先我们通过代码来演示来说明什么是浅克隆。定义一个Sheep类实现Cloneable接口,并覆写clone方法,且在原来的代码基础上增加一个Color属性:

package com.ramostear.pattern.prototype;
/**
 * @author ramostear
 * @create-time 2019/1/4 0004-14:44
 * @modify by :ramostear
 * @info:[原型类:Sheep]
 * @since:
 */

public class Sheep implements Cloneable{

    private String name;

    private  Color color = new Color();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Sheep(String name) {
        this.name = name;
    }

    public Color getColor() {
        return color;
    }

    public void setColor(Color color) {
        this.color = color;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Sheep sheep = (Sheep)super.clone();
        return sheep;
    }

}

说明:代码中增加了Color类的引用,Color类中什么都没有定义,只是一个空的类。

测试代码:

package com.ramostear.pattern.prototype;
/**
 * @author ramostear
 * @create-time 2019/1/4 0004-14:50
 * @modify by :ramostear
 * @info:[浅克隆测试代码]
 * @since:
 */

public class TestPrototype {

    public static  void main(String[] args) throws CloneNotSupportedException{
        Sheep sheep1 = new Sheep("sheep1");
        Sheep sheep2 = (Sheep) sheep1.clone();
        System.out.println("print hashcode ...");
        System.out.println("sheep1`s hashcode:"+sheep1.hashCode());
        System.out.println("sheep2`s hashcode:"+sheep2.hashCode());

        System.out.println("print color ...");
        System.out.println("sheep1`s color:"+sheep1.getColor().hashCode());
        System.out.println("sheep2`s color:"+sheep2.getColor().hashCode());
    }
}

控制台输出结果:
浅克隆-控制台输入结果

从输出结果可以看到,我们对sheep1进行了克隆,得到sheep2,此时的sheep2是一个全新的引用,但由于Sheep类中加入了另外的一个引用 Color,所以sheep1和sheep2对Color的引用还是同一个,输出的hashcode都为‘356573597’,证明Color的引用没有连带克隆,这就是浅克隆。

如何做到连同Color对象一起克隆而不是指向同一个引用?这就是接下来要讲的深克隆

首先我们对Color类进行一些改造,让Color类同样实现Cloneable接口,并覆写clone方法。

package com.ramostear.pattern.prototype;
/**
 * @author ramostear
 * @create-time 2019/1/4 0004-15:08
 * @modify by : ramostear
 * @info:[Color类,实现Cloneable接口]
 * @since:
 */
public class Color implements  Cloneable{
    private String color;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Color color = (Color)super.clone();
        return color;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public Color(String color) {
        this.color = color;
    }
    public Color(){}
}

然后对Sheep的clone方法进行一些调整

package com.ramostear.pattern.prototype;
/**
 * @author ramostear
 * @create-time 2019/1/4 0004-14:44
 * @modify by :ramostear
 * @info:[原型类:Sheep]
 * @since:
 */

public class Sheep implements Cloneable{

    private String name;

    private  Color color = new Color();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Sheep(String name) {
        this.name = name;
    }

    public Color getColor() {
        return color;
    }

    public void setColor(Color color) {
        this.color = color;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Sheep sheep = (Sheep)super.clone();
        //增加了对color的设置
        sheep.color = (Color) color.clone();
        return sheep;
    }
}

测试代码保持不变:

package com.ramostear.pattern.prototype;
/**
 * @author ramostear
 * @create-time 2019/1/4 0004-14:50
 * @modify by :ramostear
 * @info:[深克隆测试代码]
 * @since:
 */

public class TestPrototype {


    public static  void main(String[] args) throws CloneNotSupportedException{
        Sheep sheep1 = new Sheep("sheep1");
        Sheep sheep2 = (Sheep) sheep1.clone();
        System.out.println("print hashcode ...");
        System.out.println("sheep1`s hashcode:"+sheep1.hashCode());
        System.out.println("sheep2`s hashcode:"+sheep2.hashCode());

        System.out.println("print color ...");
        System.out.println("sheep1`s color:"+sheep1.getColor().hashCode());
        System.out.println("sheep2`s color:"+sheep2.getColor().hashCode());
    }
}

输出结果:
深克隆-控制台输入结果

从输出结果可以看到,我们对sheep1进行了克隆,得到sheep2,此时的sheep2是一个全新的引用,且sheep2的color也是一个全新的引用,至此,我们就实现了原型模式的深克隆。

6. Java中的实际案例

如何在Tomcat中部署Spring Boot程序?

如何在Tomcat中部署Spring Boot程序?

Spring Boot(三)—Tomcat中部署

Spring Boot应用程序除了生成JAR文件直接运行外,也可以创建成一个WAR文件部署到Web服务器中。在本章节中,我将为你展示如何使用Spring Boot创建WAR文件并在Tomcat Web容器中进行部署。

1. Spring Boot Servlet初始化程序

如果想要在Tomcat Web服务器中部署Spring Boot应用程序,需要让被@SpringBootApplication注解注释的的主类继承SpringBootServletInitializer类,并覆盖configure方法。

下面的代码片段是使用JAR文件运行Spring Boot应用程序的主文件代码:

package com.ramostear.spring.boot.deploytomcat;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author ramostear
 */
@SpringBootApplication
public class DeployTomcatApplication {

    public static void main(String[] args) {
        SpringApplication.run(DeployTomcatApplication.class, args);
    }

}

现在,我们需要扩展SpringBootServletInlitializer类以支持WAR文件部署。扩展后的主类代码如下:

package com.ramostear.spring.boot.tutorial2;

@SpringBootApplication
public class Application extends SpringBootServletInitializer{
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder){
        return builder.sources(Application.class,args);
    }
    public static void main(String[]args){
        SpringBootApplication.run(Application.class,args);
    }
}

2. 设置主类

对于Spring Boot应用程序,你需要明确的指出在maven构建项目是主类的位置。你需要在pom.xml文件加入如下的代码:

<start-class>com.ramostear.spring.boot.tutorial2.Application</start-class>

3. 更新打包方式

想要将Spring Boot应用程序从jar包更换为war包,还需要修改pom.xml中的packaging配置:

<packaging>war</packaging>

现在,我们可以编写一个简单的REST Endpoint来返回字符串“Hey Spring Boot,I am from tomcat server.”。为了演示此功能,我们还需要将Spring Boot Web Starter添加到pom.xml文件中:

<dependencies>
    ...
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    ...
</dependencies>

接下来,我们将在主类中编写一个简单的REST Endpoint:

package com.ramostear.spring.boot.deploytomcat;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author ramostear
 */
@SpringBootApplication
@RestController
public class DeployTomcatApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(DeployTomcatApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(DeployTomcatApplication.class);
    }

    @GetMapping("/")
    public String sayHello(){
        return "Hey Spring Boot,I am from tomcat server.";
    }
}

4. 打包Spring Boot应用程序

我们将使用Maven打包命令创建一个可以在Tomcat服务器中运行的WAR文件。使用Maven命令:mvn:package打包应用程序。你可以在当前工程下的target目录中找到war文件:

C:\~projectdir> mvn package

控制台输出:

[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building deploy-tomcat 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ deploy-tomcat ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 0 resource

.....


[INFO] 
[INFO] --- spring-boot-maven-plugin:2.1.3.RELEASE:repackage (repackage) @ deploy-tomcat ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.273 s
[INFO] Finished at: 2019-03-04T23:34:12+08:00
[INFO] Final Memory: 38M/288M
[INFO] ------------------------------------------------------------------------

Process finished with exit code 0

5 . 部署到Tomcat

现在,运行Tomcat服务器,并在webapps目录下部署准备好的war文件。你也可以登录Tomcat的管理界面上传war文件并部署:

成功部署war文件后,代开浏览器,并在浏览器地址栏输入:http://localhost:8080/deploy-tomcat-0.0.1-SNAPSHOT/ ,观察页面输出结果:

6 . 主要代码

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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ramostear</groupId>
    <artifactId>deploy-tomcat</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>deploy-tomcat</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <packaging>war</packaging>
</project>

DeployTomcatApplication.java文件:

package com.ramostear.spring.boot.deploytomcat;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author ramostear
 */
@SpringBootApplication
@RestController
public class DeployTomcatApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(DeployTomcatApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(DeployTomcatApplication.class);
    }

    @GetMapping("/")
    public String sayHello(){
        return "Hey Spring Boot,I am from tomcat server.";
    }
}
Spring Boot 2.0 快速构建应用

Spring Boot 2.0 快速构建应用

Spring Boot系列教程(二)快速构建

在本章节中,将想你展示如何利用Spring Initializer快速构建一个Spring Boot应用程序。

1. Spring Initializer

快速构建起一个Spring Boot应用程序的方法之一是使用Spring Initializer.首先,我们需要访问Spring Initializer的官网https://start.spring.io ,接下来,我们需要选择相应的构建方式、语言以及Spring Boot的版本。默认的构建方式是Maven Project,使用Java语言,Spring Boot的版本默认最新版本。

接下来,我们需要提供一个项目的GroupId和ArtifactId,最后是检索并选择我们需要引入的依赖包。以上选择设置完成后,点击“Generate Project”按钮生成并下载Spring Initializer生成的项目包。本次案例中,我们将添加spring-boot-starter-web依赖项,用于编写一个REST风格的端点示例:

Spring Initializer

2.Maven

现在,解压下载后的项目压缩包,并导入到对应的IDE中(本次案例使用的是Spring Tools Suite),打开根目录下的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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ramostear</groupId>
    <artifactId>quick-build</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>quick-build</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3. 依赖包

Spring Boot为开发者提供了很多starters用于快速添加依赖包。本次案例中我们添加了一个Spring Web依赖的Starter:

...
<dependencies>
    ...
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    ...
</dependencies>
...

4. 主类和方法

一个Spring Boot应用程序需要提供应用主类和方法,并且该主类需要(通常)使用@SpringBootApplication注解进行标注。你可以在src/java/main目录下找到主类文件。

此示例中,主类文件位于src/java/main目录中,且位于com.ramostear.quickbuild包中:

package com.ramostear.quickbuild;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * @author:  ramostear
 * @date:   2019年3月3日下午9:49:28
 */
@SpringBootApplication
public class QuickBuildApplication {

    public static void main(String[] args) {
        SpringApplication.run(QuickBuildApplication.class, args);
    }

}

5. REST EndPoint

接下来,我们将在主类中编写一个返回“Hello Spring Boot”信息的Rest Endpoint。我们需要在主类上新增一个@ResController注解,然后使用@GetMapping注解注释我们的请求方法:

package com.ramostear.quickbuild;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author:  ramostear
 * @date:   2019年3月3日下午9:49:28
 */
@SpringBootApplication
@RestController
public class QuickBuildApplication {

    public static void main(String[] args) {
        SpringApplication.run(QuickBuildApplication.class, args);
    }


    @GetMapping("/")
    public String sayHello() {
        return "Hello Spring Boot.";
    }


}

6. 创建可执行的JAR文件

现在,我们可用通过Maven命令行来创建一个可执行的JAR文件。在命令行工具中使用Maven命令:mvn clean install

mvn clean install

执行上述命令后,你可以在控制台看到BUILD SUCCESS消息:

BUILD SUCCESS

7. 运行JAR 文件

上述步骤完成后,可以在target目录中找到创建的JAR文件:

target dir

现在,可以使用命令java -jar 运行JAR文件。本案例中创建的JAR文件名为quick-build-0.0.1-SNAPSHOT.jar

C:\Users\Administrator\Desktop\quick-build\target> java -jar quick-build-0.0.1-SNAPSHOT.jar

JAR文件成功运行后,你可以在控制台下看到如下的信息输出:

console start infomatioin

控制台信息显示Tomcat容器的服务端口为8080,打开浏览器并在地址栏输入http://localhost:8080/,你可以看到如下的输出:

browser info

配置Spring Boot 应用程序参数

配置Spring Boot 应用程序参数

Spring Boot(四)—应用程序属性

Spring Boot应用程序属性支持我们开发的应用工作在不同的环境中。在本章节中,我将展示如何配置和指定Spring Boot 应用程序的属性。

1. 命令行属性

Spring Boot应用程序可以将命令行属性转换为Spring Boot Environment属性。在Spring Boot中,命令行属性的优先级高于其他资源属性。在默认情况下,Spring Boot使用8080端口作为Tomcat的启动端口。下面让我们使用命令行属性来更改端口号:

  • 1.创建可执行的JAR文件,然后使用命令 java -jar 运行jar文件。

  • 2.使用命令行更改Spring Boot应用程序的默认端口号

    D:\work\2019\domain-driver-design\deploy-tomcat\target> java -jar deploy-tomcat-0.0.1-SNAPSHOT.war --server.port = 8888
    

    :你可以使用分隔符“-”来分割多个属性

2. 属性文件

属性文件的作用是可以在单个文件中设置多个属性的值。在Spring Boot 应用程序中,属性保存在类路径下的application.properties文件中。通常情况下,application.properties文件存放在src/main/resource目录中,其属性设置格式如下:

server.port = 8888
server.tomcate.max-http-post-size = -1
spring.application.name = spring-boot-tutorial
spring.datasource.username = admin
spring.datasource.password = springboottutorial
...

3 . YAML文件

Spring Boot应用程序支持基于YAML格式的属性配置文件为Spring Boot提供属性设置。YAML文件时Spring Boot比较推荐的属性配置文件,相较于application.properties文件,YAML配置文件册层次更为清晰。同样的,YAML文件也存放于类路径下(“src/main/resource”),其文件内容格式为:

server:
  port: 8888
  tomcate:
    max-http-post-size: -1
spring: 
  application:
    name: spring-boot-tutorial
  datasource:
    username: admin
    password: springboottutorial
  mvc:
    favicon:
      enabled: false

注:YAML文件中,每个属性的上下层级之间相差两个空格

4. 外部属性文件

Spring Boot支持我们将属性配置文件放在外部空间中,而不仅仅局限于将配置文件放置在类路径下。在运行JAR文件的时候,我们可以指定外部配置文件的存放路径。

D:\work\2019\domain-driver-design\deploy-tomcat\target> java -jar -Dspring.config.location = D:\work\configs\application.properties deploy-tomcat-0.0.1-SNAPSHOT.war

5. @Value 注解

在Spring Boot应用程序中,可以使用@Value注解读取应用中的属性值,其读取相关属性值得语法如下:

@Value("${property_key_name}")
Object param;

接下来,我们将演示使用@Value注解来读取application.properties配置文件中spring.application.name属性的值:

@Value("${spring.application.name}")
private String appName;

完整的示例代码:

package com.ramostear.spring.boot.properties;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class SpringBootPropertiesApplication {

    @Value("${spring.application.name}")
    private String appName;

    public static void main(String[] args) {
        SpringApplication.run(SpringBootPropertiesApplication.class, args);
    }

    @GetMapping("/")
    public String say(){
        return appName;
    }
}

application.properties文件内容:

spring.application.name= Spring Boot Properties Demo

下面,我们启动程序,并在浏览器地址栏中输入:http://localhost:8080/ ,观察浏览器页面结果:

注:如果在程序运行时没有在配置文件中提供该属性,则Spring Boot将抛出IllegalArgumentException异常信息:

Could not resolve placeholder 'spring.application.name' in value "${spring.application.name}"

因为Spring Boot无法解析表达式“${spring.application.name}”中的占位符“spring.application.name”。

要解决上面演示的问题,只需要在表达式后面增加一个默认的属性值即可。

  • 语法:

    @Value("${property_key_name:default_value}")
    
  • 代码示例:

    package com.ramostear.spring.boot.properties;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @SpringBootApplication
    @RestController
    public class SpringBootPropertiesApplication {
    
        @Value("${spring.application.name:Spring Boot Application}")
        private String appName;
    
        public static void main(String[] args) {
            SpringApplication.run(SpringBootPropertiesApplication.class, args);
        }
    
        @GetMapping("/")
        public String say(){
            return appName;
        }
    }
    

再次运行应用程序,观察浏览器页面变化:

6. active配置模式

Spring Boot支持基于Spring active模式来设置不同环境下的应用属性。例如,我们可以创建两份配置文件application_dev.properties和application_prod.properties,application_dev.properties用于开发环境中设置属性,application_prod.properties则用于生产环境中配置应用属性。

首先,我们来看一下默认的application.properties文件中的配置:

server.port = 8888
spring.application.name = Default Spring Boot Application Name

启动应用程序,观察控制台输出信息:

2019-03-05 01:29:19.590  INFO 7828 --- [main] .r.s.b.p.SpringBootPropertiesApplication : 
No active profile set, falling back to default profiles: default

2019-03-05 01:29:20.869  INFO 7828 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : 
Tomcat initialized with port(s): 8888 (http)

现在,Tomcat服务器已经使用8888端口启动成功,且提示没有设置任何的active profile。

接下来,我们创建两个active配置文件application_dev.properties和application_prod.properties,其配置内容如下。

application_dev.properties配置文件:

server.port = 8081
spring.application.name = Development Spring Boot Application Name

application_prod.properties配置文件:

server.port = 8082
spring.application.name = Product Spring Boot Application Name

两个独立的配置文件准备好后,我们需要调整application.properties文件的配置内容,以模拟使用active配置文件来切换不同环境下使用不同的属性配置:

  • 1.将默认的属性配置切换到dev模式上,切换到开发环境上的配置命令如下:

    D:\work\2019\domain-driver-design\spring-boot-properties\target> java -jar spring-boot-properties-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
    

    你可以在控制台中观察到如下的输出:

    2019-03-05 02:02:00.013  INFO 7700 ---[main] .r.s.b.p.SpringBootPropertiesApplication : The following profiles are active: dev
    2019-03-05 02:02:01.697  INFO 7700 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8081 (http)
    2019-03-05 02:02:01.732  INFO 7700 --- [main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
    

    现在,Tomcat服务器已经使用端口8081(http)

  • 2.接下来,我们再将应用从dev切换到prod环境上,执行如下命令:

    D:\work\2019\domain-driver-design\spring-boot-properties\target>java -jar spring-boot-properties-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
    

    你可以在控制台中观察到如下的输出:

    2019-03-05 02:09:33.983  INFO 5944 --- [main] .r.s.b.p.SpringBootPropertiesApplication : The following profiles are active: prod
    
    2019-03-05 02:09:35.643  INFO 5944 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8082 (http)
    
    2019-03-05 02:09:35.678  INFO 5944 --- [main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
    

    现在,Tomcat服务器已经使用端口8082(http)

如果应用使用的是YAML文件来配置应用属性,我们可以将不同环境的配置信息放在一个配置文件中,而不用像使用properties文件那样创建多个独立的配置文件。

下面我们将演示使用YAML文件来配置并切换不同环境下的属性配置,在YAML文件中,使用分隔符“—-”来分割不同active下的属性配置:

application.yml配置文件

spring:
   application:
      name: Default Spring Application Name
server:
   port: 8080

---
spring:
   profiles: dev
   application:
      name: Development Spring Application Name
server:
   port: 8081

---
spring:
   profiles: prod
   application:
      name: Product Spring Application Name
server:
   port: 8082

说明:使用YAML作为配置文件时,切换active的命令与使用Properties配置文件时一致。

优雅的处理Spring Boot异常信息

优雅的处理Spring Boot异常信息

Spring Boot (七)— 异常处理

异常处理是一种识别并响应错误的一致性机制,异常机制可以把程序中的异常处理代码和正常的业务逻辑代码分离,包装程序的可读性和健壮性。在Spring Boot应用程序中,能够捕获并及时的响应客户端的错误操作是一件非常重要的事情。在本章节中,我将展示如何处理Spring Boot中的异常。

1. 相关注解说明

在进行演示之前,我们先了解一下在Spring Boot应用程序中与异常处理相关的几个注解

注解名称 说明
@ControllerAdvice 该标签用于处理全局的异常信息
@ExceptionHadler 用于处理特定异常信息,并返回相关的响应到客户端

首先,我们需要使用@ControllerAdvice注解来定义一个全局的异常信息处理类,其语法如下:

package com.ramostear.exception.handler;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * @author : ramostear
 * @date : 2019/3/6 0006-16:33
 */
@ControllerAdvice
public class UserExceptionHandler {
    //TODO ...
}

接下来,我们需要定义一个扩展了RuntimeException类的自定义异常处理类:

package com.ramostear.exception.handler;

/**
 * @author : ramostear
 * @date : 2019/3/6 0006-16:31
 */
public class UserNotFoundException extends RuntimeException{
    private static final long serialVersionUID = 5534028121297403043L;
}

最后,我们使用@ExceptionHandler注解来定义一个处理具体异常信息的方法,其语法如下:

@ExceptionHandler(value = UserNotFoundException.class)
    public ResponseEntity<Object> exception(UserNotFoundException ex){
        return new ResponseEntity<>("user not found.", HttpStatus.NOT_FOUND);
    }

以上工作准备完成之后,我们可以使用如下的方式来处理API中的异常信息:

@GetMapping("/users/{id}")
    public ResponseEntity<Object> getUser(@PathVariable(name = "id") long id){
        if(!userRepo.containsKey ( id )){
            throw new UserNotFoundException ();
        }
        return new ResponseEntity<> (userRepo.get (id), HttpStatus.OK);
    }

在接下来的内容当中,我将给出完整的示例代码,使用HTTP GET方法请求一个用户信息,当用户存储库中没有相应的用户信息时,返回“user not found”提示信息。

2. 自定义异常信息类 — UserNotFoundException.java

package com.ramostear.exception.handler;

/**
 * @author : ramostear
 * @date : 2019/3/6 0006-16:31
 */
public class UserNotFoundException extends RuntimeException{
    private static final long serialVersionUID = 5534028121297403043L;
}

说明:这里只是做了一个简单的扩展

2. 全局异常处理类 —UserExceptionHandler.java

package com.ramostear.exception.handler;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * @author : ramostear
 * @date : 2019/3/6 0006-16:33
 */
@ControllerAdvice
public class UserExceptionHandler {


    @ExceptionHandler(value = UserNotFoundException.class)
    public ResponseEntity<Object> exception(UserNotFoundException ex){
        return new ResponseEntity<>("user not found.", HttpStatus.NOT_FOUND);
    }


}

在UserExceptionHandler.java文件中,我们定义了一个处理用户不存在异常的方法,

3. API类 — UserServiceController.java

package com.ramostear.exception.handler.controller;

import com.ramostear.exception.handler.UserNotFoundException;
import com.ramostear.exception.handler.model.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

/**
 * @author : ramostear
 * @date : 2019/3/6 0006-16:26
 */
@RestController
public class UserServiceController {

    private static Map<Long,User> userRepo = new HashMap<>();

    @PostConstruct
    public void initUserRepo(){
        User admin = new User ().setId ( 1 ).setName ( "admin" );
        userRepo.put ( admin.getId (),admin );

        User editor = new User ().setId ( 2 ).setName ( "editor" );
        userRepo.put ( editor.getId (),editor );
    }


    @GetMapping("/users/{id}")
    public ResponseEntity<Object> getUser(@PathVariable(name = "id") long id){
        if(!userRepo.containsKey ( id )){
            throw new UserNotFoundException ();
        }
        return new ResponseEntity<> (userRepo.get (id), HttpStatus.OK);
    }

}

在getUser()方法中,如果用户没有找到,则抛出UserNotFoundException异常。

4. 应用主类 —ExceptionHandlerApplication.java

package com.ramostear.exception.handler;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ExceptionHandlerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ExceptionHandlerApplication.class, args);
    }

}

5. 用户POJO类 — User.java

package com.ramostear.exception.handler.model;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * @author : ramostear
 * @date : 2019/3/6 0006-16:23
 */
@Getter
@Setter
@NoArgsConstructor
public class User {

    private long id;

    private String name;

    public User setId(long id){
        this.id = id;
        return this;
    }

    public User setName(String name){
        this.name = name;
        return this;
    }
}

6. Maven构建文件 — 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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ramostear</groupId>
    <artifactId>exception-handler</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>exception-handler</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

8. 运行测试

接下来,我们将打包运行我们的程序,本次教程演示将使用IDEA来运行程序,运行结果如下图所示:

然后,启动Postman应用程序,我们先在地址栏输入:http://localhost:8080/users/1 ,观察正常情况下的测试信息:

Postman的测试结果显示,请求状态为200,且返回了用户的详细信息。现在,我们更新URL为:http://localhost:8080/users/3 ,再次观察测试结果:

此时的HTTP Status为404,且返回了“user not found.”的提示信息。

使用Spring Boot构建RESTful 服务

使用Spring Boot构建RESTful 服务

Spring Boot(六) — 构建RESTful Web服务

Spring Boot能够轻松的构建起企业级的RESTful Web服务程序。在本章节中,我将通过一个简单的例子演示Spring Boot的这一能力。
为了构建RESTful Web服务,我们需要将Spring Boot Starter Web依赖添加到pom.xml配置文件中。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

下面是完整的Maven 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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ramostear</groupId>
    <artifactId>restful-webservice</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>restful-webservice</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

说明:Lombok是一个java库,可以自动插入编辑器并构建工具,使用lombok注解可以自动生成getter、setter以及构造函数等方法,你可以到Lombok官网了解更多详细信息。

1. RestController

@RestController注解用于定义RESTful Web服务,它提供JSON、XML以及自定义的相应信息,其使用语法如下:

@RestController
public class UserController{
    //TODO ...
}

2. RequestMapping

@RequestMapping注解用于定义访问REST Endpoint的请求路径。@RequestMapping的使用语法如下:

@RequestMapping("/users")
public ResponseEntity<Object> getUsers(){
    //TODO ...
}

注:@RequestMapping注解默认的请求方法GET.

在接下来的内容当中,我们将使用@RequestMapping的组合注解来定义REST Endpoint方法的映射路径:

3. RequestBody

@RequestBody注解用于定义请求正文内容类型。其语法如下:

public ResponseEntity<Object> createUser(@RequestBody User user){
    //TODO ...
}

4. PathVariable

@PathVariable注解用于定义请求路径中的动态变量,请求路径中的动态变量使用花括号“{}”包裹起来。语法如下:

@PutMapping("/users/{id}")
public ResponseEntity<Obejct> updateUser(@PathVariable("id") long id,@RequestBody User user){
    //TODO ...
}

5. GET API

默认的HTTP请求方法是GET,GET方法不需要任何的Request Body。你可以发送任何的请求参数和路径变量来定义一个动态的URL。接下来,我将演示如何定义一个HTTP GET请求方法,用于获取所有的用户信息。

package com.ramostear.restful.webservice.controller;

import com.ramostear.restful.webservice.model.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

/**
 * @author ramostear
 * @create-time 2019/3/6 0006-3:14
 * @modify by :
 * @since:
 */
@RestController
public class UserServiceController {

    private static Map<Long,User> userRepository = new HashMap<>();

    @PostConstruct
    public void initUserRepository(){
        User admin = new User();
        admin.setId(1).setName("admin");
        userRepository.put(admin.getId(),admin);

        User editor = new User();
        editor.setId(2).setName("editor");
        userRepository.put(editor.getId(),editor);
    }

   @GetMapping("/users")
    public ResponseEntity<Object> getUser(){
        return new ResponseEntity<>(userRepository.values(), HttpStatus.OK);
    }

}

在这里,我使用了一个Hash Map来扮演用户存储库的角色,定义的请求路径是/users

6. POST API

HTTP POST请求用于创建新的资源,此方法包含了Request Body。下面我将演示如何定义HTTP POST方法来创建用户信息,并存储到用户存储库中。

package com.ramostear.restful.webservice.controller;

import com.ramostear.restful.webservice.model.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

/**
 * @author ramostear
 * @create-time 2019/3/6 0006-3:14
 * @modify by :
 * @since:
 */
@RestController
public class UserServiceController {

    private static Map<Long,User> userRepository = new HashMap<>();

    @PostConstruct
    public void initUserRepository(){
        User admin = new User();
        admin.setId(1).setName("admin");
        userRepository.put(admin.getId(),admin);

        User editor = new User();
        editor.setId(2).setName("editor");
        userRepository.put(editor.getId(),editor);
    }


    @PostMapping("/users")
    public ResponseEntity<Object> createUser(@RequestBody User user){
        userRepository.put(user.getId(),user);
        return new ResponseEntity<>("User is created successfully",HttpStatus.CREATED);
    }
}

7. PUT API

HTTP PUT请求用于更新存储库中现有的资源,此方法包含请求正文。接下来我将给出相关示例来演示如何定义一个HTTP PUT请求方法来更新用户信息。

package com.ramostear.restful.webservice.controller;

import com.ramostear.restful.webservice.model.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

/**
 * @author ramostear
 * @create-time 2019/3/6 0006-3:14
 * @modify by :
 * @since:
 */
@RestController
public class UserServiceController {

    private static Map<Long,User> userRepository = new HashMap<>();

    @PostConstruct
    public void initUserRepository(){
        User admin = new User();
        admin.setId(1).setName("admin");
        userRepository.put(admin.getId(),admin);

        User editor = new User();
        editor.setId(2).setName("editor");
        userRepository.put(editor.getId(),editor);
    }

    @PutMapping("/users/{id}")
    public ResponseEntity<Object> updateUser(@PathVariable(name = "id") long id,@RequestBody User user){
        userRepository.remove(id);
        user.setId(id);
        userRepository.put(id,user);
        return new ResponseEntity<>("User is updated successfully",HttpStatus.OK);
    }

}

在此方法中,定义的请求路径是/users/{id} ,其中{id}是动态的路径变量,用来定义需要更新的用户ID。

8. DELETE API

HTTP DELETE请求用于删除存储库中现有的资源,此方法不包含任何请求正文。接下来我将给出相关示例来演示如何定义一个HTTP PUT请求方法来更新用户信息。

package com.ramostear.restful.webservice.controller;

import com.ramostear.restful.webservice.model.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

/**
 * @author ramostear
 * @create-time 2019/3/6 0006-3:14
 * @modify by :
 * @since:
 */
@RestController
public class UserServiceController {

    private static Map<Long,User> userRepository = new HashMap<>();

    @PostConstruct
    public void initUserRepository(){
        User admin = new User();
        admin.setId(1).setName("admin");
        userRepository.put(admin.getId(),admin);

        User editor = new User();
        editor.setId(2).setName("editor");
        userRepository.put(editor.getId(),editor);
    }


    @DeleteMapping("/users/{id}")
    public ResponseEntity<Object> delete(@PathVariable(name = "id") long id){
        userRepository.remove(id);
        return new ResponseEntity<>("User is deleted successfully",HttpStatus.OK);
    }

}

在此方法中,定义的请求路径是/users/{id} ,其中{id}是动态的路径变量,用来定义需要删除的用户ID。

9. 代码清单

9.1 Spring Boot应用程序主类 — RestfulWebserviceApplication.java

package com.ramostear.restful.webservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RestfulWebserviceApplication {

    public static void main(String[] args) {
        SpringApplication.run(RestfulWebserviceApplication.class, args);
    }

}

9.2 POJO类 — User.java

package com.ramostear.restful.webservice.model;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * @author ramostear
 * @create-time 2019/3/6 0006-3:12
 * @modify by :
 * @since:
 */
@Getter
@Setter
@NoArgsConstructor
public class User {
    private long id;
    private String name;

    public User setId(long id){
        this.id = id;
        return this;
    }

    public User setName(String name){
        this.name = name;
        return this;
    }
}

9.3 Rest Controller类 — UserServiceController.java

package com.ramostear.restful.webservice.controller;

import com.ramostear.restful.webservice.model.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

/**
 * @author ramostear
 * @create-time 2019/3/6 0006-3:14
 * @modify by :
 * @since:
 */
@RestController
public class UserServiceController {

    private static Map<Long,User> userRepository = new HashMap<>();

    @PostConstruct
    public void initUserRepository(){
        User admin = new User();
        admin.setId(1).setName("admin");
        userRepository.put(admin.getId(),admin);

        User editor = new User();
        editor.setId(2).setName("editor");
        userRepository.put(editor.getId(),editor);
    }

    @GetMapping("/users")
    public ResponseEntity<Object> getUser(){
        return new ResponseEntity<>(userRepository.values(), HttpStatus.OK);
    }

    @PostMapping("/users")
    public ResponseEntity<Object> createUser(@RequestBody User user){
        userRepository.put(user.getId(),user);
        return new ResponseEntity<>("User is created successfully",HttpStatus.CREATED);
    }

    @PutMapping("/users/{id}")
    public ResponseEntity<Object> updateUser(@PathVariable(name = "id") long id,@RequestBody User user){
        userRepository.remove(id);
        user.setId(id);
        userRepository.put(id,user);
        return new ResponseEntity<>("User is updated successfully",HttpStatus.OK);
    }

    @DeleteMapping("/users/{id}")
    public ResponseEntity<Object> delete(@PathVariable(name = "id") long id){
        userRepository.remove(id);
        return new ResponseEntity<>("User is deleted successfully",HttpStatus.OK);
    }

}

10. 测试

你可以选择命令行工具或者IDE来编译、打包并运行项目,这里我使用Intellij IDEA来运行项目,运行结果如下图所示:

接下来,我们将使用Postman应用程序来测试准备好的RESTful Web 服务接口:

  1. GET API : http://localhost:8080/users

  1. POST API : http://locahost:8080/users

  1. PUT API : http://locahost:8080/users/3

  1. DELETE API : http://localhost:8080/users/3