現在大家都追趕新的技術潮流,我來逆行一下。
其實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.properties
和application-xxx.properties
中的變數會自動放到 Environment中,並且可以通過@Value
直接注入到變數中。
如果使用 ClassPathXmlApplicationContext 初始化專案,可以看到原始碼裡 Environment 是一個 StandardEnvironment 例項,僅僅包含系統變數和環境變數,為了把application-xxx.properties
放到 Environment 當中我們需要擴充套件一下 ClassPathXmlApplicationContext,下面是CustomApplicationContext
和CustomEnvironment
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 est
下的
esourcesapplication.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可以達到差不多同樣的效果,但是在結構和配置上更加清晰。