从seasar2迁移到spring boot2系
请在别的网站上搜索。
由于之前写过的内容已经在某个时间结束了,所以我转移到了这篇第6篇文章。这是2019年10月1日的内容。
从seasar2迁移到spring boot2
由于工作需要,我将seasar2迁移到spring boot,并将其内容记录下来。
当然,很多内容都被改写了。
过渡期前的配置。
-
- seasar2
-
- SAStruts
-
- Doma2(ORM)
- velocity(テンプレートエンジン)
在Gradle的多项目中,将这些项目分开使用。
-
- build.gradle(親プロジェクト)
web/build.gradle(アプリケーションからアクセスする層)
admin/build.gradle(会社側から触る管理用ページの層)
core/build.gradle(データとの接続を行う層)
batch/build.gradle(定期バッチ処理用の層)
几年来,我们一直在使用类似上述的方案。
立即采取行动
dicon文件
让我们抹掉它,毫无疑问地。
修改 build.gradle 文件
只需要一种选择的话,以下是对该句子的汉语本地化改写:
只改变了一些地方。
buildscript {
ext {
- seasarVersion = '2.4.48'
+ springBootVersion = '2.1.1.RELEASE'
+ profile = project.hasProperty('profile') ? project[ 'profile' ] : 'localhost'
}
repositories {
+ gradlePluginPortal()
}
dependencies {
+ classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
plugins {
+ id 'org.springframework.boot' version '2.1.1.RELEASE'
}
subprojects {
+ apply plugin: 'io.spring.dependency-management'
repositories {
+ gradlePluginPortal()
}
dependencies {
- // seasar
- implementation "org.seasar.container:s2-framework:${seasarVersion}"
- implementation "org.seasar.container:s2-extension:${seasarVersion}"
- implementation "org.seasar.container:s2-tiger:${seasarVersion}"
+ // spring boot
+ implementation "org.springframework.boot:spring-boot-dependencies:${springBootVersion}"
+ implementation "org.springframework.boot:spring-boot-starter-web:${springBootVersion}"
+ implementation "org.springframework.boot:spring-boot-starter:${springBootVersion}"
+ implementation "org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}"
+ implementation "org.springframework.boot:spring-boot-starter-aop:${springBootVersion}"
+ implementation "org.springframework.data:spring-data-commons:${springBootVersion}"
+ implementation "org.apache.tomcat:tomcat-jdbc:9.0.13"
+ implementation "org.seasar.doma.boot:doma-spring-boot-starter:1.1.1"
+ implementation "org.springframework.boot:spring-boot-devtools:${springBootVersion}"
// web
- implementation "org.seasar.sastruts:sa-struts:1.0.4-sp9"
- implementation "struts:struts:1.2.9"
- implementation "org.apache.velocity:velocity:1.7"
- implementation "org.apache.velocity:velocity-tools:2.0"
+ implementation "org.springframework.boot:spring-boot-starter-thymeleaf:${springBootVersion}"
+ implementation "net.sourceforge.nekohtml:nekohtml:1.9.22"
- implementation "log4j:log4j:1.2.17"
+ implementation "org.springframework.boot:spring-boot-starter-log4j2:${springBootVersion}"
+ implementation "ognl:ognl:3.1.12"
+ implementation "commons-dbutils:commons-dbutils:1.7"
- testImplementation "org.seasar.aptina:aptina-unit:1.0.0"
- testCompile 'junit:junit'
+ testImplementation "org.springframework.boot:spring-boot-starter-test:${springBootVersion}"
+ testImplementation 'junit:junit:4.12'
}
+ configurations.all {
+ exclude module: 'spring-boot-starter-logging'
+ exclude module: "HikariCP"
+ exclude module: "android-json"
+ }
}
删除与seasar2有关的内容,并添加spring boot相关的内容。关于要添加的内容,可以选择项目所需的内容。将需要排除的内容放入configurations.all中。在这一点上,会出现大量错误,让人心灰意冷。
subproject的build.gradle配置文件
+apply plugin: 'org.springframework.boot'
+sourceSets {
+ main {
+ java {
+ srcDir "${rootDir}/core/src/main/java"
+ }
+ resources {
+ srcDir "${rootDir}/core/src/main/resources"
+ }
+ }
+}
dependencies {
+ implementation "org.springframework.boot:spring-boot-starter-security:2.1.1.RELEASE"
+ implementation "org.springframework.boot:spring-boot-starter-actuator:2.1.1.RELEASE"
+ implementation "de.codecentric:spring-boot-admin-starter-client:2.1.1"
}
+ springBoot {
+ mainClassName = "${project.group}.admin.Application"
+ }
+ bootRun {
+ systemProperties = ['spring.profiles.active': "${profile}"]
+ }
+ bootJar {
+ mainClassName = "${project.group}.admin.Application"
+ launchScript()
+ }
加入spring boot的設定。
由於使用core來進行web等操作,所以需要設定sourceSets。
至於spring security和spring actuator,則根據個人喜好選擇。
如果不需要使用,就沒有特別需要加入的必要。
创建应用程序
@EnableAsync
@SpringBootApplication(exclude = {
ManagementWebSecurityAutoConfiguration.class,
SecurityAutoConfiguration.class,
UserDetailsServiceAutoConfiguration.class,
SecurityFilterAutoConfiguration.class
})
@ComponentScan(basePackages = "[パッケージのroot]")
public class Application {
static {
Security.setProperty("networkaddress.cache.ttl", "60");
Security.setProperty("networkaddress.cache.negative.ttl", "10");
}
public static void main(String[] args) throws Exception {
System.setProperty("server.servlet.context-path", "/admin");
SpringApplication springApplication = new SpringApplication(Application.class);
Properties properties = new Properties();
properties.setProperty("spring.profiles.active", "localhost");
properties.setProperty("spring.datasource.initialization-mode", "NEVER");
properties.setProperty("spring.resources.add-mappings", "true");
springApplication.setDefaultProperties(properties);
springApplication.run(args);
}
}
安全性を追加したが、使用しているURLが誤って不正とされてしまいましたので、URLを修正するのは少し困難な状況でしたので、結局除外して無効にしました。意味がありません…
キャッシュの設定やコンテキストパスの設定、その他のプロパティの設定などを行います。
各种动作的响应
@Controller
@RequiredArgsConstructor
@RequestMapping("")
对于类的注解
使用@Controller来指定seasar2中的action
@RequiredArgsConstructor是lombok的一个注解,但由于构造器注入很方便,所以进行设置
通过@RequestMapping来进行URL的设置
顺便说一下,即使在@RestController中基本上也是一样的。
- @Resource
- private LoginService loginService;
+ private final LoginService loginService;
以下是DI(依赖注入)的更改示例。通过将其设为private final,使其成为构造函数注入的目标。
- @Resource
- @ActionForm
- public LoginForm loginForm;
+ @ModelAttribute
+ LoginForm setUpForm() {
+ return new LoginForm();
+ }
有很多种方法可以设置表单,但我觉得使用@ModelAttribute是最容易理解的。
- public Boolean notLoginFlg;
- @Execute(validator= false)
- public String index() {
- notLoginFlg = null;
- return "index.html";
- }
+ @RequestMapping("/")
+ public String index(ModelMap model) {
+ model.put("notLoginFlg", null);
+ return "index";
+ }
使用不同的注释方法和ModelMap存储数据的动作方法。
@Controller
@RequiredArgsConstructor
@RequestMapping("")
public class IndexAction {
private final LoginService loginService;
@ModelAttribute
HogeForm setUpForm() {
return new HogeForm();
}
@RequestMapping("/")
public String index(HttpServletRequest request, HttpServletResponse response, HogeForm hogeForm, ModelMap model) {
model.put("notLoginFlg", null);
return "index";
}
}
结果就是这种感觉
顺便一提,如果把HttpServletRequest和HttpServletResponse作为参数输入,它们会帮你传递数据,很方便。
用这种方式来改变DI配置、表单和操作定义的写法。
使用@RequiredArgsConstructor注解,会为final字段生成构造函数,因此可以实现对目标字段的构造函数注入,使代码更加清晰。
(我记得在searsar2中,默认构造函数不存在,因此无法进行构造函数注入,我曾经放弃过。)
应用程序中,采用将变量传递给屏幕端的构成方式,需要将其设置到MedelMap等类中,因此在这个过程中变得非常麻烦。(我也有同样的经历,非常辛苦。)
当使用path作为变量时
@RequestMapping(value = "/{hoge}/{huga}")
public String index(@PathVariable String hoge, @PathVariable String huga) {
return "index";
}
通过使用@PathVariable可以获取
当使用@Controller时,想要返回JSON时。
@RequestMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String index() {
return "{}";
}
通过使用 produces = MediaType.APPLICATION_JSON_VALUE 和 @ResponseBody,即使在 @Controller 中也可以返回 JSON。
由于我在 @Controller 中执行页面跳转并在同一个 action 类中处理 ajax 请求,所以需要这样做。
会话信息存储类
- @Component(instance = InstanceType.SESSION)
+ @Component
+ @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
只要更改标注就可以了。
速度 -> 时光叶
春天5以后,与Velocity协作的类被删除了,导致Velocity无法正常运行,所以迁移起来有些困难。
如果不擅长操作,就没必要勉强自己去做。
由于需要将HTML调整为正确的语法,这个步骤可以省略。
存储文件夹似乎从webapp变为resources/static和resources/templates等。
这方面可以参考Thymeleaf的官方文档。
基本而言,HTML的表现应该可以实现相同的效果。
只是由于HTML文件较多,实际工作中有一半的时间都花在这上面,真的很让人沮丧。
如果在Velocity中使用了自定义宏函数的情况下
如果我们能够创建与thymeleaf相同的东西,就能够解决这个问题。
宏处理
public class ThymeleafUtils {
public boolean hoge() {
return true;
}
}
我将在某处创建相同的宏。你也可以直接使用之前的宏。
创建一个宏调用类
@Component
public class ThymeleafDialect implements IExpressionObjectDialect {
static final String KEY = "呼び出す時のキー名";
final Set<String> names = new HashSet<String>() {
{
add(KEY);
}
};
@Override
public IExpressionObjectFactory getExpressionObjectFactory() {
return new IExpressionObjectFactory() {
@Override
public Set<String> getAllExpressionObjectNames() {
return names;
}
@Override
public Object buildObject(IExpressionContext context, String expressionObjectName) {
if (KEY.equals(expressionObjectName)) { //名前が一致したなら
return new ThymeleafUtils(); // マクロを返す
}
return null;
}
@Override
public boolean isCacheable(String expressionObjectName) {
return true;
}
};
}
@Override
public String getName() {
return "ThymeleafUtilsDialect";
}
}
使用IExpressionObjectDialect并指定特定的键时,应使用已设置宏的ThymeleafUtils。
注册到模板引擎
@Configuration
public class ThymeleafConfig {
@Autowired
private ThymeleafDialect thymeleafDialect;
@Bean
public TemplateEngine templateEngine(TemplateEngine templateEngine) {
templateEngine.addDialect(thymeleafDialect);
return templateEngine;
}
}
使用之前所创建的模板引擎配置。这样就可以在Thymeleaf上使用了。
${#キー名.hoge()}
应该可以在诸如这样的地方使用。
面向切面编程
转移拦截器处理
public class HogeInterceptor extends AbstractInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return invocation.proceed();
}
}
在seasar2中,我认为可以将其定义在customizer.dicon文件中,并以类似的方式来实现。
移行后的感觉如下。我们将在Java内编写拦截条件等。
在以下示例中,当经过与action包相关的处理时,将进行拦截。
如果不需要Order,但想要插入多个拦截器并控制顺序,建议使用它。
@Order(10)
@Component
@Aspect
public class HogeInterceptor {
@Around("execution(* *..*Action.*(..))")
public Object invoke(ProceedingJoinPoint jp) throws Throwable {
MethodSignature signature = (MethodSignature) jp.getSignature();
Method method = signature.getMethod();
return jp.proceed();
}
}
经常用到的Method和Class<?>很方便快速获取。
单例S2容器
- SingletonS2Container.getComponent("hogehoge");
+ ApplicationContextProvider.getApplicationContext().getBean(HogeHoge.class);
由于SingletonS2Container不再存在,所以需要从Spring容器中获取信息来进行更改。
当从jar文件中调用时
如果原始的形式是在Java中放置一个main类并调用它,
CLASSPATH=$CLASSPATH:$ROOTDIR/bin/batch.jar
java -cp $CLASSPATH hogehoge.batch.job.BatchJob
移行后的情况是这样的
ENVIRONMENT_NAME=dev
java -jar -Dspring.profiles.active=$ENVIRONMENT_NAME -Dserver.port=9000 -Dbatch.execute=BatchJob $ROOTDIR/bin/batch.jar
在个人资料上,可以通过变量来切换和获取信息。
主要在通过sh调用批处理时,我们使用了Java命令进行调用,所以需要做相应的适配。
在Java端,将采用以下方式实现,使用ApplicationRunner进行实现。
public class BatchJob implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
}
@Configuration
public static class BatchConfig {
@Bean
@ConditionalOnProperty(value = { "batch.execute" }, havingValue = "BatchJob")
public BatchJob batchJob() {
return new BatchJob();
}
}
}
道
在Spring Boot中需要使用@ConfigAutowireable进行添加,虽然Dao类是由doma-gen自动生成的。
+ @ConfigAutowireable
@Dao
public interface HogeDao {
}
修改自动生成模板后,之后就可以实现自动化。
像是Helper这样的组件
给这个类添加@Component注解,如果还添加@RequiredArgsConstructor注解,就不需要@Autowired注解了,代码会变得更简洁。
对于服务类,要使用`@Service`进行标注。
// 元のやつ
public class HogeHelper {
@Resource
private HogeDao hogeDao;
}
// 移行後
@Component
@RequiredArgsConstructor
public class HogeHelper {
private final HogeDao hogeDao;
}
// service
@Service
@RequiredArgsConstructor
public class HogeService {
private final HogeHelper hogeHelper;
}
切换连接的数据库。 de .)
因为seasar2和原来的方法有很大的不同,一开始我有点陷入困境。
原来的方式是在dao包中的处理中添加拦截器,将dataSource名设置到DataSourceFactory中。
由于没有了DataSourceFactory,为了以同样的方式进行操作,我创建了一个持有目标数据库的持有者,在连接时从该持有者中获取,采用这种方式。
在doma的设置中,逐步将其更改为引用此持有者。
切换拦截器
@Component
@Aspect
public class HogeInterceptor {
@Around("execution(* *..*Dao.*(..))")
public Object invoke(ProceedingJoinPoint jp) throws Throwable {
Schema schema = SchemaHolder.getSchema() == null ? Schema.DEFAULT : SchemaHolder.getSchema();
// ここで切り替えたいDBのdataSourceNameをとってくる
String dataSourceName = "hogehogeDataSource";
try {
SchemaHolder.setSchema(dataSourceName);
return jp.proceed();
} finally {
// 戻す
SchemaHolder.setSchema(schema);
}
}
}
一个用于持有者的类
public class SchemaHolder {
@AllArgsConstructor
@Getter
public static enum Schema {
DEFAULT("defaultDataSource"),
HOGEHOGE("hogehogeDataSource"),
;
private final String name;
}
private static final ThreadLocal<Schema> schemaHolder = new ThreadLocal<>();
public static void setSchema(Schema schema) {
schemaHolder.set(schema);
}
public static void setSchema(String name) {
for (Schema schema : Schema.values()) {
if (schema.getName().equals(name)) {
setSchema(schema);
break;
}
}
}
public static Schema getSchema() {
return schemaHolder.get();
}
public static void clear() {
schemaHolder.remove();
}
}
多麻二 (Duō má èr)
对于doma的情况,通过实现org.seasar.doma.jdbc.Config并定义设置,因此可以重新编写该部分。
根据我们所知,使用MyBatis时似乎不需要,但如果使用Doma,为了使事务参与其中,需要通过TransactionAwareDataSourceProxy对DataSource进行包装。
将tomcat和datasource的配置写入yml文件,并通过读取和设置来实现。
使用先前的持有者进行DB切换
@Configuration
@EnableTransactionManagement
public class DaoAppConfig implements Config {
@Override
@Bean
public DataSource getDataSource() {
if (dataSource != null) {
return dataSource;
}
AbstractRoutingDataSource abstractRoutingDataSource = new AbstractRoutingDataSource() {
@Override
protected Object determineCurrentLookupKey() {
if (SchemaHolder.getSchema() == null) {
return Schema.DEFAULT.getName();
}
return SchemaHolder.getSchema().getName();
}
};
Map<Object, Object> dataSources = Map.ofEntries(
entry(Schema.DEFAULT.getName(), defaultDataSource()),
entry(Schema.HOGEHOGE.getName(), hogehogeDataSource()),
);
abstractRoutingDataSource.setTargetDataSources(dataSources);
abstractRoutingDataSource.setDefaultTargetDataSource(defaultDataSource());
dataSource = abstractRoutingDataSource;
return dataSource;
}
@Bean
public DataSource defaultDataSource() {
return new TransactionAwareDataSourceProxy(this.tomcatJdbcConnectionPool(defaultDbc(), dbcProp()));
}
@Bean
public DataSource hogehogeDataSource() {
return new TransactionAwareDataSourceProxy(this.tomcatJdbcConnectionPool(hogehogeDbc(), dbcProp()));
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.default")
protected DataBaseConnectionSetting defaultDbc() {
return new PostgresqlConnectionSetting();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hogehoge")
protected DataBaseConnectionSetting hogehogeDbc() {
return new PostgresqlConnectionSetting();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.tomcat")
protected DataBaseConnectionSettingProperties dbcProp() {
return new PostgresqlConnectionSettingProperties();
}
private org.apache.tomcat.jdbc.pool.DataSource tomcatJdbcConnectionPool(DataBaseConnectionSetting dbc, DataBaseConnectionSettingProperties dbcProp) {
org.apache.tomcat.jdbc.pool.DataSource ds = new org.apache.tomcat.jdbc.pool.DataSource();
// 接続先情報等を設定
ds.setDriverClassName(dbc.getDriver());
ds.setUsername(dbc.getUser());
ds.setPassword(dbc.getPassword());
ds.setUrl(dbc.getUrl());
return ds;
}
@Bean
public PlatformTransactionManager transactionManager() {
PlatformTransactionManager[] platformTransactionManagers = new PlatformTransactionManager[] {
new DataSourceTransactionManager(defaultDataSource()),
new DataSourceTransactionManager(hogehogeDataSource()),
};
return new ChainedTransactionManager(platformTransactionManagers);
}
}
DB设置的方法接口
public interface DataBaseConnectionSettingProperties {
Integer getMaxActive();
Integer getInitialSize();
Integer getMaxIdle();
Integer getMinIdle();
Integer getMaxAge();
Boolean getFairQueue();
Integer getMaxWait();
Boolean getTestOnBorrow();
Boolean getTestOnReturn();
String getValidationQuery();
Integer getValidationQueryTimeout();
Integer getValidationInterval();
Boolean getTestWhileIdle();
Integer getTimeBetweenEvictionRunsMillis();
Integer getMinEvictableIdleTimeMillis();
Boolean getRemoveAbandoned();
Integer getRemoveAbandonedTimeout();
String getConnectionProperties();
}
数据库设置的属性类 (DB设置属性的类)
@Data
public class PostgresqlConnectionSettingProperties implements DataBaseConnectionSettingProperties {
private Integer maxActive;
private Integer initialSize;
private Integer maxIdle;
private Integer minIdle;
private Integer maxAge;
private Boolean fairQueue;
private Integer maxWait;
private Boolean testOnBorrow;
private Boolean testOnReturn;
private String validationQuery;
private Integer validationQueryTimeout;
private Integer validationInterval;
private Boolean testWhileIdle;
private Integer timeBetweenEvictionRunsMillis;
private Integer minEvictableIdleTimeMillis;
private Boolean removeAbandoned;
private Integer removeAbandonedTimeout;
private String connectionProperties;
}
DB設置的連接介面
public interface DataBaseConnectionSetting {
String getUrl();
String getUser();
String getPassword();
String getDriver();
String getDriverClassName();
Integer getMaxActive();
Integer getInitialSize();
Integer getMaxIdle();
Integer getMinIdle();
}
数据库设置的连接类 (DB设置的连接类)
@Data
public class PostgresqlConnectionSetting implements DataBaseConnectionSetting {
private String driver = "org.postgresql.Driver";
private Integer port = 5432;
private String domain = "127.0.0.1";
private String schema;
private String user;
private String password;
private String dbname = "postgresql";
private Integer maxActive = null;
private Integer initialSize = null;
private Integer maxIdle = null;
private Integer minIdle = null;
@Override
public String getUrl() {
return String.format("jdbc:%s://%s:%s/%s", this.dbname, this.domain, this.port, this.schema);
}
@Override
public String getDriverClassName() {
return driver;
}
}
应用配置文件.yml
描述应用程序的设置
根据启动时的配置文件,会读取并覆盖对应的 application-[profile名称].yml 文件的设置,因此可以在 application.yml 中编写默认设置,并根据每个配置文件进行特定的更改。另外,由于使用了 actuator 和 spring boot admin,需要在配置文件中说明这一点。
应用配置文件。
spring.thymeleaf.mode: HTML
spring.datasource.initialization-mode: NEVER
spring.datasource.type: [org.seasar.doma.jdbc.Configをimplementsしたクラス]
spring.main.allow-bean-definition-overriding: true
server.connection-timeout: 600000
spring.datasource.tomcat:
maxActive: 10
initialSize: 10
maxIdle: 2
minIdle: 1
spring:
datasource:
default:
domain: localhost
user: user
password: password
schema: default
hogehoge:
domain: localhost
user: user
password: password
schema: hogehoge
management:
endpoints:
web:
base-path: /actuator
exposure:
exclude: '*'
endpoint:
shutdown:
enabled: false
基本的设置和用于doma2加载的数据库设置以及actuator设置都被完成了
应用开发配置文件- application-dev.yml
spring:
datasource:
default:
domain: 255.255.255.255
user: user
password: password
schema: default
hogehoge:
domain: 255.255.255.255
user: user
password: password
schema: hogehoge
只有当profile=dev时,才会创建并覆写这样的文件。
交易
如果想要在所有错误发生时进行回滚,可以使用@Transactional来设置事务的边界。另外,可以通过设置rollbackFor来指定回滚的异常类型。
@Transactional(rollbackFor = Exception.class)
public List<> findList() {
return hogeHelper.findList();
}
所以,只要解决错误,尝试运行并处理异常情况,看起来就可以顺利完成迁移。
请记录下遇到的困难以及之后需要追加完成的事项。
在使用构造函数注入时,出现了依赖循环导致错误。
如果使用构造函数注入,则在启动时进行依赖注入,并对其成员进行依赖注入,因此如果源代码存在相互依赖或循环依赖,则会在应用程序启动时出现错误。
对于解决依赖性可以使用字段注入或者setter注入的方式,但有时也可能会遇到无法修改或访问的情况,比如在使用Spaghetti框架时。
@Component
public class HogeHelper {
private HogeDao hogeDao;
@Autowired
public void hogeDao(HogeDao hogeDao) {
this.hogeDao = hogeDao;
}
}
log4j转变为log4j2。
在使用执行器(actuator)时,当尝试更改日志级别时,发现日志配置中没有对应的设置项,经过调查发现必须使用log4j2或logback才能解决这个问题,所以顺利进行迁移。
log4j.xml 文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="threshold" value="info" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
<appender name="FILE" class="org.apache.log4j.FileAppender">
<param name="File" value="/tmp/logs/hoge.log" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%m%n" />
</layout>
</appender>
<logger name="org.apache.commons" additivity="false">
<level value="WARN" />
<appender-ref ref="STDOUT" />
</logger>
<logger name="file_log" additivity="false">
<level value="INFO" />
<appender-ref ref="FILE" />
</logger>
<root>
<priority value="INFO" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
如果log4j.xml文件被定义为这样,可以按照下面的方式进行迁移。
log4j2配置文件
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="off">
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%d [%t] %-5p %c - %m%n"/>
</Console>
<File name="FILE" fileName="/tmp/logs/hoge.log">
<PatternLayout pattern="%m%n"/>
</File>
</Appenders>
<Loggers>
<Logger name="org.apache.commons" level="WARN" additivity="false">
<AppenderRef ref="STDOUT"/>
</Logger>
<Logger name="file_log" level="INFO" additivity="false">
<AppenderRef ref="FILE"/>
</Logger>
<Root level="INFO">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
应用程序启动后的处理
一旦应用程序启动,由于有一些想要执行的操作,所以也进行了相应的实现。
@EventListener(ApplicationReadyEvent.class)
当指定某项内容时,在启动准备完毕时调用。
@SpringBootApplication
@ComponentScan(basePackages = "hoge")
public class Application {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(Application.class);
springApplication.run(args);
}
@EventListener(ApplicationReadyEvent.class)
public void doAfterStartup() {
// 起動時にやりたい処理をする
// ここに来た時点でDI等は行われているはずなのでDao操作する処理の呼び出しとかもできる
}
}
异常处理程序
如果想使用类似于GlobalException的通用错误处理,需要创建它。
@Controller
@RequiredArgsConstructor
@ControllerAdvice(annotations = { RestController.class })
public class ApiExceptionHandler {
@ExceptionHandler(Throwable.class)
public Object handlerException(HttpServletRequest request, HttpServletResponse response, Throwable t) {
// Throwableなので全部ひろってしまう
// 種類で処理をわけたいとき
if (t instanceof NullPointerException) {
} else {
}
// 共通処理とか書く
return null;
}
@ExceptionHandler(NullPointerException.class)
public Object nullPointerException(HttpServletRequest request, HttpServletResponse response, Throwable t) {
// 特定のエラー拾いたい時
return new RedirectView("http://localhost:8080/nullpointer");
}
}
如果完成了这些步骤,基本上就可以顺利进行转移了,于是我们需要调整一些自行安装的组件,以确保其正常运行。
在我所做的事情中,有一件是由于spring5以后将XMLBeans的集成类删除了,所以我将其替换为XLSMapper并重新编写了处理逻辑。这使我感到非常沮丧。
一旦系统开始运行,就可以引入诸如actuator、restdocs和spring-boot-admin等,自主开发的功能可以被取消,使得系统管理更加方便。
作为所花费的时间,因为是在日常工作的同时进行的,所以大约是一个人独自完成了将近两个月。起初连编译都无法通过,所以是一场孤独的战斗,但我相信总会有一天会迎来光明,所以我忍耐着战斗下去。
由于源代码完全改变,所以需要修复相关的Jenkins、Shell等辅助工具。还需要在先行的代码中使用git进行合并,同时注意每次都要合并主代码来解决问题,否则最终合并会变得困难。如果项目的提交很多,或者有很多人参与其中,难度可能会增加。
部署包含的jar文件到服务器。
我会在附录中写下将要应用到CentOS服务器的备忘录。
创建活动文件夹
sudo mkdir -p /var/local/app
sudo chown -R user:user /var/local/app
无论文件夹在哪里都可以。只要启动用户可以操作的位置就没有问题。
将使用bootJar创建的jar文件放置在创建的文件夹中。
./gradlew web:bootJar -x test
# web/build/libs/web.jarができているのでspcとかであげる
创建init.d
sudo ln -s /var/local/app/web.jar /etc/init.d/web
使用这个选项
在build.gradle中加入launchScript()到bootJar,这样/etc/init.d/web将变成一个启动脚本(二进制文件)的重点。
将conf文件生成到与jar文件相同的位置。
cat web.conf
JAVA_OPTS="-Dspring.profiles.active=dev"
将一个与jar文件同名的conf文件放置在同一文件夹中。虽然有很多东西要写,但即使使用默认配置也可以运行,所以不需要改变配置也可以。
管理员权限
sudo visudo
user ALL=(root) NOPASSWD:/etc/init.d/web *
为了更容易启动,将权限添加到visudo。
动起来
sudo /etc/init.d/web start
轮换
sudo vi /etc/logrotate.d/syslog
/var/log/web.log
由于在/var/log目录下生成日志,因此我们将进行日志轮转设置。除了/var/log之外,还可以通过log4j2等配置将日志输出到喜欢的位置。
对这个的想法或者看法
最开始以为需要更多时间才开始的工作,所以能够比想象中更快完成真是太好了。
这是最初的感觉。
对我个人来说,seasar2本身很好用,但开发已经结束了,这是一个很大的问题,不得不进行迁移了。
虽然如此,也有一些可用的东西,所以在不依赖seasar2的情况下,也会移植和使用一些方便的util类库。
在我快速转换的过程中,我立即喜欢上了restdocs。现在可以很容易地按照以下流程进行:
– 编写单元测试
– 创建测试数据
– 运行测试
– 生成文档
这使得API文档的质量更容易得到保证。