从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文档的质量更容易得到保证。

广告
将在 10 秒后关闭
bannerAds