比较Spring Boot和Dropwizard的应用程序代码

我们将在代码基础上比较Dropwizard的Spring版本和Spring Boot。为了更加清晰地进行比较,我们将按照Dropwizard的包结构和命名进行设置。另外,为了与Dropwizard类似的编写方式,我们还引入了一些在Spring Boot中不是必需的jar包。

我将在代码注释中写上Dropwizard的情况。

Maven配置

因为想要写一篇关于代码比较的文章,所以在这里不会涉及Dropwizard的pom.xml。如果你有兴趣,请参考以下的文章。

    • いますぐ採用すべきJavaフレームワークDropWizard(その1)

 

    • いますぐ採用すべきJavaフレームワークDropWizard(その2)

 

    • いますぐ採用すべきJavaフレームワークDropWizard(その3)

 

    dropwizard-testingが便利でした

以下是 Spring Boot 的一部分情况。

  <!-- Inherit defaults from Spring Boot -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.1.2.RELEASE</version>
  </parent>

  <dependencies>
<!-- tomcatからjettyに変更したい場合の記述。liquibaseを依存させたらtomcatで動作しなかったため -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>${boot.version}</version>
      <exclusions>
        <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jetty</artifactId>
      <version>${boot.version}</version>
    </dependency>

<!-- メトリックスを利用したい場合だけ -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-actuator</artifactId>
      <version>${boot.version}</version>
    </dependency>

<!-- DBを利用したい場合だけ -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
      <version>${boot.version}</version>
    </dependency>

<!-- freemarkerを利用したい場合だけ。これを依存させるとclasspath:/templates/ディレクトリがないとエラーになりました -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-freemarker</artifactId>
      <version>${boot.version}</version>
    </dependency>

<!-- 必須でない -->
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>17.0</version>
    </dependency>

<!-- liquibaseを利用したい場合だけ -->
    <dependency>
      <groupId>org.liquibase</groupId>
      <artifactId>liquibase-core</artifactId>
      <version>3.2.0</version>
    </dependency>

    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <version>1.3.175</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <version>${boot.version}</version>
      <scope>test</scope>
    </dependency>

  </dependencies>

  <build>
    <resources>
      <resource>
       <directory>src/main/resources</directory>
        <includes>
          <include>**/*</include>
        </includes>
      </resource>
    </resources>  
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
・・・
    </plugins>
  </build>

你好-世界(基础)的比较

首先,我们将使用一个相对轻松的”Hello-World”示例来进行比较。
源代码中的注释部分将转变为Dropwizard。

应用程序类

Dropwizard的作用是为该类添加各种功能,因此要写很多代码。
在Spring Boot中,可以通过SpringApplication类或DI来实现这些。

@Configuration
@EnableAutoConfiguration
@ComponentScan
@EnableConfigurationProperties
public class HelloWorldApplication {
//public class HelloWorldApplication extends Application<HelloWorldConfiguration> {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(HelloWorldApplication.class, args);
        //new HelloWorldApplication().run(args);
    }

//  @Override
//  public String getName() {
//      return "hello-world";
//  }
//
//  @Override
//  public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
//  }
//
//  @Override
//  public void run(HelloWorldConfiguration configuration,
//          Environment environment) throws ClassNotFoundException {
//      final Template template = configuration.buildTemplate();
//      environment.jersey().register(new HelloWorldResource(template));
    }

配置类

“hello-world”这个前缀是在example.yml文件中定义的一个键。
在Spring Boot中,如果不将这个前缀添加到yml文件中,应用将无法启动。

附带一提,在启动时指定了example.yml。

$ java -jar target/spike-spring-boot-1.0-SNAPSHOT.jar --spring.config.location=example.yml

$ java -jar target/spike-dropwizard-1.0-SNAPSHOT.jar server example.yml
@ConfigurationProperties(prefix = "hello-world", ignoreUnknownFields = false)
@Component
public class HelloWorldConfiguration {
//public class HelloWorldConfiguration extends Configuration {
    @NotEmpty
    private String template;

    @NotEmpty
    private String defaultName = "Stranger";

//  @JsonProperty
    public String getTemplate() {
        return template;
    }

//  @JsonProperty
    public void setTemplate(String template) {
        this.template = template;
    }

//  @JsonProperty
    public String getDefaultName() {
        return defaultName;
    }

//  @JsonProperty
    public void setDefaultName(String defaultName) {
        this.defaultName = defaultName;
    }

//  public Template buildTemplate() {
        return new Template(template, defaultName);
    }
}
hello-world:
    template: Hello, %s!
    defaultName: Stranger
#template: Hello, %s!
#defaultName: Stranger

资源类

如果在POST中存在验证器错误(在这种情况下,content必须是至少4个字符的”hoge”),则会有以下区别。

以下是不同的方式来原生中文释义”HTTP状态码”:

– HTTP状态码指示了在进行HTTP通信时所发生的特定情况。
– HTTP状态码用于标识在HTTP传输过程中发生的特定情况。
– HTTP状态码用来指示HTTP通信中出现的具体情况。

422 <- Dropwizard

400 <- Spring Boot

身体是

{“errors”:[“content length must be between 0 and 3 (was hoge)”]} <- Dropwizard

{“timestamp”:1403789235278,”status”:400,”error”:”Bad Request”,”exception”:”org.springframework.web.bind.MethodArgumentNotValidException”,”message”:”Validation failed for argument at index 0 in method: public void com.github.ko2ic.resources.HelloWorldResource.receiveHello(com.github.ko2ic.core.Saying), with 1 error(s): [Field error in object ‘saying’ on field ‘content’: rejected value [hoge]; codes [Length.saying.content,Length.content,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [saying.content,content]; arguments []; default message [content],3,0]; default message [length must be between 0 and 3]] “,”path”:”/hello-world”} <- Spring Boot

//@Path("/hello-world")
//@Produces(MediaType.APPLICATION_JSON)
@RestController
@RequestMapping(value = "/hello-world")
public class HelloWorldResource {
    private static final Logger LOGGER = LoggerFactory
            .getLogger(HelloWorldResource.class);

//  private final Template template;
//  private final AtomicLong counter;
//
//  public HelloWorldResource(Template template) {
//      this.template = template;
//      this.counter = new AtomicLong();
//  }

    @Autowired
    private HelloWorldConfiguration configuration;
    private final AtomicLong counter = new AtomicLong();

//  @GET
//  @Timed(name = "get-requests")
//  @CacheControl(maxAge = 1, maxAgeUnit = TimeUnit.DAYS)
//  public Saying sayHello(@QueryParam("name") Optional<String> name) {
//      return new Saying(counter.incrementAndGet(), template.render(name));
//  }
    @RequestMapping(method = RequestMethod.GET)
    public Saying sayHello(
            @RequestParam(value = "name", required = false) String name,
            HttpServletResponse response) {
        response.setHeader("Cache-Control", "max-age=86400");
        Template template = configuration.buildTemplate();
        return new Saying(counter.incrementAndGet(), template.render(Optional
                .fromNullable(name)));
    }

//  @POST
    @RequestMapping(method = RequestMethod.POST)
//  public void receiveHello(@Valid Saying saying) {
    public void receiveHello(@RequestBody @Valid Saying saying) {
        LOGGER.info("Received a saying: {}", saying);
    }
}

模型类

在Spring Boot中,不需要使用@JsonProperty。

public class Saying {
    private long id;

    @Length(max = 3)
    private String content;

    public Saying() {
    }

    public Saying(long id, String content) {
        this.id = id;
        this.content = content;
    }

//  @JsonProperty
    public long getId() {
        return id;
    }

//  @JsonProperty
    public String getContent() {
        return content;
    }

    @Override
    public String toString() {
        return String.format("id=%d content=%s", getId(), getContent());
    }
public class Template {
    private final String content;
    private final String defaultName;

    public Template(String content, String defaultName) {
        this.content = content;
        this.defaultName = defaultName;
    }

    public String render(Optional<String> name) {
        return format(content, name.or(defaultName));
    }
}

DB访问的比较(Hibernate)

应用程序类

在使用Dropwizard时,需要进行以下配置,但使用Spring Boot则不需要进行任何额外的修改。

//  // hibernateを使うため
//  private final HibernateBundle<HelloWorldConfiguration> hibernateBundle = new HibernateBundle<HelloWorldConfiguration>(
//          Person.class) {
//      @Override
//      public DataSourceFactory getDataSourceFactory(
//              HelloWorldConfiguration configuration) {
//          return configuration.getDataSourceFactory();
//      }
//  };
//
//  @Override
//  public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
//      // liquibaseを使うため
//      bootstrap.addBundle(new MigrationsBundle<HelloWorldConfiguration>() {
//          @Override
//          public DataSourceFactory getDataSourceFactory(
//                  HelloWorldConfiguration configuration) {
//              return configuration.getDataSourceFactory();
//          }
//      });
//      bootstrap.addBundle(hibernateBundle);
//  }
//  @Override
//  public void run(HelloWorldConfiguration configuration,
//          Environment environment) throws ClassNotFoundException {
//      final PersonRepository repository = new PersonRepository(
//              hibernateBundle.getSessionFactory());
//      environment.jersey().register(new PeopleResource(repository));

配置类

在Dropwizard中,我们使用DataSourceFactory。
在Spring Boot中,会自动配置DataSourceAutoConfiguration和DataSourceProperties,所以您只需在配置文件中进行一些设置即可。
(使用@EnableAutoConfiguration注解在Application类中,会自动加载在spring-boot-autoconfigure.jar/META-INF/spring.factories中定义的类。)

    // @Valid
    // @NotNull
    // private DataSourceFactory database = new DataSourceFactory();
    //
    // @JsonProperty("database")
    // public DataSourceFactory getDataSourceFactory() {
    // return database;
    // }
    //
    // @JsonProperty("database")
    // public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
    // this.database = dataSourceFactory;
    // }

在Dropwizard的情况下,没有特别为liqibase编写代码。

spring:
    application:
        name: Hello World!
    datasource:
        driverClassName: org.h2.Driver
        url: jdbc:h2:target/example
        user: sa
        password: sa
    jpa:
        hibernate:
            ddl-auto: false
        database: H2
        show-sql: true
    freemarker:
        cache: false
# デフォルトは、classpath:/templates/
        templateLoaderPath: classpath:/views/  
liquibase:
    change-log: classpath:/migrations.xml
    drop-first: true
    enabled: false  

# dropwizardの場合は、hello-worldで利用したexample.ymlに追記

#database:
#  driverClass: org.h2.Driver
#  user: sa
#  password: sa
#  url: jdbc:h2:target/example
#  properties:
#    charSet: UTF-8
#    hibernate.dialect: org.hibernate.dialect.H2Dialect
#  maxWaitForConnection: 1s
#  validationQuery: "/* MyApplication Health Check */ SELECT 1"
#  minSize: 8
#  maxSize: 32
#  checkConnectionWhileIdle: false

资源类

特别是当希望有意地返回HTTP状态码404等时,以下有以下区别。

    • DropwizardではNotFoundExceptionを投げるだけ

 

    Spring Bootでは自作例外を作成して、そのクラスを@ExceptionHandlerでハンドリングする
//@Path("/people")
//@Produces(MediaType.APPLICATION_JSON)
@RestController
@RequestMapping(value = "/people")
public class PeopleResource {

//  private final PersonRepository repository;
//  public PeopleResource(PersonRepository repository) {
//      this.repository = repository;
//  }

    @Autowired
    private PeopleRepository peopleRepository;

//  @POST
//  @UnitOfWork
    @RequestMapping(method = RequestMethod.POST)
    @Transactional
    //public Person createPerson(Person person) {
    public @ResponseBody
    Person createPerson(@RequestBody Person people) {
        return repository.create(person);
    }

//  @GET
//  @UnitOfWork
    @RequestMapping(method = RequestMethod.GET)
    @Transactional
    public List<Person> listPeople() {
        return repository.findAll();
    }

//  @GET
//  @UnitOfWork
//  @Path("/{personId}")
    @RequestMapping(value = "/{personId}", method = RequestMethod.GET)
    @Transactional
//  public Person getPerson(@PathParam("personId") LongParam personId) {
    public Person getPerson(@PathVariable("personId") Long personId) {
        final Optional<Person> person = repository.findById(personId.get());
        if (!person.isPresent()) {
            //throw new NotFoundException("Not Found Person");
            throw new PersonNotFoundException("Not Found Person");
        }
        return person.get();
    }

    @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Not Found Person")
    @ExceptionHandler(PersonNotFoundException.class)
    public void notfound() {
        // Nothing to do
    }

    private static class PersonNotFoundException extends RuntimeException {
        public PersonNotFoundException(String message) {
            super(message);
        }
    }
}

实体类.

Dropwizard和Spring Boot两者都是一样的。
在Spring Boot中,如果将列名命名为fullName这样的驼峰命名法,它不会工作。(Dropwizard是可以工作的)

@Entity
@Table(name = "people")
@NamedQueries({ @NamedQuery(name = "com.github.ko2ic.core.People.findAll", query = "SELECT p FROM Person p") })
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "full_name", nullable = false)
    private String fullName;

    @Column(name = "job_title", nullable = false)
    private String jobTitle;

    public Person() {
    }

    public Person(long id, String fullName, String jobTitle) {
        this.id = id;
        this.fullName = fullName;
        this.jobTitle = jobTitle;
    }
// あとはgetter,setterなので省略
}

代码存储类

在Dropwizard中使用AbstractDAO。
在Spring Boot中处理EntityManager。这在Dropwizard中使用Guice时也是类似的。

//public class PersonRepository extends AbstractDAO<Person> {
//  public PersonRepository(SessionFactory factory) {
//      super(factory);
//  }
//
//  public Optional<Person> findById(Long id) {
//      return Optional.fromNullable(get(id));
//  }
//
//  public Person create(Person person) {
//      return persist(person);
//  }
//
//  public List<Person> findAll() {
//      return list(namedQuery("com.github.ko2ic.core.Person.findAll"));
//  }
//}

@Repository
public class PeopleRepository {

    @PersistenceContext
    private EntityManager entityManager;

    public Optional<Person> findById(Long id) {
        return Optional.fromNullable(entityManager.find(Person.class, id));
    }

    public Person create(Person person) {
        entityManager.persist(person);
        return person;
    }

    @SuppressWarnings("unchecked")
    public List<Person> findAll() {
        return entityManager.createNamedQuery(
                "com.github.ko2ic.core.People.findAll").getResultList();
    }
}

使用Liquibase

Dropwizard和Spring Boot的changelog文件是相同的。

<?xml version="1.0" encoding="UTF-8"?>

<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">

    <changeSet id="1" author="ko2ic">
        <createTable tableName="people">
            <column name="id" type="bigint" autoIncrement="true">
                <constraints primaryKey="true" nullable="false" />
            </column>
            <column name="full_name" type="varchar(255)">
                <constraints nullable="false" />
            </column>
            <column name="job_title" type="varchar(255)" />
        </createTable>
    </changeSet>    
</databaseChangeLog>

使用Spring Boot时,如果在配置中将enabled设为true,则每次启动时都会删除表并进行迁移。
因此,在生产环境中需要注意。
尚不清楚通过Spring Boot是否可以使用Liquibase的其他功能。
可能需要直接使用Liquibase。

liquibase:
    change-log: classpath:/migrations.xml
    drop-first: true
    enabled: true

Dropwizard可以通过Dropwizard框架实现以下文章所述的各种功能。

    Dropwizard(Java)でrailsのようにDBマイグレーションをする

使用模板进行动态文件显示的比较(Freemarker)

这次使用的是freemarker,但是在Spring Boot的情况下,使用velocity和thymeleaf也几乎没有区别。

应用程序类

在使用Dropwizard时,需要进行如下设置,而在使用Spring Boot时则不需要做任何额外的添加。

//  @Override
//  public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
//      ・・・
//      bootstrap.addBundle(new ViewBundle());
//  }
//  @Override
//  public void run(HelloWorldConfiguration configuration,
//          Environment environment) throws ClassNotFoundException {
//      ・・・
//      environment.jersey().register(new ViewResource());
//  }

资源类

在Dropwizard的情况下,我们将值设置在继承自View的类中,并返回它以在视图中显示。
而在Spring Boot的情况下,我们将值设置在Map类的参数中供视图显示,并指定返回值为模板文件的位置。

//@Path("/views")
@Controller
public class ViewResource {

    @Value("${spring.freemarker.templateEncoding:UTF-8}")
    private String charset;

//  @GET
//  @Produces("text/html;charset=UTF-8")
//  @Path("/freemarker")
//  public View freemarkerUTF8() {
//      return new View("/views/ftl/utf8.ftl", Charsets.UTF_8) {
//      };
//  }

    @RequestMapping(value = "/views/freemarker")
    public String freemarkerUTF8(Map<String, Object> model) {
        model.put("charset", charset);
        return "ftl/utf8";
    }
}

两边都是相同的模板文件。

<html>
<body>
<h1>This is an example of a freemarker</h1>
文字コード:${charset}
</body>
</html>

比较静态文件显示

Application类

在Dropwizard的情况下,只需要注册AssetsBundle。
在Spring Boot的情况下,需要继承SpringBootServletInitializer类,并重写configure方法。

//public class HelloWorldApplication {
public class HelloWorldApplication extends SpringBootServletInitializer {

//  public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
//      bootstrap.addBundle(new AssetsBundle());
//      ・・・
//  }

    @Override
    protected SpringApplicationBuilder configure(
            SpringApplicationBuilder application) {
        return application.sources(HelloWorldApplication.class);
    }

以下是这里的静态文件。

alert('sample');

本想定可在 http://localhost:8080/assets/js/example.js 中呈现。

对于Dropwizard来说,将example.js放置在src/main/resources/assets/js中。
对于Spring Boot来说,将example.js放置在src/main/resources/static/assets/js中。

自定义健康检查的对比

应用程序类

在Dropwizard中,需要进行以下设置,但是在Spring Boot中并不需要额外添加任何内容。

//  @Override
//  public void run(HelloWorldConfiguration configuration,
//          Environment environment) throws ClassNotFoundException {
//      ・・・
//      environment.healthChecks().register("template",
//              new TemplateHealthCheck(template));

健康检查类

Dropwizard和Spring Boot虽然接口不同,但很相似。

//public class TemplateHealthCheck extends HealthCheck {
//  private final Template template;
//
//  public TemplateHealthCheck(Template template) {
//      this.template = template;
//  }
//
//  @Override
//  protected Result check() throws Exception {
//      return Result.unhealthy(template.render(Optional.of("error")));
//  }
//}
@Component
public class TemplateHealthCheck implements HealthIndicator {

    @Autowired
    private HelloWorldConfiguration configuration;

    @Override
    public Health health() {
        String data = configuration.buildTemplate()
                .render(Optional.of("error"));
        return Health.down().withDetail("message", data).build();
    }
}
Drpwizardの場合
$ curl http://localhost:8081/healthcheck
{"deadlocks":{"healthy":true},"hibernate":{"healthy":true},"template":{"healthy":false,"message":"Hello, error!"}}

Spring Bootの場合
$ curl http://localhost:8081/health
{"status":"DOWN","templateHealthCheck":{"status":"DOWN","message":"Hello, error!"},"db":{"status":"UP","database":"H2","hello":1}}
广告
将在 10 秒后关闭
bannerAds