別サイト
に書いていたのだけどサ終してしまったので移行した記事その6
2019/10/01のもの
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の設定を入れていく。
webなどはcoreを使用するのでsourceSetsを設定していく。
spring securityやspring actuatorはお好み。
使う必要がなければとくに入れる必要はない。
Application作成
@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);
}
}
securityを入れたはいいんだけど使っているURLがsecurity側で不正扱いになってしまい、
URLをなおすのは少し厳しい状況だったため結局excludeに入れてオフる。
意味ない。。。。
キャッシュの設定やコンテキストパスの設定、その他プロパティの設定などを入れる。
各種Actionの対応
@Controller
@RequiredArgsConstructor
@RequestMapping("")
クラスへのアノテーション
@Controllerでseasar2でいうアクションを指定していく
@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();
+ }
formの設定
やり方はいろいろありそうだけどわかりやすいのは@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設定やformやアクションの定義と書き方を変えていく
@RequiredArgsConstructorを使うとfinalのフィールドに対してコンストラクタが生成されるので
対象のフィールドをコンストラクタインジェクションされるようにできるのでコードがすっきりする
(たしかこれsearsar2だとデフォルトコンストラクタがないよって怒られてコンストラクタインジェクションができなくて諦めてた記憶がある)
publicフィールドに変数をもって画面側に渡している構成になっているアプリケーションは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でページ遷移してその中のajax処理も同じactionクラス内で受けていたのでこういうのが必要だった
session情報格納クラス
- @Component(instance = InstanceType.SESSION)
+ @Component
+ @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
アノテーションをかえればOK
velocity -> thymeleaf
spring5以降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()}
とかで使えるはず
AOP
インターセプター処理を移行していく
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>はよく使うけどさくっと取れる
SingletonS2Container
- 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
profileのとこは変数とかでもらうようにして切り替えたりとかとか
主にバッチ処理を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();
}
}
}
dao
daoクラスはdoma-genで自動生成しているがspring bootでは@ConfigAutowireableをつける必要があるので追加
+ @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;
}
接続先DBの切り替え
seasar2とは結構違うので最初はちょっと行き詰まった
元々はdaoパッケージにおいている処理にインターセプターを挟んでDataSourceFactoryにdataSource名をセットするというようにやってた
DataSourceFactoryがなくなるから同じようにやるために対象DBを保持するホルダーを作成して接続しに行く時にそのホルダーから取り出す、という方式にした
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();
}
}
doma2
domaの場合org.seasar.doma.jdbc.Configをimplementsして設定を定義しているのでそこを書き換える
mybatisだと不要らしいのだけどdomaの場合はtransactionに参加させるために
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;
}
}
application.yml
アプリケーションの設定を記述
起動時のprofileによってapplication-[profile名].ymlの設定も読み込んで上書きするので
デフォルトの設定をapplication.ymlに書きプロファイルごとに変えたい設定をそれぞれに書く。
actuatorやspring boot adminを使用することにしたのでその旨も記述する。
application.yml
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に読み込ませるDB設定、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で上書きしたいときはこんなファイルを作り書き換える
profile=devだったときのみ上書かれる
transaction
トランザクションの境界は@Transactionalを使用する
すべてのエラーでロールバックしたいときはrollbackForを設定する
@Transactional(rollbackFor = Exception.class)
public List<> findList() {
return hogeHelper.findList();
}
で、ここまでやればあとはエラーを潰したり、動かしてみておかしいところを対応していけば移行できそう
以下、行き詰まったことや追加であとからやっていったものをメモ書きしておく
コンストラクタインジェクションで依存が循環してエラーになる
コンストラクタインジェクションの場合、起動時にDIを行いそのメンバに対してもDIを行うのでソースが相互依存していたり依存が循環しているとアプリケーションの起動時にエラーします。
依存性を解消してしまうのがよいけどスパゲッティだったり触れない場合もあるのでその場合はフィールドインジェクションかセッターインジェクションなどを使う
@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
<?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>
Application起動後の処理
アプリケーションが起動したら行いたい処理があったためそちらも実装した
@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操作する処理の呼び出しとかもできる
}
}
exceptionのハンドラー
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");
}
}
ここまですべてやったらだいたい移行できたので独自で入れてるものなどを動くように調整していく
自分がやった中であったのはXMLBeansがspring5以降連携クラスが消されてしまったので
XLSMapperに差し替えて処理を書き換えたなどがあった
心折れた
動くようになればactuatorとかrestdocsとかspring-boot-adminとか導入できるようになり独自でやっていたものが消せて色々管理しやすくなった
かかった期間としては日次の作業の傍らだったのもあり自分1人でやって2ヶ月弱ぐらい
最初はコンパイルすら通らない状態になるから孤独な戦いだけどいつか光が開けると思って我慢の戦い
ソースが完全に変わってしまうので付随するjenkinsやらシェルやらも直すことになるし
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
これをやると/etc/init.d/webが起動スクリプト(バイナリ)になります
build.gradleでbootJarにlaunchScript()を入れているのがポイントです
conf作成(jarと同じ場所)
cat web.conf
JAVA_OPTS="-Dspring.profiles.active=dev"
jarと同じ名前のconfを同フォルダに置きます
色々書いてますがデフォルトでも動くはするので特にコンフィグ変えないよって場合はなしでもいける
sudo権限
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ドキュメントの質が担保しやすくなったなあ、と。