如何把Spring Boot 项目变成一个XML配置的Spring项目

现在大家都追赶新的技术潮流,我来逆行一下。

其实Spring Boot 隐藏了大量的细节,有大量的默认配置,其实通过xml配置的方式也可以达到和Spring Boot一样的效果。

Profile

在Spring Boot项目中我们通过application.properties中的设置来配置使用哪个配置文件application-dev.properties,application-prod.properties等等

spring.profiles.active=dev

Spring 3.0以后就包含了Profile功能,在xml中可以这么写,不过所有的bean需要显式的配置。需要弄清楚自己项目的依赖关系,在Spring中第三方包如何初始化。

<beans profile="dev,test">
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:application-dev.properties" />
    </bean>
</beans>

<beans profile="prod">
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:application-prod.properties" />
    </bean>
</beans>

在Spring Boot中大量的Jave Bean都包含了默认初始化的功能,只需要配置预先设置好的属性名称,但是在xml中需要显式的初始化Bean,并且可以在初始化的时候用Placeholder来配置。

Environment

在 Spring Boot 项目中application.propertiesapplication-xxx.properties 中的变量会自动放到 Environment中,并且可以通过@Value直接注入到变量中。

如果使用 ClassPathXmlApplicationContext 初始化项目,可以看到源代码里 Environment 是一个 StandardEnvironment 实例,仅仅包含系统变量和环境变量,为了把application-xxx.properties放到 Environment 当中我们需要扩展一下 ClassPathXmlApplicationContext,下面是CustomApplicationContextCustomEnvironment

import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

public class CustomApplicationContext extends ClassPathXmlApplicationContext {

    public CustomApplicationContext(){
        super();
    }

    public CustomApplicationContext(String configLocation) {
        super(new String[]{configLocation}, true, null);
    }

    @Override
    public ConfigurableEnvironment createEnvironment() {
        return new CustomEnvironment();
    }
}
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePropertySource;

import java.io.IOException;

public class CustomEnvironment extends StandardEnvironment {

    private static final String APPCONFIG_PATH_PATTERN = "classpath:application-%s.properties";

    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        super.customizePropertySources(propertySources);
        try {
            propertySources.addLast(initResourcePropertySourceLocator());
        } catch (IOException e) {
            logger.warn("failed to initialize application config environment", e);
        }
    }

    private PropertySource<?> initResourcePropertySourceLocator() throws IOException {
        String profile = System.getProperty("spring.profiles.active", "dev");
        String configPath = String.format(APPCONFIG_PATH_PATTERN, profile);
        System.out.println("Using application config: " + configPath);

        Resource resource = new DefaultResourceLoader(this.getClass().getClassLoader()).
                getResource(configPath);
        PropertySource resourcePropertySource = new ResourcePropertySource(resource);
        return resourcePropertySource;
    }
}

日志配置

Spring Boot 默认使用的是logback,在logback-spring.xml 的配置文件中可以使用Spring Profile,而且还有一个默认的CONSOLE Appender

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml" />
<springProfile name="dev,test">
    <logger name="org.springframework" level="DEBUG" additivity="false">
        <appender-ref ref="CONSOLE"/>
    </logger>
</springProfile>
<springProfile name="prod">
    <logger name="org.springframework" level="INFO" additivity="false">
        <appender-ref ref="CONSOLE"/>
    </logger>
</springProfile>
</configuration>

在没有使用Spring Boot的情况下,不能在logback的config中使用Spring Profile,只能分拆成多个文件,然后根据环境变量读取不同的配置文件,需要添加依赖org.logback-extensions

<dependency>
    <groupId>org.logback-extensions</groupId>
    <artifactId>logback-ext-spring</artifactId>
    <version>0.1.4</version>
</dependency>

logback-dev.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%15.15t] %-40.40logger{39} : %m%n}"/>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>
    <logger name="org.springframework" level="DEBUG" additivity="false">
        <appender-ref ref="CONSOLE"/>
    </logger>
</configuration>

logback-prod.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%15.15t] %-40.40logger{39} : %m%n}"/>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>
    <logger name="org.springframework" level="INFO" additivity="false">
        <appender-ref ref="CONSOLE"/>
    </logger>
</configuration>

下面的代码根据环境变量读取不同的配置文件

    private static final String LOGCONFIG_PATH_PATTERN = "classpath:logback-%s.xml";

    public static void main(String[] args) throws FileNotFoundException, JoranException {
        String profile = System.getProperty("spring.profiles.active", "dev");
        System.setProperty("file.encoding", "utf-8");

        // logback config
        String logConfigPath = String.format(LOGCONFIG_PATH_PATTERN, profile);
        System.out.println("Using logback config: " + logConfigPath);
        LogbackConfigurer.initLogging(logConfigPath);
        SLF4JBridgeHandler.removeHandlersForRootLogger();
        SLF4JBridgeHandler.install();
        
        ConfigurableApplicationContext context = new CustomApplicationContext("classpath:applicationContext.xml");
    }

测试

有Spring Boot 的时候TestCase写起来很方便,在类上添加两行注解即可,在src\test\resources下的application.properties中设置spring.profiles.active=test即可指定Profile为test

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestStockService {

    @Autowired
    StockService stockService;

    @Before
    public void setUp() {
    }

    @After
    public void tearDown() {
    }

    @Test
    public void testMissingBbTickerEN() {

    }
}

不使用Spring Boot的情况下,需要指定好几个配置。

@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
@ActiveProfiles(profiles = "test")
@TestPropertySource("classpath:application-test.properties")
public class TestStockService {

    @Autowired
    StockService stockService;

    @Before
    public void setUp() {
    }

    @After
    public void tearDown() {
    }

    @Test
    public void testMissingBbTickerEN() {
    }
}

打包

Spring Boot 会把项目和所依赖的 Jar 包打包成一个大 Jar 包,直接运行这个 Jar 包就可以。这个功能是通过spring-boot-maven-plugin实现的。

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

不使用Spring Boot 之后,我们需要配置maven-jar-plugin,但是依赖包无法像Spring Boot一样打包成一个大的 Jar 包,需要我们指定classpath。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.6</version>
    <configuration>
        <archive>
            <manifest>
                <mainClass>com.exmaple.demo.DemoApplication</mainClass>
                <addClasspath>true</addClasspath>
                <classpathPrefix>lib/</classpathPrefix>
            </manifest>
        </archive>
    </configuration>
</plugin>

注意:

当用java -jar yourJarExe.jar来运行一个经过打包的应用程序的时候,你会发现如何设置-classpath参数应用程序都找不到相应的第三方类,报ClassNotFound错误。实际上这是由于当使用-jar参数运行的时候,java VM会屏蔽所有的外部classpath,而只以本身yourJarExe.jar的内部class作为类的寻找范围。所以需要在jar包mainfest中添加classpath。

依赖包

使用下面的maven配置帮你把所有的依赖包复制到targetlib目录下,方便我们部署或者是测试时复制依赖包。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
        <execution>
            <id>copy-dependencies</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>copy-dependencies</goal>
            </goals>
            <configuration>
                <outputDirectory>target/lib</outputDirectory>
                <overWriteIfNewer>true</overWriteIfNewer>
                <excludeGroupIds>
                    junit,org.hamcrest,org.mockito,org.powermock,${project.groupId}
                </excludeGroupIds>
            </configuration>
        </execution>
    </executions>
    <configuration>
        <verbose>true</verbose>
        <detail>true</detail>
        <outputDirectory>${project.build.directory}</outputDirectory>
    </configuration>
</plugin>

运行

运行时通过指定命令行参数 -Dspring.profiles.active=prod 来切换profile

java -jar -Dspring.profiles.active=prod demo.jar

总结

Spring Boot很大程度上方便了我们的开发,但是隐藏了大量的细节,我们使用xml配置spring可以达到差不多同样的效果,但是在结构和配置上更加清晰。

    原文作者:openmartin
    原文地址: https://segmentfault.com/a/1190000013088916
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞