非 WEB 环境下运行 SpringBootApplication
非 WEB 环境下运行 SpringBootApplication
前言
有时候一些项目并不需要提供 Web 服务,例如跑定时任务的项目等。因为启动一个 Tomcat 这样的 WEB 服务器容器也比较消耗资源,浪费内存及算力。 非 WEB 项目可以修改 maven 依赖为:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
当然,不修改也是没有问题的,可以仍旧依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
首次尝试
不过 SpringBoot 的启动类的 main 方法需要做一些改变
package com.sample.api;
import com.sample.api.entity.User;
import com.sample.api.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.annotation.Resource;
@SpringBootApplication
@EnableTransactionManagement
@EnableCaching
@MapperScan("com.sample.api.mapper")
@Slf4j
public class CmdApplication implements ApplicationRunner {
@Resource
private UserService userService;
public static void main(String[] args) {
new SpringApplicationBuilder(CmdApplication.class)
.web(WebApplicationType.NONE) // .REACTIVE, .SERVLET
//.bannerMode(Banner.Mode.OFF)
.run(args);
log.info("运行完成!");
}
@Override
public void run(ApplicationArguments args) throws Exception {
User user = userService.findById("1");
log.info("user={}",user);
}
}
重要的内容
- 要实现 ApplicationRunner 接口
- 要定制应用启动方式 WebApplicationType.NONE
注意: 此种方式运行完成后应用不会立即退出应用
这离我们的需求有一点差距,我们希望独立运行的非 WEB 环境下的 Application 能够在运行完成后自动退出,而不是一直在运行。
改进之法
将 SpringBootApplication 抽象出来 App.java
package com.sample.api;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement
@EnableCaching
@MapperScan("com.sample.api.mapper")
public class App {
}
之后,新增两个入口启动类: WebApplication.java 和 CmdApplication.java
WebApplication.java 用于启动带有 WEB Server 的微服务
package com.sample.api;
import org.springframework.boot.SpringApplication;
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
CmdApplication.java 用于启动命令行程序
package com.sample.api;
import com.sample.api.entity.User;
import com.sample.api.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
@Slf4j
public class CmdApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(App.class).web(WebApplicationType.NONE).run(args);
UserService userService = context.getBean(UserService.class);
User user = userService.findById("3");
log.info("user={}",user);
log.info("运行完成!");
System.exit(0);//使用强制退出来终止SpringBootApplication应用
}
}
如此调整之后,就完美无瑕了!^_^
进一步改良
App.java
在抽象出来的 App.java 增加一些辅助的命令行相关的静态方法,供命令行程序调用。
package com.sample.api;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.util.function.Consumer;
@SpringBootApplication
@EnableTransactionManagement
@EnableCaching
@MapperScan("com.sample.api.mapper")
public class App {
private static ConfigurableApplicationContext context = null;
public static synchronized ConfigurableApplicationContext start(String[] args) {
if (context == null) {
context = new SpringApplicationBuilder(App.class).web(WebApplicationType.NONE).run(args);
}
return context;
}
public static synchronized void stop() {
System.exit(0);
}
public static <T> T getBean(Class<T> bean) {
ConfigurableApplicationContext context = start(new String[]{});
return context.getBean(bean);
}
public static void exec(String[] args, Consumer<ConfigurableApplicationContext> consumer) {
ConfigurableApplicationContext context = start(args);
consumer.accept(context);
stop();
}
public static void exec(Consumer<ConfigurableApplicationContext> consumer) {
exec(new String[]{}, consumer);
}
}
WebApplication.java
启动 WEB 微服务的入口类就变成这个样子啦!
package com.sample.api;
import org.springframework.boot.SpringApplication;
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
CmdApplication.java
那么命令行的启动非 WEB 的入口类就是此类形式,应对一般需求的,例如本地运行导入数据等程序,就可以以此为样板代码进行。
package com.sample.api;
import com.sample.api.entity.User;
import com.sample.api.service.UserService;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CmdApplication {
// 进行测试,以后的单个应用,比如临时用于倒入数据,执行某个服务逻辑都可以以此为样板代码
private static void test() {
//App.exec((context)->{
UserService userService = App.getBean(UserService.class);
User user = userService.findById("3");
String id="4";
String name="新加入";
userService.createNewUser(id,name);
log.info("user={}", user);
log.info("运行完成!");
//});
}
public static void main(String[] args) {
App.start(args);
try {
test();
}finally {
App.stop();
}
}
}