从SpringMVC迁移到SpringBoot

2018/10/19

SpringBoot是大势所趋,个人认为

  1. SpringBoot简化了开发环境的搭建,包括依赖包的管理,配置的管理等;

  2. 便于应用微服务架构,从单体SpringBoot到SpringCloud微服务架构体系是平滑的;

本文记录个人将某个项目从SpringMVC迁移到SpringBoot所关注的几个方面。

数据库层迁移(Mybatis)

1.数据源变更

SpringBoot项目中很少使用XML配置,我们的主要工作就是将SpringMVC项目中的XML配置转化为对应的Java配置。

SpringMVC的XML数据源配置:

<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
		p:driverClass="${db.driverClass}" p:jdbcUrl="${db.jdbcUrl}" p:user="${db.user}"
		p:password="${db.password}" p:initialPoolSize="${db.initialPoolSize}"
		p:minPoolSize="${db.minPoolSize}" p:maxPoolSize="${db.maxPoolSize}"
		p:preferredTestQuery="${db.preferredTestQuery}" p:testConnectionOnCheckin="true"
		p:idleConnectionTestPeriod="300" destroy-method="close"/>

SpringBoot数据源配置:

@Configuration
public class DataSourceConfiguration {

    @Value("${db.driverClass}")
    private String jdbcDriver;

    @Value("${db.jdbcUrl}")
    private String jdbcUrl;

    @Value("${db.user}")
    private String jdbcUsername;

    @Value("${db.password}")
    private String jdbcPassword;

    @Value("${db.preferredTestQuery}")
    private String preferredTestQuery;

    @Value("${db.initialPoolSize}")
    private Integer initialPoolSize;

    @Value("${db.minPoolSize}")
    private Integer minPoolSize;

    @Value("${db.maxPoolSize}")
    private Integer maxPoolSize;

    @Bean(destroyMethod = "close")
    public ComboPooledDataSource createDataSource() throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(jdbcDriver);
        dataSource.setJdbcUrl(jdbcUrl);
        dataSource.setUser(jdbcUsername);
        dataSource.setPassword(jdbcPassword);
        dataSource.setPreferredTestQuery(preferredTestQuery);
        dataSource.setInitialPoolSize(initialPoolSize);
        dataSource.setMaxPoolSize(maxPoolSize);
        dataSource.setMinPoolSize(minPoolSize);
        dataSource.setTestConnectionOnCheckin(true);
        dataSource.setIdleConnectionTestPeriod(300);
        return dataSource;
    }

}

2.Mybatis的SqlSessionFactory

SpringMVC的SqlSessionFactory定义如下

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="wrappedDataSource"></property>
        <property name="configLocation" value="classpath:spring/mybatis-config.xml"></property>
        <property name="mapperLocations">
            <array>
                <value>classpath:mybatis/*.xml</value>
            </array>
        </property>
        <!-- 分页插件 -->
        <property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
                        <!-- config params as the following -->
                        <value>
                            helperDialect=mysql
                        </value>
                    </property>
                </bean>
            </array>
        </property>
    </bean>

转化为SpringBoot,需要引入mybatis的自动配置jar包,分页jar包。

在POM文件中添加如下jar包:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.8</version>
</dependency>

并在application.properties中添加如下配置:

mybatis.config-location=classpath:mybatis-config.xml
mybatis.mapper-locations=classpath:mapper/*.xml
pagehelper.helper-dialect=mysql

SqlSessionFactory能自动找到Spring容器中的DataSource 实例。

还需要告诉Mybatis从哪个包里搜索Mapper实例,在启动Class上使用注解@MapperScan完成。

服务层迁移

服务层主要是事务的迁移,在SpringMVC中,在XML文件中定义切片,并根据特定的异常类型进行事务回滚(在这里是ServiceException)。

<aop:config>
        <aop:pointcut id="txPointCutDef" expression="this(com.jhcl.ucclub.core.TransactionalAspectAwareService)"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCutDef"/>
    </aop:config>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="list*" read-only="true"/>
            <tx:method name="search*" read-only="true"/>
            <tx:method name="select*" read-only="true"/>
            <tx:method name="view*" read-only="true"/>
            <tx:method name="*" read-only="false" propagation="REQUIRED"
                       rollback-for="com.jhcl.ucclub.core.ServiceException"/>
        </tx:attributes>
    </tx:advice>

SpringBoot中使用Java Config完成:

@Aspect
@Configuration
public class TransationAdviceConfig {

    private static final int TX_METHOD_TIMEOUT = 5;

    private static final String AOP_POINTCUT_EXPRESSION = "execution(* com.***.service.impl..*.*.*(..))";

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Bean
    public TransactionInterceptor txAdvice() {

        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();

        RuleBasedTransactionAttribute readOnlyRule = new RuleBasedTransactionAttribute();
        readOnlyRule.setReadOnly(true);
        readOnlyRule.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);

        RuleBasedTransactionAttribute requireRule = new RuleBasedTransactionAttribute();
        requireRule.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(ServiceException.class)));
        requireRule.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        requireRule.setTimeout(TX_METHOD_TIMEOUT);
        Map<String, TransactionAttribute> txMap = new HashMap<>();
        txMap.put("add*", requireRule);
        txMap.put("save*", requireRule);
        txMap.put("insert*", requireRule);
        txMap.put("update*", requireRule);
        txMap.put("delete*", requireRule);
        txMap.put("remove*", requireRule);
        txMap.put("get*", readOnlyRule);
        txMap.put("query*", readOnlyRule);
        txMap.put("find*", readOnlyRule);
        txMap.put("select*", readOnlyRule);
        source.setNameMap(txMap);
        TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, source);
        return txAdvice;
    }

    @Bean
    public Advisor txAdviceAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
        return new DefaultPointcutAdvisor(pointcut, txAdvice());
    }
}

还需要在启动Class上使用注解@EnableTransactionManagement启用事务管理。

Web层迁移

SpringMVC消息转换器以及文件上传配置:

<annotation-driven>	
		<message-converters >
			<beans:bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
				<!--解决 HttpMediaTypeNotAcceptableException: Could not find acceptable representation -->
				<beans:property name="supportedMediaTypes">
					<beans:list>
                        <beans:value>text/html;charset=UTF-8</beans:value>
                        <beans:value>text/json;charset=UTF-8</beans:value>
                        <beans:value>application/json;charset=UTF-8</beans:value>
						<beans:value>application/x-www-form-urlencoded;charset=UTF-8</beans:value>
						<beans:value>application/x-www-form-urlencoded</beans:value>
					</beans:list>
				</beans:property>
			</beans:bean>
    	</message-converters>
</annotation-driven>
	
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<beans:property name="maxUploadSize" value="104857600" />
		<beans:property name="maxInMemorySize" value="4096" />
		<beans:property name="defaultEncoding" value="UTF-8"></beans:property>
		<beans:property name="uploadTempDir" value="upload/temp"></beans:property>
</beans:bean>

SpringBoot对应的Java Config:

@Configuration
public class MvcConfiguration extends WebMvcConfigurerAdapter {

    public void configureMessageConverters(List<HttpMessageConverter<?>> converters){
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        List<MediaType> list = new ArrayList<>();
        list.add(MediaType.TEXT_HTML);
        list.add(MediaType.APPLICATION_JSON_UTF8);
        list.add(MediaType.APPLICATION_JSON);
        list.add(MediaType.APPLICATION_FORM_URLENCODED);
        converter.setSupportedMediaTypes(list);
        converters.add(converter);
    }

    @Bean(name = {"multipartResolver"})
    public MultipartResolver multipartResolver() throws IOException {
        CommonsMultipartResolver commonsMultipartResolver=new CommonsMultipartResolver();
        commonsMultipartResolver.setDefaultEncoding("UTF-8");
        commonsMultipartResolver.setMaxUploadSize(10485760L);
        commonsMultipartResolver.setMaxInMemorySize(4096);
        commonsMultipartResolver.setUploadTempDir(new FileSystemResource("upload/temp"));
        return commonsMultipartResolver;
    }
}

Web.xml 配置文件迁移

  1. 由于注解都会被@SpringBootApplication扫描到并且加入到Spring容器中,消除了XML文件中的bean定义,所以ContextLoaderListener不需要了。

  2. DispatcherServlet也不需要配置了,SpringBoot会自动配置

  3. UTF-8编码过滤器改为使用配置实现:

# Charset of HTTP requests and responses. Added to the "Content-Type" header if not set explicitly.
spring.http.encoding.charset=UTF-8
# Enable http encoding support.
spring.http.encoding.enabled=true
# Force the encoding to the configured charset on HTTP requests and responses.
spring.http.encoding.force=true
  1. 自定义Filter在Configuration类中配置,并且可以指定顺序:
@Bean
    public FilterRegistrationBean testFilterRegistration() {

        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new MyFilter());
        registration.addUrlPatterns("/*");
        registration.addInitParameter("paramName", "paramValue");
        registration.setName("MyFilter");
        registration.setOrder(1);
        return registration;
    }

其他

  1. SpringBoot 2.0开始在将Java对象转换为JSON时,会自动将Date类型字段转为UTC格式,可以通知修改配置改为时间戳:
spring.jackson.serialization.write-dates-as-timestamps=true

总结

以上,便是从SpringMVC迁移到SpringBoot的一些关注点,后续还会根据情况补充更新。

(转载本站文章请注明作者和出处 湘江鸿的博客

Post Directory