spring boot約定優於配置的這種做法在如今越來越流行了


約定優於配置的這種做法在如今越來越流行了,它的特點是簡單、快速、便捷。但是這是建立在程序員熟悉這些約定的前提上。而 Spring 擁有一個龐大的生態體系,剛開始轉到 Spring Boot 完全舍棄 XML 時肯定是不習慣的,所以也會造成一些困擾。

 

運行方式

spring-boot-starter-web 包含了 Spring MVC 的相關依賴(包括 Json 支持的 Jackson 和數據校驗的 Hibernate Validator)和一個內置的 Tomcat 容器,這使得在開發階段可以直接通過 main方法或是 JAR 包獨立運行一個 WEB 項目。而在部署階段也可以打成 WAR 包放到生產環境運行。

 

@SpringBootApplication public class Application extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(Application.class); } public static void main(String[] args) throws Exception { SpringApplication.run(Application.class, args); } }

在擁有 @SpringBootApplication 注解的類中,使用 SpringApplication 的 run 方法可以通過JAR啟動項目。

 

繼承 SpringBootServletInitializer 類並實現 configure 方法,使用 application 的 sources 方法可以通過WAR啟動項目。

 

 

配置文件

Spring boot 的默認配置文件是 resources 下的 application.properties 和 application.yml

 

 

配置文件

Spring boot 的默認配置文件是 resources 下的 application.properties 和 application.yml

我曾在項目中遇到過 application.properties 出現中文亂碼問題,當時嘗試了很多辦法都沒有解決。Spring Boot 總是會以 iso-8859 的編碼方式讀取該文件,后來改用 YAML 了就再也沒有出現過亂碼了。並且它也擁有更簡潔的語法,所以在此也更推薦使用 application.yml 作為默認的配置文件。

配置文件中可以定義一個叫做 spring.profiles.active 的屬性,該屬性可以根據運行環境自動讀取不同的配置文件。例如將該屬性定義為 dev 的話,Spring Boot 會額外從 application-dev.yml 文件中讀取該環境的配置。

Spring Boot 注入配置文件屬性的方法有兩種,一種是通過 @Value 注解接受配置文件中的屬性,另外一種是通過 @ConfigurationProperties 注解通過 set 方法自動為Bean注入對應的屬性。

通過 @Value 注入屬性,接收者既可以是方法參數,也可以是成員變量。例如配置文件為:

dataSource: url: jdbc:mysql://127.0.0.1:3306/test username: test password: test filters: stat,slf4j redis: host: 192.168.1.222 port: 6379

通過 @Value 接受方法參數初始化Bean:

@Bean public JedisPool jedisPool(@Value("${redis.host}") String host, @Value("${redis.port}") int port) { return new JedisPool(host, port); }


注入配置文件屬性
注入配置文件屬性
注入配置文件屬性
 

通過 @ConfigurationProperties 讀取配置初始化Bean,會直接調用對應的 set 方法注入:

@Bean(initMethod="init",destroyMethod="close") @ConfigurationProperties(prefix="dataSource") public DataSource dataSource() { return new DruidDataSource(); }

Spring Boot 目前還無法直接注入的靜態變量。我目前使用的方法是專門建立一個讀取配置文件的Bean,然后使用 @PostConstruct 注解修飾的方法對這些靜態屬性進行初始化,例如:

@Configuration public class ConstantsInitializer { @Value("${paging_size}") private String pagingSize; @PostConstruct public void initConstants() { Constants.PAGING_SIZE = this.pagingSize; } }
 

Servlet

Servlet 中最重要的配置文件就是 web.xml ,它的主要用途是配置Servlet映射和過濾器。而在 Spring Boot 中這將簡單很多,只需要將對應的 Servlet 和 Filter 定義為 Bean 即可。

聲明一個映射根路徑的 Servlet ,例如 Spring MVC 的 DispatcherServlet :

 

 

Spring MVC

Spring MVC 主要的配置都可以通過繼承 WebMvcConfigurerAdapter (或者 WebMvcConfigurationSupport )類進行修改,這兩個類的主要方法有:

Spring MVC 主要的配置都可以通過繼承 WebMvcConfigurerAdapter (或者 WebMvcConfigurationSupport )類進行修改,這兩個類的主要方法有:

Spring MVC 主要的配置都可以通過繼承 WebMvcConfigurerAdapter (或者 WebMvcConfigurationSupport )類進行修改,這兩個類的主要方法有:

  • addFormatters :增加格式化工具(用於接收參數)
  • configureMessageConverters :配置消息轉換器(用於 @RequestBody 和 @ResponseBody )
  • configurePathMatch :配置路徑映射
  • addArgumentResolvers :配置參數解析器(用於接收參數)
  • addInterceptors :添加攔截器

總之幾乎所有關於 Spring MVC 都可以在這個類中配置。之后只需要將其設為 @Configuration,Spring Boot 就會在運行時加載這些配置。

只需要將其設為 @Configuration,Spring Boot 就會在運行時加載這些配置。

只需要將其設為 @Configuration,Spring Boot 就會在運行時加載這些配置。

只需要將其設為 @Configuration,Spring Boot 就會在運行時加載這些配置。

 

還有一些常用的 Bean 默認會自動創建,但是可以通過自定義進行覆蓋,例如負責 @RequestBody 和 @RequestBody 進行轉換的 MappingJackson2HttpMessageConverter 和 ObjectMapper ,可以直接這樣覆蓋掉:

@Bean public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { return new CustomMappingJackson2HttpMessageConverter(); } @Bean public ObjectMapper jsonMapper(){ ObjectMapper objectMapper = new ObjectMapper(); //null輸出空字符串 objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() { @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeString(""); } }); return objectMapper; }

DataSource

如果使用了 spring-boot-starter-data-jpa ,Spring Boot將會自動創建一個 DataSource Bean。可以直接在配置文件中定義它的屬性,前綴是 spring.datasource 。並且無需指定數據庫的方言,這個 Bean 會自動根據項目中依賴的數據庫驅動判斷使用的哪種數據庫。

同樣的,如果使用了 spring-boot-starter-data-redis ,也會自動創建 RedisTemplate 、 ConnectionFactory 等 Bean。也同樣可以在配置文件中定義屬性,前綴是 spring.redis 。

還有一些常用的 Bean 默認會自動創建,但是可以通過自定義進行覆蓋,例如負責 @RequestBody 和 @RequestBody 進行轉換的 MappingJackson2HttpMessageConverter 和 ObjectMapper ,可以直接這樣覆蓋掉:

@Bean public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { return new CustomMappingJackson2HttpMessageConverter(); } @Bean public ObjectMapper jsonMapper(){ ObjectMapper objectMapper = new ObjectMapper(); //null輸出空字符串 objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() { @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeString(""); } }); return objectMapper; }

DataSource

如果使用了 spring-boot-starter-data-jpa ,Spring Boot將會自動創建一個 DataSource Bean。可以直接在配置文件中定義它的屬性,前綴是 spring.datasource 。並且無需指定數據庫的方言,這個 Bean 會自動根據項目中依賴的數據庫驅動判斷使用的哪種數據庫。

同樣的,如果使用了 spring-boot-starter-data-redis ,也會自動創建 RedisTemplate 、 ConnectionFactory 等 Bean。也同樣可以在配置文件中定義屬性,前綴是 spring.redis 。

 

 

springboot用來簡化Spring框架帶來的大量XML配置以及復雜的依賴管理,讓開發人員可以更加關注業務邏輯的開發。

比如不使用springboot而使用SpringMVC作為web框架進行開發的時候,需要配置相關的SpringMVC配置以及對應的依賴,比較繁瑣;而使用springboot的話只需要以下短短的幾行代碼就可以使用SpringMVC,可謂相當地方便:

 

@RestController
class App {
  @RequestMapping("/")
  String home() {
    "hello"
  }
}

其中maven配置如下:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.5.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

我們以使用SpringMVC並且視圖使用freemarker為例,分析springboot內部是如何解析freemarker視圖的。

如果要在springboot中使用freemarker視圖框架,並且使用maven構建項目的時候,還需要加入以下依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
    <version>1.3.5.RELEASE</version>
</dependency>

這個spring-boot-starter-freemarker依賴對應的jar包里的文件如下:

META-INF
├── MANIFEST.MF
├── maven
│   └── org.springframework.boot
│       └── spring-boot-starter-freemarker
│           ├── pom.properties
│           └── pom.xml
└── spring.provides

這個spring-boot-starter-parent的pom文件在http://central.maven.org/maven2/org/springframework/boot/spring-boot-starter-parent/1.3.5.RELEASE/spring-boot-starter-parent-1.3.5.RELEASE.pom 里。

它內部也有一個parent:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>1.3.5.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

這個spring-boot-dependencies的pom文件在http://central.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/1.3.5.RELEASE/spring-boot-dependencies-1.3.5.RELEASE.pom,內部有很多依賴。

 

 

其中spring-boot-starter-web內部依賴了spring的兩個spring web依賴:spring-web和spring-webmvc。

spring-boot-starter-web內部還依賴spring-boot-starter,這個spring-boot-starter依賴了spring核心依賴spring-core;還依賴了spring-bootspring-boot-autoconfigure這兩個。

spring-boot定義了很多基礎功能類,像運行程序的SpringApplication,Logging系統,一些tomcat或者jetty這些EmbeddedServlet容器,配置屬性loader等等。

包括了這些包:

image

spring-boot-autoconfigure定義了很多自動配置的類,比如jpa,solr,redis,elasticsearch、mongo、freemarker、velocity,thymeleaf等等自動配置的類。

以freemarker為例,看一下它的自動化配置類:

@Configuration // 使用Configuration注解,自動構造一些內部定義的bean
@ConditionalOnClass({ freemarker.template.Configuration.class,
        FreeMarkerConfigurationFactory.class }) // 需要freemarker.template.Configuration和FreeMarkerConfigurationFactory這兩個類存在在classpath中才會進行自動配置
@AutoConfigureAfter(WebMvcAutoConfiguration.class) // 本次自動配置需要依賴WebMvcAutoConfiguration這個配置類配置之后觸發。這個WebMvcAutoConfiguration內部會配置很多Wen基礎性的東西,比如RequestMappingHandlerMapping、RequestMappingHandlerAdapter等
@EnableConfigurationProperties(FreeMarkerProperties.class) // 使用FreeMarkerProperties類中的配置
public class FreeMarkerAutoConfiguration {

    private static final Log logger = LogFactory
            .getLog(FreeMarkerAutoConfiguration.class);

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private FreeMarkerProperties properties;

    @PostConstruct // 構造之后調用的方法,組要檢查模板位置是否存在
    public void checkTemplateLocationExists() {
        if (this.properties.isCheckTemplateLocation()) {
            TemplateLocation templatePathLocation = null;
            List<TemplateLocation> locations = new ArrayList<TemplateLocation>();
            for (String templateLoaderPath : this.properties.getTemplateLoaderPath()) {
                TemplateLocation location = new TemplateLocation(templateLoaderPath);
                locations.add(location);
                if (location.exists(this.applicationContext)) {
                    templatePathLocation = location;
                    break;
                }
            }
            if (templatePathLocation == null) {
                logger.warn("Cannot find template location(s): " + locations
                        + " (please add some templates, "
                        + "check your FreeMarker configuration, or set "
                        + "spring.freemarker.checkTemplateLocation=false)");
            }
        }
    }

    protected static class FreeMarkerConfiguration {

        @Autowired
        protected FreeMarkerProperties properties;

        protected void applyProperties(FreeMarkerConfigurationFactory factory) {
            factory.setTemplateLoaderPaths(this.properties.getTemplateLoaderPath());
            factory.setPreferFileSystemAccess(this.properties.isPreferFileSystemAccess());
            factory.setDefaultEncoding(this.properties.getCharsetName());
            Properties settings = new Properties();
            settings.putAll(this.properties.getSettings());
            factory.setFreemarkerSettings(settings);
        }

    }

    @Configuration
    @ConditionalOnNotWebApplication // 非Web項目的自動配置
    public static class FreeMarkerNonWebConfiguration extends FreeMarkerConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public FreeMarkerConfigurationFactoryBean freeMarkerConfiguration() {
            FreeMarkerConfigurationFactoryBean freeMarkerFactoryBean = new FreeMarkerConfigurationFactoryBean();
            applyProperties(freeMarkerFactoryBean);
            return freeMarkerFactoryBean;
        }

    }

    @Configuration // 自動配置的類
    @ConditionalOnClass(Servlet.class) // 需要運行在Servlet容器下
    @ConditionalOnWebApplication // 需要在Web項目下
    public static class FreeMarkerWebConfiguration extends FreeMarkerConfiguration {

        @Bean
        @ConditionalOnMissingBean(FreeMarkerConfig.class)
        public FreeMarkerConfigurer freeMarkerConfigurer() {
            FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
            applyProperties(configurer);
            return configurer;
        }

        @Bean
        public freemarker.template.Configuration freeMarkerConfiguration(
                FreeMarkerConfig configurer) {
            return configurer.getConfiguration();
        }

        @Bean
        @ConditionalOnMissingBean(name = "freeMarkerViewResolver") // 沒有配置freeMarkerViewResolver這個bean的話,會自動構造一個freeMarkerViewResolver
        @ConditionalOnProperty(name = "spring.freemarker.enabled", matchIfMissing = true) // 配置文件中開關開啟的話,才會構造
        public FreeMarkerViewResolver freeMarkerViewResolver() {
            // 構造了freemarker的ViewSolver,這就是一開始我們分析的為什么沒有設置ViewResolver,但是最后卻還是存在的原因
            FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
            this.properties.applyToViewResolver(resolver);
            return resolver;
        }

    }
}

freemarker對應的配置類:

@ConfigurationProperties(prefix = "spring.freemarker") // 使用配置文件中以spring.freemarker開頭的配置
public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {
    public static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/"; // 默認路徑

    public static final String DEFAULT_PREFIX = ""; // 默認前綴

    public static final String DEFAULT_SUFFIX = ".ftl"; // 默認后綴

    ...

}

下面是官網上的freemarker配置:

# FREEMARKER (FreeMarkerAutoConfiguration)
spring.freemarker.allow-request-override=false # Set whether HttpServletRequest attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.freemarker.allow-session-override=false # Set whether HttpSession attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.freemarker.cache=false # Enable template caching.
spring.freemarker.charset=UTF-8 # Template encoding.
spring.freemarker.check-template-location=true # Check that the templates location exists.
spring.freemarker.content-type=text/html # Content-Type value.
spring.freemarker.enabled=true # Enable MVC view resolution for this technology.
spring.freemarker.expose-request-attributes=false # Set whether all request attributes should be added to the model prior to merging with the template.
spring.freemarker.expose-session-attributes=false # Set whether all HttpSession attributes should be added to the model prior to merging with the template.
spring.freemarker.expose-spring-macro-helpers=true # Set whether to expose a RequestContext for use by Spring's macro library, under the name "springMacroRequestContext".
spring.freemarker.prefer-file-system-access=true # Prefer file system access for template loading. File system access enables hot detection of template changes.
spring.freemarker.prefix= # Prefix that gets prepended to view names when building a URL.
spring.freemarker.request-context-attribute= # Name of the RequestContext attribute for all views.
spring.freemarker.settings.*= # Well-known FreeMarker keys which will be passed to FreeMarker's Configuration.
spring.freemarker.suffix= # Suffix that gets appended to view names when building a URL.
spring.freemarker.template-loader-path=classpath:/templates/ # Comma-separated list of template paths.
spring.freemarker.view-names= # White list of view names that can be resolved.

所以說一開始我們加入了一個spring-boot-starter-freemarker依賴,這個依賴中存在freemarker的lib,滿足了FreeMarkerAutoConfiguration中的ConditionalOnClass里寫的freemarker.template.Configuration.class這個類存在於classpath中。

所以就構造了FreeMarkerAutoConfiguration里的ViewResolver,這個ViewResolver被自動加入到SpringMVC中。

同樣地,如果我們要使用velocity模板,springboot內部也有velocity的自動配置類VelocityAutoConfiguration,原理是跟freemarker一樣的。

其他:

Mybatis的autoconfigure是Mybatis提供的springboot的自動配置模塊,由於springboot官方沒有提供mybatis的自動化配置模塊,所以mybatis自己寫了這么一個模塊,觀察它的源碼,發現基本上跟freemarker的autoconfigure模塊一樣,只需要構造對應的實例即可。

總結:

springboot內部提供了很多自動化配置的類,這些類會判斷classpath中是否存在自己需要的那個類,如果存在則會自動配置相關的配置,否則就不會自動配置。

如果我們需要使用一些框架,只需要加入依賴即可,這些依賴內部是沒有代碼的,只是一些對應框架需要的lib,有了這些lib就會觸發自動化配置,於是就能使用框架了。

這一點跟當時看springmvc的時候對response進行json或xml渲染的原理相同。springmvc中的requestmapping注解加上responsebody注解后會返回xml或者json,如果依賴中加入jackson依賴就會轉換成json,如果依賴中加入xstream依賴就會轉換成xml。當然,前提是springmvc中有了這兩種依賴的HttpMessageConverter代碼,這個HttpMessageConverter代碼就相當於springboot中的各種AutoConfiguration。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM