.NET CORE与Spring Boot编写控制台程序应有的优雅姿势
本文分别说明.net core与spring boot 编写控制台程序应有的“正确”方法,以便.net程序员、java程序员可以相互学习与加深了解,注意本文只介绍用法,不会刻意强调哪种语言或哪种框架写的控制台程序要好。
本文所说的编写控制台程序应有的“正确”方法,我把正确二字加上引号,因为没有绝对的正确,因人而异,因系统设计需求而异,我这里所谓的正确方法是指使用面向对象,依赖注入ioc,切面控制aop等编码规范来提升程序的性能、整洁度、可读性、可维护性等,最终达到让人感觉有点高大上,有点优雅的样子。
先来说说.net core编写控制台程序,目前网络上大把的讲解asp.net core的编写规范,反而对于.net core控制台程序编写规范介绍比较少,大多停留在hello word 程序中,而本文则来讲讲.net core控制台的编写规范(应有的优雅姿势)^ v ^
如果说不讲什么ioc,di,aop等,不讲扩展性,规范性,全部面向过程(方法)编程,那估计没什么好讲的,因为无非就是定义一个class,然后在class中定义一堆的method(方法),如果在方法中需要使用到其它第三方组件,则直接单独引用,引用后进行简单封装util工具类的静态方法,甚至也不用封装,直接使用原生的方法,总之全部都是方法调方法。而这里所演示的编写控制台方法均是尽可能的使用.net core所具有的特性,只有这样才能体现出.net core框架的优势,否则普通控制台程序与.net core控制台程序有什么区别。
编写.net core控制台程序优雅姿势一:(直接使用.net core的 ioc、logging、config组件)
代码如下:
//program.cs
using microsoft.extensions.dependencyinjection;
using system;
using microsoft.extensions.logging;
using microsoft.extensions.configuration.json;
using microsoft.extensions.configuration;
using system.io;
namespace netcoreconsoleapp
{
class program
{
static void main(string[] args)
{
//设置config文件
var config = new configurationbuilder()
.setbasepath(directory.getcurrentdirectory())
.addjsonfile("appsettings.json", optional: true, reloadonchange: true).build();
//设置依赖注入
var provider = new servicecollection()
.addlogging(configlogging => //设置日志组件
{
configlogging.setminimumlevel(loglevel.information);
configlogging.addconsole();
})
.addscoped<iconfiguration>(p => config)
.addscoped<hostservice>()
.buildserviceprovider();
var hostservice = provider.getservice<hostservice>();
hostservice.runasync();//统一入口服务
console.writeline("提示:程序已正常启动运行,按任意键停止运行并关闭程序...");
console.readline();
}
}
}
//hostservice.cs
using microsoft.extensions.configuration;
using microsoft.extensions.logging;
using system;
using system.diagnostics;
using system.threading;
using system.threading.tasks;
namespace netcoreconsoleapp
{
public class hostservice
{
private readonly iconfiguration config;
private readonly ilogger<hostservice> logger;
public hostservice(iconfiguration config, ilogger<hostservice> logger)
{
this.config = config;
this.logger = logger;
}
public void runasync()
{
task.run((action)execute);
}
/// <summary>
/// 控制台核心执行入口方法
/// </summary>
private void execute()
{
//todo 业务逻辑代码,如下模拟
stopwatch stopwatch = stopwatch.startnew();
for (int i = 1; i <= 100; i++)
{
console.writeline("test writeline:" + i);
thread.sleep(100);
}
stopwatch.stop();
logger.loginformation("logging - execute elapsed times:{}ms", stopwatch.elapsedmilliseconds);
}
}
}
因为要使用.net core相关核心组件,故需要引用相关的nuget包(引用包的方式有多种方式),而且默认的.net core控制台只会生成dll并不会生成exe启动程序,故如果仅在win系统下使用,还需要设置生成方式等,详细配置属性如下:(项目文件csproj)
<project sdk="microsoft.net.sdk"> <propertygroup> <outputtype>exe</outputtype> <targetframework>netcoreapp2.2</targetframework> <runtimeidentifiers>win10-x64</runtimeidentifiers> <selfcontained>false</selfcontained> </propertygroup> <itemgroup> <packagereference include="microsoft.extensions.configuration.json" version="2.2.0" /> <packagereference include="microsoft.extensions.dependencyinjection" version="2.2.0" /> <packagereference include="microsoft.extensions.logging.console" version="2.2.0" /> </itemgroup> </project>
如上代码虽简单但代码编写顺序很关键,这里进行说明一下:
1.因为一般应用程序都会有config文件,故我们需要先通过new configurationbuilder来设置config文件的方式及路径;
2.因为要使用.net core默认的ioc框架,故new servicecollection,然后将相关的依赖服务组件注册到ioc容器中;
3.config、logging 均是一个程序最基本的依赖组件,故将其注册到ioc容器中,注册logging有专门的扩展方法(addlogging),而config没有则直接使用通过的注册方法(当然也可以基于servicecollection写一个addconfiguration扩展方法)
4.控制台需要一个核心的入口方法,用于处理核心业务,不要直接在program中写方法,这样就不能使用ioc,同时也没有做到职责分明,program仅是程序启动入口,业务处理应该有专门的入口,故上述代码中有hostservice类(即:核心宿主服务类, 意为存在于控制台中的服务处理类,在这个类的构造涵数中列出所需依赖的服务组件,以便实例化时ioc可以自动注入这个参数),并注册到ioc容器中,当然也可以先定义一个ihostservice接口然后实现这个接口。(如果有多个hostservice类实例,建议定义一个ihostservice接口,接口中只需要入口方法定义即可,如:runasync)
5.当各组件初始化设置ok、ioc注册到位后,就应该通过ioc解析获得hostservice类实例,并执行入口方法:runasync,该方法为异步后台执行,即调用该方法后,会在单独的后台线程处理核心业务,然后主线程继续往下面走,输出关闭提示信息,最后的console.readline();很关键,这个是等待输入流并挂起当前主线程,目的大家都知道,不要让控制台程序关闭。
通过上述的讲解及源代码展示,有没有感觉优雅呢?如果觉得这样还算优雅,那下面展示的第二种更优雅的姿势
编写.net core控制台程序优雅姿势二:(使用通用主机也称泛型主机hostbuilder)
代码如下:program.cs
using microsoft.extensions.dependencyinjection;
using microsoft.extensions.hosting;
using microsoft.extensions.logging;
using nlog.extensions.logging;
using microsoft.extensions.configuration;
using system.io;
using polly;
using system;
namespace netcoreconsoleapp
{
class program
{
static void main(string[] args)
{
var host = new hostbuilder()
.configurehostconfiguration(confighost =>
{
confighost.setbasepath(directory.getcurrentdirectory());
})
.configureappconfiguration(configapp =>
{
configapp.addjsonfile("appsettings.json", optional: false, reloadonchange: true);
})
.configureservices((context, services) =>
{
//添加数据访问组件示例:services.addtransient<idbaccesser>(provider =>
//{
// string connstr = context.configuration.getconnectionstring("conndbstr");
// return new sqldappereasyutil(connstr);
//});
//添加httpclient封装类示例:services.addhttpclient<githubapiclient>()
//.addtransienthttperrorpolicy(builder => builder.waitandretryasync(3, t => timespan.frommilliseconds(800)));
services.addhostedservice<demohostedservice>();
})
.configurelogging((context, configlogging) =>
{
configlogging.clearproviders();
configlogging.setminimumlevel(loglevel.trace);
configlogging.addnlog(context.configuration);
})
.useconsolelifetime()
.build();
host.run();
}
}
}
demohostedservice类代码:
using microsoft.extensions.configuration;
using microsoft.extensions.hosting;
using microsoft.extensions.logging;
using system;
using system.diagnostics;
using system.threading;
using system.threading.tasks;
namespace netcoreconsoleapp
{
public class demohostedservice : ihostedservice
{
private readonly iconfiguration config;
private readonly ilogger logger;
public demohostedservice(iconfiguration config, ilogger<demohostedservice> logger)
{
this.config = config;
this.logger = logger;
}
public task startasync(cancellationtoken cancellationtoken)
{
console.writeline(nameof(demohostedservice) + "已开始执行...");
//todo 业务逻辑代码,如下模拟
stopwatch stopwatch = stopwatch.startnew();
for (int i = 1; i <= 100; i++)
{
console.writeline("test writeline:" + i);
thread.sleep(100);
}
stopwatch.stop();
logger.loginformation("logging - execute elapsed times:{}ms", stopwatch.elapsedmilliseconds);
return task.fromresult(0);
}
public task stopasync(cancellationtoken cancellationtoken)
{
console.writeline(nameof(demohostedservice) + "已被停止");
return task.fromresult(0);
}
}
}
因为要使用hostbuilder类及相关的.net core组件(如上代码主要使用到了:host、dapper、nlog、polly等),故仍需引用相关的nuget包,详细配置属性如下:(项目文件csproj)
<project sdk="microsoft.net.sdk"> <propertygroup> <outputtype>exe</outputtype> <targetframework>netcoreapp2.2</targetframework> <runtimeidentifiers>win10-x64</runtimeidentifiers> <selfcontained>false</selfcontained> </propertygroup> <itemgroup> <packagereference include="dapper" version="1.60.6" /> <packagereference include="microsoft.extensions.configuration.fileextensions" version="2.2.0" /> <packagereference include="microsoft.extensions.configuration.json" version="2.2.0" /> <packagereference include="microsoft.extensions.hosting" version="2.2.0" /> <packagereference include="microsoft.extensions.http.polly" version="2.2.0" /> <packagereference include="nlog.extensions.logging" version="1.5.1" /> <packagereference include="system.collections.concurrent" version="4.3.0" /> </itemgroup> <itemgroup> <none update="appsettings.json"> <copytooutputdirectory>preservenewest</copytooutputdirectory> </none> <none update="nlog.config"> <copytooutputdirectory>preservenewest</copytooutputdirectory> </none> </itemgroup> </project>
如上代码所示,写过asp.net core程序的人可能比较眼熟,这与asp.net core的写法很类似,是的,你没有看错,hostbuilder是通用主机,是可以广泛应用于非http的环境下,而asp.net core中的webhostbuilder 主要用于http web环境,使用方式基本类似,都是先定义hostbuilder,然后利用扩展方法注册、配置各种组件(中间件),最后调用host的run方法,开启后台服务执行,不同的是webhostbuilder多了属于http专有的一些属性及方法及其适用的中间件。
由于这种写法比较通用,适用于已熟悉.net core或asp.net core的人群,上手也较简单,故建议采取这种方式来写.net core控制台程序。需要注意的是hostbuilder中最重要的是:注册hostedservice 服务,如上代码中的demohostedservice即是实现了ihostedservice接口的宿主后台服务类,可以定义多个,然后都注册到ioc中,最后host会按注册先后顺序执行多个hostedservice服务的startasync方法,当停止时同样会执行多个hostedservice服务的stopasync方法
下面再来看看使用spring&spring boot框架来优雅的编写控制台程序
编写spring控制台程序优雅姿势一:(只引用所必需的spring jar包、logger jar包,追求极简风)
使用idea +maven 创建一个quickstart 控制台项目,在maven pom xml中先引用所必需的spring jar包、logger jar包等,配置如下:
<?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> <artifactid>spring-console</artifactid> <name>spring-console</name> <!-- fixme change it to the project's website --> <url>http://www.zuowenjun.cn</url> <properties> <project.build.sourceencoding>utf-8</project.build.sourceencoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <spring.version>5.1.8.release</spring.version> </properties> <dependencies> <dependency> <groupid>junit</groupid> <artifactid>junit</artifactid> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-core</artifactid> <version>${spring.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-beans</artifactid> <version>${spring.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-context</artifactid> <version>${spring.version}</version> </dependency> <dependency> <groupid>org.slf4j</groupid> <artifactid>slf4j-api</artifactid> <version>1.7.25</version> </dependency> <dependency> <groupid>ch.qos.logback</groupid> <artifactid>logback-classic</artifactid> <version>1.2.3</version> </dependency> </dependencies> <build> <pluginmanagement><!-- lock down plugins versions to avoid using maven defaults (may be moved to parent pom) --> <plugins> <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_lifecycle --> <plugin> <artifactid>maven-clean-plugin</artifactid> <version>3.1.0</version> </plugin> <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#plugin_bindings_for_jar_packaging --> <plugin> <artifactid>maven-resources-plugin</artifactid> <version>3.0.2</version> </plugin> <plugin> <artifactid>maven-compiler-plugin</artifactid> <version>3.8.0</version> </plugin> <plugin> <artifactid>maven-surefire-plugin</artifactid> <version>2.22.1</version> </plugin> <plugin> <artifactid>maven-jar-plugin</artifactid> <version>3.0.2</version> </plugin> <plugin> <artifactid>maven-install-plugin</artifactid> <version>2.5.2</version> </plugin> <plugin> <artifactid>maven-deploy-plugin</artifactid> <version>2.8.2</version> </plugin> <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_lifecycle --> <plugin> <artifactid>maven-site-plugin</artifactid> <version>3.7.1</version> </plugin> <plugin> <artifactid>maven-project-info-reports-plugin</artifactid> <version>3.0.0</version> </plugin> </plugins> </pluginmanagement> <plugins> <plugin> <groupid>org.apache.maven.plugins</groupid> <artifactid>maven-compiler-plugin</artifactid> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> </plugins> </build> </project>
然后采取自定义注解类(springbeansconfig)的方式注册相关bean(包含配置映射类bean:appproperties),代码如下:
//app.java
package cn.zuowenjun.spring;
import cn.zuowenjun.spring.cn.zuowenjun.spring.services.hostservice;
import org.springframework.context.annotation.annotationconfigapplicationcontext;
import java.io.ioexception;
/**
* hello world!
*/
public class app {
public static void main(string[] args) {
annotationconfigapplicationcontext applicationcontext = new annotationconfigapplicationcontext(springbeansconfig.class);
hostservice hostservice = applicationcontext.getbean(hostservice.class);
hostservice.run();
applicationcontext.registershutdownhook();
try {
system.in.read();
} catch (ioexception e) {
system.out.println("等待读取输入数据报错:" + e.getmessage() + ",将直接退出程序!");
}
}
}
//appproperties.java
package cn.zuowenjun.spring;
import org.springframework.beans.factory.annotation.value;
public class appproperties {
@value("${app.name}")
private string appname;
@value("${app.author}")
private string appauthor;
@value("${app.test.msg}")
private string testmsg;
public string getappname() {
return appname;
}
public void setappname(string appname) {
this.appname = appname;
}
public string getappauthor() {
return appauthor;
}
public void setappauthor(string appauthor) {
this.appauthor = appauthor;
}
public string gettestmsg() {
return testmsg;
}
public void settestmsg(string testmsg) {
this.testmsg = testmsg;
}
}
//springbeansconfig.java
package cn.zuowenjun.spring;
import cn.zuowenjun.spring.cn.zuowenjun.spring.services.hostservice;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.context.annotation.propertysource;
import org.springframework.context.annotation.scope;
import org.springframework.core.annotation.order;
@configuration
@propertysource(value = "classpath:app.properties", ignoreresourcenotfound = false)
public class springbeansconfig {
@bean
@order(1)
public hostservice hostservice() {
return new hostservice();
}
@bean
@order(0)
@scope("singleton")
public appproperties appproperties() {
return new appproperties();
}
//注册其它所需bean...
}
//hostservice.java
package cn.zuowenjun.spring.cn.zuowenjun.spring.services;
import cn.zuowenjun.spring.appproperties;
import org.slf4j.logger;
import org.slf4j.loggerfactory;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.util.stopwatch;
import java.util.collections;
import java.util.concurrent.executorservice;
import java.util.concurrent.executors;
public class hostservice {
private static final logger logger = loggerfactory.getlogger(hostservice.class);
@autowired
private appproperties appproperties;
//可以添加其它属性注入
public void run() {
// executorservice pool = executors.newsinglethreadexecutor();
// pool.execute(() -> execute());
new thread(this::execute).start();
}
/// <summary>
/// 控制台核心执行入口方法
/// </summary>
private void execute() {
//todo 业务逻辑代码,如下模拟
stopwatch stopwatch = new stopwatch();
stopwatch.start();
for (int i = 1; i <= 100; i++) {
system.out.println("test writeline:" + i);
try {
thread.sleep(100);
} catch (exception e) {
}
}
stopwatch.stop();
system.out.println(string.join("", collections.ncopies(30, "=")));
system.out.printf("app name is:%s %n", appproperties.getappname());
system.out.printf("app author is:%s %n", appproperties.getappauthor());
system.out.printf("app test msg:%s %n", appproperties.gettestmsg());
logger.info("logging - execute elapsed times:{}ms", stopwatch.gettotaltimemillis());
}
}
app.properties配置文件内容如下,注意应放在classpth目录下(即:resources目录下,没有需自行创建并设为resources目录):
app.name=demo spring console
app.author=zuowenjun
app.test.msg=hello java spring console app!
如上即上实现一个spring的控制台程序,当然由于是示例,故只引用了logger包,正常还需引用jdbc或orm框架的相关jar包, 上述代码关键逻辑说明(同样要注意顺序):
1.new annotationconfigapplicationcontext类(spring ioc容器),创建一个ioc容器,类似.net core中的serviceprovider类;
2.定义 springbeansconfig bean注册配置类(注册相关依赖),这个类中依次注入相关的bean,如果bean之间有依赖顺序关系,建议添加@order并指明序号;该类作为annotationconfigapplicationcontext的构造函数参数传入,以便ioc自动解析并完成实际注册;
3.同样是定义一个hostservice 宿主服务类,并实现run方法逻辑,一般采取后台线程异步执行,为了演示效果与.net core的hostservice 类相同,示例逻辑基本相同。另外还定义了appproperties配置映射类,便于直接读取配置,.net core同样也有类似注册bind到配置类中,然后在服务类中使用:ioptions<配置类>作为构造函数参数实现构造函数注入。只是由于篇幅有限故.net core部份直接采取了注入iconfiguration,大家有兴趣可以查看网上相关资料。
4.ioc容器初始化并注册成功后,即可解析hostservice 类获得实例,执行run方法,run方法会开启线程在后台处理,并返回到主线程,直至in.read()阻塞挂起主线程,防止程序自动关闭。
编写spring boot控制台程序优雅姿势二:(引用spring boot jar包)
使用idea+spring initializr来创建一个spring boot项目,创建过程中按需选择依赖的框架,我这里是示例,故除了默认spring-boot-starter依赖外,其余什么依赖都不添加,创建后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.6.release</version> <relativepath/> <!-- lookup parent from repository --> </parent> <groupid>cn.zuowenjun.spring</groupid> <artifactid>springboot-console</artifactid> <version>0.0.1-snapshot</version> <name>springboot-console</name> <description>demo project for spring boot</description> <url>http://www.zuowenjun.cn</url> <properties> <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-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>
然后创建相关的bean类:hostservice(宿主服务类,这个与前文定义类均相同)、appproperties(配置映射类,这个是映射默认的application.properties配置文件,注意这里的映射方式与前文所描述稍有不周,采用:@configurationproperties+属性映射,无需加@value注解,映射属性时如果有-则应写成驼峰式,如果有.则应定义内部静态类,呈现层级属性完成映射,具体的用法可以参见我之前的文章):
//hostservice.java
package cn.zuowenjun.spring.services;
import cn.zuowenjun.spring.appproperties;
import org.slf4j.logger;
import org.slf4j.loggerfactory;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.component;
import org.springframework.util.stopwatch;
import java.util.collections;
@component
public class hostservice {
private static final logger logger = loggerfactory.getlogger(hostservice.class);
@autowired
private appproperties appproperties;
//可以添加其它属性注入
public void run() {
// executorservice pool = executors.newsinglethreadexecutor();
// pool.execute(() -> execute());
new thread(this::execute).start();
}
/// <summary>
/// 控制台核心执行入口方法
/// </summary>
private void execute() {
//todo 业务逻辑代码,如下模拟
stopwatch stopwatch = new stopwatch();
stopwatch.start();
for (int i = 1; i <= 100; i++) {
system.out.println("test writeline:" + i);
try {
thread.sleep(100);
} catch (exception e) {
}
}
stopwatch.stop();
system.out.println(string.join("", collections.ncopies(30, "=")));
system.out.printf("app name is:%s %n", appproperties.getname());
system.out.printf("app author is:%s %n", appproperties.getauthor());
system.out.printf("app test msg:%s %n", appproperties.gettestmsg());
logger.info("logging - execute elapsed times:{}ms", stopwatch.gettotaltimemillis());
}
}
//appproperties.java
package cn.zuowenjun.spring;
import org.springframework.boot.context.properties.configurationproperties;
import org.springframework.stereotype.component;
@component
@configurationproperties(prefix = "app")
public class appproperties {
private string name;
private string author;
private string testmsg;
public string getname() {
return name;
}
public void setname(string name) {
this.name = name;
}
public string getauthor() {
return author;
}
public void setauthor(string author) {
this.author = author;
}
public string gettestmsg() {
return testmsg;
}
public void settestmsg(string testmsg) {
this.testmsg = testmsg;
}
}
application.properties配置文件:(注意app.test.msg此处改为了app.test-msg,因为这样就可以直接映射到类的属性中,否则得定义内部类有点麻烦)
app.name=demo spring console
app.author=zuowenjun
app.test-msg=hello java spring console app!
最后改造spring boot application类,让springbootconsoleapplication类实现applicationrunner接口,并在run方法中编写通过属性依赖注入获得hostservice类的实例,最后执行hostservice的run方法即可,代码如下:
package cn.zuowenjun.spring;
import cn.zuowenjun.spring.services.hostservice;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.boot.applicationarguments;
import org.springframework.boot.applicationrunner;
import org.springframework.boot.springapplication;
import org.springframework.boot.autoconfigure.springbootapplication;
@springbootapplication
public class springbootconsoleapplication implements applicationrunner {
@autowired
private hostservice hostservice;
public static void main(string[] args) {
springapplication.run(springbootconsoleapplication.class, args);
}
@override
public void run(applicationarguments args) throws exception {
hostservice.run();
}
}
如上步骤即完成了优雅编写spring boot控制台程序,关键点是applicationrunner,这个是给spring boot执行的入口,另一种思路,我们其实还可以把hostservice类改造一下,让其实现applicationrunner接口,那么run方法即为spring boot的启动入口。
总结一下:.
net core控制台程序优雅姿势一与spring控制台优雅姿势一核心思想是一样的,都是手动创建各个依赖组件及ioc容器的实例,都是通过ioc容器显式的解析获得hostservice类的实例,最后运行hostservice#run方法。
net core控制台程序优雅姿势二与spring控制台优雅姿势二核心思想也是一样的,都是利用ioc容器来直接管理注册的各个依赖组件,并由.net core、spring boot框架自行调度hostservice#run方法。
我个人更倾向优雅姿势二的方法来编写.net core或spring boot的控制台程序,因为写得更少,做得更多。
上一篇: 浅谈IHttpHandler
下一篇: 月经吃香蕉会有影响吗