【进行中】使用Spring Boot创建命令行应用程序的基础结构
简要描述
这篇文章介绍了如何使用Spring Boot创建一个命令行应用程序的框架。(尚未完成)
该应用程序的用途是用于定期执行批处理作业,可以通过cron或jenkins来触发。
(虽然有适用于批处理的Spring Batch框架,但在这个例子中并没有使用它。)
环境
-
- Windows10
-
- Java 1.8.0_92
-
- Spring-Boot 1.4.0
Spring JDBC
MyBatis
MySQL 5.6
请参考
-
- Spring Bootでコマンドラインアプリを作る時の注意点
- mybatis-spring-boot-starterの使い方
源代码
目前而言,源代码尚未完成,但其部分已存在于 sbcl-example。
创建雏形
我是用SPRING INITIALIZR创建了原型。暂时包括了以下三个依赖关系。
-
- JDBC
-
- MySQL
- DevTools
修改过的状态
设定
pom.xml -> maven项目配置文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>sbcl-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sbcl-example</name>
<description>CommandLine Application project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</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>
应用程序属性文件
只需要进行datasource的配置,假设要连接到MySQL数据库。
spring.profiles.active = local
# DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url = jdbc:mysql://localhost/test
spring.datasource.username = test_user
spring.datasource.password = test_user
spring.datasource.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.schema = import.sql
# MyBatis
mybatis.config-location = mybatis-config.xml
# mybatis.mapper-locations =
# mybatis.type-aliases-package =
# mybatis.type-handlers-package =
# mybatis.executor-type = SIMPLE
# mybatis.configuration =
# JOOQ (JooqAutoConfiguration)
# spring.jooq.sql-dialect=
请你参考一下。
- Appendix A. Common application properties
logback-spring.xml 改写成中文: 《logback-spring.xml》
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_DIR" value="D:/logs" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MMM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{35} - %msg %n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_DIR}/sbcl-example.log</file>
<encoder>
<charset>UTF-8</charset>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] - %msg %n</pattern>
</encoder>
</appender>
<logger name="com.example" level="DEBUG" />
<logger name="org.springframework" level="INFO"/>
<root>
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>
实施的功能
命令行应用程序所需功能
-
- 参数处理
-
- 数据库连接
-
- 多重启动控制
-
- 处理结果的外部输出
- 通知处理
处理参数
-
- Commons CLI
- args4j
如果使用Commons CLI
pom.xml 的中文翻译可以是:“项目对象模型.xml”
<!-- https://mvnrepository.com/artifact/commons-cli/commons-cli -->
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.3.1</version>
</dependency>
如果使用args4j parent
pom.xml的汉语翻译:项目对象模型文件
<!-- https://mvnrepository.com/artifact/args4j/args4j -->
<dependency>
<groupId>args4j</groupId>
<artifactId>args4j</artifactId>
<version>2.33</version>
</dependency>
数据库连接
-
- Spring JDBC (spring-boot-starter-jdbc)
-
- MyBatis (mybatis-spring-boot-starter)
-
- JOOQ (spring-boot-starter-jooq)
- JPA (spring-boot-starter-data-jpa)
如果使用Spring JDBC
-
- 使用JDBC进行数据访问
- http://docs.spring.io/spring/docs/current/spring-framework-reference/html/jdbc.html
pom.xml的中文释义是项目对象模型的XML配置文件。
加入spring-boot-starter-jdbc库来建立依赖关系。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
如果要使用MyBatis的话
我的Batis-Spring-Boot-启动器
pom.xml 可以被简述为一个项目的配置文件。
添加mybatis-spring-boot-starter依赖。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
我的batis-config.xml文件
src/java/resources/mybatis-config.xml 的中文同意译文:src/java/resources/mybatis-config.xml。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.example.domain"/>
</typeAliases>
<mappers>
<mapper resource="mapper/CityMapper.xml"/>
<mapper resource="mapper/HotelMapper.xml"/>
</mappers>
</configuration>
导入.sql文件
src/java/resources/import.sql 可在项目的 Java 资源文件夹中找到 import.sql 文件。
drop table if exists city;
drop table if exists hotel;
create table city (
`id` int not null
, `name` varchar(30)
, `state` varchar(6)
, `country` varchar(6)
, primary key(id)
) engine = INNODB;
create table hotel (
`id` int not null
, `city` int not null
, `name` varchar(60)
, `address` varchar(60)
, `zip` varchar(12)
, primary key(id)
) engine = INNODB;
insert into city (id, name, state, country) values (1, 'San Francisco', 'CA', 'US');
insert into city (id, name, state, country) values (2, 'New York City', 'NY', 'US');
insert into city (id, name, state, country) values (3, 'Los Angeles', 'CA', 'US');
insert into city (id, name, state, country) values (4, 'Chicago', 'IL', 'US');
insert into city (id, name, state, country) values (5, 'Houston', 'TX', 'US');
insert into hotel(id, city, name, address, zip) values (1, 1, 'Conrad Treasury Place', 'William & George Streets', '4001');
insert into hotel(id, city, name, address, zip) values (2, 1, 'The Westin St. Francis San Francisco on Union Square', 'Powell Street, Union Square, San Francisco, CA', '335');
insert into hotel(id, city, name, address, zip) values (3, 1, 'The Fairmont San Francisco', 'Mason Street, San Francisco, CA', '950');
地图绘制程序/城市地图绘制程序.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.CityMapper">
<select id="selectCityById" resultType="City">
SELECT * FROM city WHERE id = #{id}
</select>
</mapper>
@Mapper
public interface CityMapper {
City selectCityById(final Long id);
@Select("SELECT * FROM city WHERE state = #{state}")
List<City> findByState(@Param("state") String state);
}
映射器/HotelMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.HotelMapper">
<select id="selectHotelById" resultType="Hotel">
SELECT * FROM hotel WHERE id = #{id}
</select>
<resultMap id="selectByCityIdMap" type="Hotel">
<id property="id" column="ホテルID" />
<result property="city" column="都市ID" />
<result property="name" column="ホテル名" />
<result property="address" column="住所" />
<result property="zip" column="郵便番号" />
</resultMap>
<select id="selectByCityId" resultMap="selectByCityIdMap">
SELECT * FROM hotel WHERE city = #{id}
</select>
</mapper>
@Mapper
public interface HotelMapper {
Hotel selectHotelById(final Long id);
List<Hotel> selectByCityId(final Long cityId);
}
我的蝙蝠信号灯。
Eclipse的插件
如果使用JOOQ
春季起步器-jooq
请帮我将pom.xml文件进行一下语义改写,只需要一个版本即可。
在项目中添加spring-boot-starter-jooq依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jooq</artifactId>
</dependency>
请参考
- SpringBoot : Working with JOOQ
如果使用JPA的话
春季启动器-数据-JPA
pom.xml 的中文释义是什么?
添加spring-boot-starter-data-jpa以满足依赖关系。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
多重启动控制
如果使用FileChannel
处理结果的外部输出
如果要将其输出到Google Sheets的话
Google Sheets API
https://developers.google.com/sheets/
谷歌表格 API
https://developers.google.com/sheets/
pom.xml的中文原生释义是什么?
所需的库文件
-
- google-api-client
-
- google-oauth-client-jetty
- google-api-services-sheets
<!-- https://mvnrepository.com/artifact/com.google.api-client/google-api-client -->
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>1.22.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.oauth-client/google-oauth-client-jetty -->
<dependency>
<groupId>com.google.oauth-client</groupId>
<artifactId>google-oauth-client-jetty</artifactId>
<version>1.22.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.apis/google-api-services-sheets -->
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-sheets</artifactId>
<version>v4-rev21-1.22.0</version>
</dependency>
处理通知
想要定制的是通知处理的“开始、进行、结束、异常发生”等时间和结果,以及常驻型情况下的状态通知等处理。
-
- spring-boot-starter-mail
- Simple Slack API
如果您选择通过电子邮件通知的话(spring-boot-starter-mail),
pom.xml –> 项目对象模型文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
应用程序配置文件
谷歌邮箱
# Email (MailProperties)
spring.mail.default-encoding = UTF-8
spring.mail.host = smtp.gmail.com
#spring.mail.jndi-name= # Session JNDI name. When set, takes precedence to others mail settings.
spring.mail.password = ********** # Google App password
spring.mail.port = 578
#spring.mail.properties.*= # Additional JavaMail session properties.
spring.mail.protocol = smtp
spring.mail.test-connection = false
spring.mail.username = *****.*****.*****@gmail.com # Google account mail address
如果使用Slack进行通知(简单的Slack API)
pom.xml 文件
<!-- https://mvnrepository.com/artifact/com.ullink.slack/simpleslackapi -->
<dependency>
<groupId>com.ullink.slack</groupId>
<artifactId>simpleslackapi</artifactId>
<version>0.6.0</version>
</dependency>
实施代码
Application.java应用程序.java
package com.example;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import com.example.service.ServiceAlpha;
import com.example.service.ServiceBeta;
import com.example.service.ServiceGamma;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@SpringBootApplication
public class Application {
@Autowired
ServiceAlpha alpha;
@Autowired
ServiceBeta beta;
@Autowired
ServiceGamma gamma;
public static void main(String[] args) {
log.debug(">>> call main");
try (ConfigurableApplicationContext ctx = SpringApplication.run(Application.class, args)) {
Application app = ctx.getBean(Application.class);
app.run(args);
}
log.debug("<<< main end");
}
@PostConstruct
public void init() {
log.debug("application init");
}
@PreDestroy
public void destory() {
log.debug("application finish");
}
public void run(final String... args) {
log.debug("application run");
alpha.execute();
beta.execute(args);
gamma.execute();
}
}
服务阿尔法
package com.example.service;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class ServiceAlpha {
@Autowired
private JdbcTemplate jdbc;
public void execute() {
log.debug("*** alpha in ***");
if (jdbc == null) {
throw new RuntimeException("JdbcTemplate is null");
}
Map<String, Object> result = jdbc.queryForMap("select now() as current");
if (result == null) {
log.debug("result null");
return;
}
result.forEach((k,v)->{
log.debug("key:{} value:{}", k, v);
});
log.debug("*** alpha out ***");
}
}
服务测试版
package com.example.service;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class ServiceBeta {
public void execute(final String... args) {
log.debug("*** beta in ***");
if (args!= null && args.length > 0) {
log.debug("UpperCase:{}", StringUtils.join(args, ",").toUpperCase());
}
log.debug("*** beta out ***");
}
}
服务伽马
package com.example.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.example.domain.City;
import com.example.domain.Hotel;
import com.example.mapper.CityMapper;
import com.example.mapper.HotelMapper;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class ServiceGamma {
@Autowired
private CityMapper cityMapper;
@Autowired
private HotelMapper hotelMapper;
public void execute() {
log.debug("*** gamma in ***");
City city = cityMapper.selectCityById(1L);
log.debug("city:{}", city);
List<City> cityList = cityMapper.findByState("CA");
if (cityList != null) {
cityList.forEach(c -> {
log.debug("city:{}",c);
});
} else {
log.debug("city list is null");
}
Hotel hotel = hotelMapper.selectHotelById(1L);
log.debug("hotel:{}", hotel);
List<Hotel> hotelList = hotelMapper.selectByCityId(1L);
if (hotelList != null) {
hotelList.forEach(h -> {
log.debug("hotel:{}",h);
});
} else {
log.debug("hotel list is null");
}
log.debug("*** gamma out ***");
}
}
构建和执行
在项目的根目录下执行以下的mvn命令。
> mvn clean package
...省略...
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ sbcl-example ---
[INFO] Building jar: D:\dev\eclipse-jee-neon-workspace\sbcl-example\target\sbcl-example-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:1.4.0.RELEASE:repackage (default) @ sbcl-example ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 8.619 s
[INFO] Finished at: 2016-08-30T21:44:51+09:00
[INFO] Final Memory: 27M/269M
[INFO] ------------------------------------------------------------------------
执行在目标目录下创建的jar文件。
> java -jar .\target\sbcl-example-0.0.1-SNAPSHOT.jar abc xyz
...省略...
DEBUG [main] com.example.Application - application init
INFO [main] o.s.j.e.a.AnnotationMBeanExporter - Registering beans for JMX exposure on startup
INFO [main] com.example.Application - Started Application in 3.771 seconds (JVM running for 4.699)
DEBUG [main] com.example.Application - application run
DEBUG [main] com.example.service.ServiceAlpha - *** alpha in ***
DEBUG [main] com.example.service.ServiceAlpha - key:current value:2016-08-30 21:46:11.0
DEBUG [main] com.example.service.ServiceAlpha - *** alpha out ***
DEBUG [main] com.example.service.ServiceBeta - *** beta in ***
DEBUG [main] com.example.service.ServiceBeta - UpperCase:ABC,XYZ
DEBUG [main] com.example.service.ServiceBeta - *** beta out ***
DEBUG [main] com.example.Application - application finish
DEBUG [main] com.example.Application - <<< main end