Spring工程转成Spring Boot工程

来源:互联网 发布:yum安装kernel devel 编辑:程序博客网 时间:2024/06/16 18:21

Spring工程转成Spring Boot工程

最近小研究了一下SpringBoot,将平时用到的框架工程由纯Spring替换成SpringBoot。本文就是记录这次变动过程中的一些知识点和坑。

什么是SpringBoot

本人比较菜,没法用一句话给SpringBoot下一个定义,或者一句话说出SpringBoot的特点。以下是我对SpringBoot的一些理解。

  • 其实SpringBoot的基础就是Spring,它在Spring的基础上做了很多其它的工作,如取消了applicationContext.xml配置项的使用,通过application.properties或者application.yml,以及Java Configuration方式来配置。如果是web工程可以不用web.xml(这个其实是Serverlet 3.0的特性)
  • 内嵌了web容器,可以通过打成jar包,然后通过java -jar的方式启动工程。
  • 各种starter工程帮我们做了很多事情。比如你想开发一个web工程,可以通过引入spring-boot-starter-web工程来进行配置。这样你就不需要配置很多,比如对MessageConverter的支持等等一些跟web相关的配置。(在替换过程中确实体会到了这个好处,很方便)正如很多教程里说的,这种starter工程还帮助我们屏蔽了不同lib包版本不兼容的问题,你只需要指定你想要用的SpringBoot版本,SpringBoot自己会帮你处理这些依赖。

替换历程

初始工程

想要使用SpringBoot其实很简单,可以去Spring Boot网站上,通过网站生成你想要的SpringBoot初始工程,在这里你可以选择构建工具Maven或者Gradle,SpringBoot的版本,工程相关信息,以及需要的SpringBoot starter。

有了工程,就可以开始迁移了。比如我替换的是一个Web工程,所以我引入了spring-boot-starter-web。如果你看spring-boot-starter-web的pom文件,你会发现它包含了spring web,spring mvc,jackson等一些列跟web开发相关的spring依赖包。这些它都帮我们干了,很方便。

数据库

接下来替换的是数据库,我使用的spring的jdbcTemplate进行数据库访问。数据库用的是MySQL数据库。首先引入另外一个starter–spring-boot-starter-jdbc,让工程对jdbc有支持,这里面就有spring-jdbc的依赖包。我们就可以在工程里使用JDBCTemplate了。然后需要配置数据源。按照原来Spring的方式,我们可以在配置文件中加上这一段:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:tx="http://www.springframework.org/schema/tx"       xsi:schemaLocation="        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">    <bean id="transactionManager"          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <property name="dataSource" ref="dataSource"></property>    </bean>    <tx:annotation-driven transaction-manager="transactionManager"                          proxy-target-class="true"/>    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">        <property name="dataSource" ref="dataSource"/>    </bean>    <!--配置数据源 -->    <bean id="dataSource"          class="org.springframework.jdbc.datasource.DriverManagerDataSource">        <property name="driverClassName">            <value>com.mysql.jdbc.Driver</value>        </property>        <property name="url">            <value>jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8            </value>        </property>        <property name="username">            <value>root</value>        </property>        <property name="password">            <value>11111111</value>        </property>    </bean></beans>

这么一大段配置文件,其实就干了三件事:

  • 生成一个dataSource
  • 生成一个jdbcTemplate
  • 开启事物

但是使用SpringBoot之后不需要配置这么多了。jdbcTemplate在前面已经说过了,只要引入了starter-jdbc就可以直接使用了。那么我们还缺少dataSource和事务。

dataSource很好配置,我使用的是application.yml。

spring:  datasource:    driver-class-name: com.mysql.jdbc.Driver    url: jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8    username: root    password: 11111111

只要上面这一段,就配置好数据源了。是不是很简单。

那么事务呢?开启事务很简单,需要一个注解@EnableTransactionManagement这个注解可以跟@SpringBootApplication放在一起,表示开启事务管理。然后在具体使用事务的地方使用@Transactional即可。是不是依然很方便?

JNDI

在我们的项目工程中,使用到了JNDI去配置数据源。而且由于jndi的读取方式被写死在了底层依赖包里,我们没法修改,为了能够方便启动项目,还需要对jndi进行处理。(正常工作run Application.java就可以启动项目。而如果不处理JNDI,就得打包成war,然后放入外部的web容器里,再启动外部容器)

Spring Boot是自带web容器的。(web容器在哪我也不知道)这样导致我们不太好配置JNDI数据源。但是Spring Boot为内部的web容器提供了一个加入JNDI的方式。

    @Bean    public TomcatEmbeddedServletContainerFactory tomcatFactory() {        return new TomcatEmbeddedServletContainerFactory() {            @Override            protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(                    Tomcat tomcat) {                tomcat.enableNaming();                return super.getTomcatEmbeddedServletContainer(tomcat);            }            @Override            protected void postProcessContext(Context context) {                ContextResource resource = new ContextResource();                resource.setName("cache/memcached");                resource.setType(DataSource.class.getName());                resource.setProperty("auth", "Container");                resource.setProperty("type", "com.letvpicture.common.memcached.client.MemcacheClient");                resource.setProperty("factory", "com.letvpicture.common.memcached.client.MemcacheClientFactory");                resource.setProperty("poolSize", "40");                resource.setProperty("serverAddresses", "127.0.0.1:11211");                context.getNamingResources().addResource(resource);                resource = new ContextResource();                resource.setName("data/mongodb");                resource.setType(DataSource.class.getName());                resource.setProperty("auth", "Container");                resource.setProperty("type", "com.letvpicture.common.mongo.client.MongoDataBaseClient");                resource.setProperty("factory", "com.letvpicture.common.mongo.client.MongoClientFactory");                resource.setProperty("closeMethod", "shutdown");                resource.setProperty("host", "localhost");                resource.setProperty("port", "27017");                resource.setProperty("authDb", "wz");                resource.setProperty("username", "admin");                resource.setProperty("password", "111");                resource.setProperty("minPoolSize", "10");                resource.setProperty("maxPoolSize", "100");                resource.setProperty("maxWaitTime", "10000");                context.getNamingResources().addResource(resource);            }        };    }    @Bean(destroyMethod="")    public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {        JndiObjectFactoryBean bean = new JndiObjectFactoryBean();        bean.setJndiName("java:comp/env/jdbc/membership");        bean.setProxyInterface(DataSource.class);        bean.setLookupOnStartup(false);        bean.afterPropertiesSet();        return (DataSource)bean.getObject();    }

正如上面代码所示,你可以自己向Embedded容器里写入JNDI数据源。

  • 通过@Bean注解,可以向Spring的Bean容器里注入Bean实体。TomcatEmbeddedServletContainerFactory用于装载一些信息。
  • 内嵌容器默认是不开启JNDI功能的,所以需要通过tomcat.enableNaming();来开启。
  • 在postProcessContext方法中写入自己需要的JNDI信息。我们的项目用了MongoDB和Memcached两个JNDI数据源。
  • jndiDataSource是为数据库提供数据源的。所以这里必须返回的是数据库的数据源。

写入了这些代码,就可以正常使用MongoDB和Memcached数据源了。

Profile

在企业应用中,Profile很常见,在开发,测试和生产环境需要应用不同的配置。在使用SpringBoot的时候这个配置很简单。

spring:  profiles:    active: dev---spring:  profiles: dev---spring:  profiles: production---spring:  profiles: test

SpringBoot通过---来区分区域,最上面是通用配置以及表明默认使用的是哪个Profile。

Zookeeper的启动

正如上一节看到的,原来写在applicationContext.xml里的配置,都可以用@Bean的方式配置进Bean容器中(当然类上也需要一个@Component或者其它的注解,让Spring框架可以识别它)。我们的ZK的启动是有一个单独的启动类,它监听了ApplicationEvent,然后通过Profile中配置的serverAddresses属性来链接ZK。最初我的想法是这很简单嘛。

    @ConfigurationProperties(prefix = "zkConfig")    @Bean    public SpringConfiguratorLauncher springConfiguratorLauncher() {        SpringConfiguratorLauncher launcher = new SpringConfiguratorLauncher();        return launcher;    }
spring:  profiles:    active: dev---spring:  profiles: dev  datasource:    driver-class-name: com.mysql.jdbc.Driver    url: jdbc:mysql://10.140.64.113:3306/membership_dadi?useUnicode=true&amp;characterEncoding=UTF-8    username: developer    password: Admin9@membership_dadizkConfig:  serverAddresses: localhost:2181

SpringConfiguratorLauncher里拥有启动zk的代码。然后通过@configuratoinProperties注解在SpringConfiguratorLauncher返回的时候注入serverAddresses属性。

  • @ConfigurationProperties是SpringBoot提供的属性注入注解,它可以读取yml文件中配置的属性。但是想要使用它,需要一个依赖。
        <!-- 支持 @ConfigurationProperties 注解 -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-configuration-processor</artifactId>            <optional>true</optional>        </dependency>
  • prefix是一个域的区分,如spring一样。对于zk的配置,我们定义的前缀是zkConfig。
  • 为了能正确注入属性,属性名必须和SpringConfiguratorLauncher中的成员变量名一样。

正当我满心认为这很easy的时候。发现zk启动失败了,告诉我serverAddresses为null。这是为啥?!网上的属性注入就是这么写的啊。查了很久我才发现,其实属性注入成功了,但是它注入的时机是在zk启动之后。我一脸懵逼。仔细看了看代码,发现zk的启动是通过监听AppliationEvent,而@ConfigurationProperties的注入功能是在这之后的。那么,怎么解决呢?

仔细查阅了资料,发现yml中属性值的读取其实是SpringBoot启动的第一步,也就是说,肯定早于Event的监听响应,程序里是可以读到具体的Profiles值的。那么我们怎么获取呢?

public class CustomConfigListener implements ApplicationListener<ApplicationEvent> {    @Override    public void onApplicationEvent(ApplicationEvent event) {        if (event instanceof ApplicationEnvironmentPreparedEvent) {            for(PropertySource<?> source : ((ApplicationEnvironmentPreparedEvent) event).getEnvironment().getPropertySources()){                if(source.getName().equals("applicationConfigurationProperties")){                    if (source instanceof EnumerablePropertySource) {                        for(String name : ((EnumerablePropertySource) source).getPropertyNames()){                            System.out.println(name+" :: "+ ((EnumerablePropertySource) source).getProperty(name));                            if("zkConfig.serverAddresses".equals(name)){                                Config.ZK_SERVER_ADDRESS = (((EnumerablePropertySource) source).getProperty(name).toString());                            }                        }                    }                }            }        }    }}

我自定义了一个Listener,监听了Spring的启动事件,在响应代码中,将zkConfig.serverAddresses缓存了下来。然后改动了SpringConfiguratorLauncher注入,将SpringConfiguratorLauncher中的属性从缓存中读取,并且在返回SpringConfiguratorLauncher实例之前就设置好。

AOP

我们的项目中拥有自己的换成方案,需要开启AOP功能。Spring工程开启AOP功能需要这样:

<aop:aspectj-autoproxy proxy-target-class="true"/>

只需要一行配置,就可以开启AOP配置。

在Spring Boot中,首先我们需要一个starter:

        <!-- 添加对AOP的支持 -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-aop</artifactId>        </dependency>

然后在配置中

spring:  profiles:    active: dev---spring:  profiles: dev  aop:    auto: true

在spring下配置aop的auto属性为true。

web.xml

本文最开始就说了,Spring Boot里没有web.xml文件,那么我们的Filter怎么配置呢?

在SpringBoot中这部分配置很简单,只要@Bean中注入的是一个Filter或者FilterRegistrationBean(SpringBoot对filter的封装,基本属性配置都有)。SpringBoot在启动的时候会自动识别这些Bean,然后加入到web的启动过程中。

    @Bean    public FilterRegistrationBean encodingFilter() {        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter("UTF-8", true);        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();        filterRegistrationBean.setFilter(encodingFilter);        List<String> list = new ArrayList<>();        list.add("/*");        filterRegistrationBean.setUrlPatterns(list);        return filterRegistrationBean;    }

那么没有web.xml,DispatchSeverlet也没法配置了。怎么办呢?其实你什么都不用做,SpringBoot在启动过程中已经帮你处理好了。

Spring MVC

跟Spring MVC相关的配置还有很多,如资源文件位置,视图解析器配置,拦截器,MessageConverter还有参数解析器。那么他们怎么配置呢?

Spring Boot提供了一个抽象类WebMvcConfigurerAdapter从名字就可以看出,它是为WebMvc配置准备的,里面有很多抽象方法

//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//package org.springframework.web.servlet.config.annotation;import java.util.List;import org.springframework.format.FormatterRegistry;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.validation.MessageCodesResolver;import org.springframework.validation.Validator;import org.springframework.web.method.support.HandlerMethodArgumentResolver;import org.springframework.web.method.support.HandlerMethodReturnValueHandler;import org.springframework.web.servlet.HandlerExceptionResolver;import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;import org.springframework.web.servlet.config.annotation.CorsRegistry;import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {    public WebMvcConfigurerAdapter() {    }    public void configurePathMatch(PathMatchConfigurer configurer) {    }    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {    }    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {    }    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {    }    public void addFormatters(FormatterRegistry registry) {    }    public void addInterceptors(InterceptorRegistry registry) {    }    public void addResourceHandlers(ResourceHandlerRegistry registry) {    }    public void addCorsMappings(CorsRegistry registry) {    }    public void addViewControllers(ViewControllerRegistry registry) {    }    public void configureViewResolvers(ViewResolverRegistry registry) {    }    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {    }    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {    }    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {    }    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {    }    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {    }    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {    }    public Validator getValidator() {        return null;    }    public MessageCodesResolver getMessageCodesResolver() {        return null;    }}

基本上你想配置的里面都有,如我的工程配置

@EnableWebMvc@Componentpublic class MvcConfiguration extends WebMvcConfigurerAdapter {    @Autowired    private ActDynamicConfig actDynamicConfig;    @Override    public void addResourceHandlers(ResourceHandlerRegistry registry) {        registry.addResourceHandler("/js/**")                .addResourceLocations("/WEB-INF/js/");        registry.addResourceHandler("/css/**")                .addResourceLocations("/WEB-INF/css/");        registry.addResourceHandler("/img/**")                .addResourceLocations("/WEB-INF/img/");    }    @Override    public void configureViewResolvers(ViewResolverRegistry registry) {        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver("/WEB-INF/jsp/", ".jsp");        registry.viewResolver(viewResolver);    }    @Override    public void addInterceptors(InterceptorRegistry registry) {        CSRFInterceptor csrfInterceptor = new CSRFInterceptor("", "");        InterceptorRegistration interceptorRegistration = registry.addInterceptor(csrfInterceptor);        interceptorRegistration.addPathPatterns("/**");        interceptorRegistration.excludePathPatterns("/test/**");    }    @Override    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {        converters.add(new AdvanceFastJsonHttpMessageConverter());    }    @Override    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {        argumentResolvers.add(new UidWebArgumentResolver());    }}

需要注意的是,需要使用注解@EnableWebMvc来开启这个配置的使用。

部署

终于来到了最后一步,我们的线上项目是通过war包部署的,而SpringBoot默认使用Application run就可以了。那么怎么将SpringBoot工程打包成war包呢?

首先还是老问题,我们没有web.xml,没有这个启动文件,web容器怎么启动呢?SpringBoot提供了一个抽象类SpringBootServletInitializer。它封装了对Serverlet 3.0的支持。我们需要继承这个类。

public class ActServletInitializer extends SpringBootServletInitializer {    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {        return builder.sources(Application.class);    }}

其实看代码,这里最终还是使用了启动用的Application类。只是从run main方式,变成了web容器去运行它。

然后是构建工具的支持。

    <build>        <plugins>            <!-- maven打包的时候告诉maven不需要web.xml,否则会报找不到web.xml错误 -->            <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-war-plugin</artifactId>                <version>2.4</version>                <configuration>                    <failOnMissingWebXml>false</failOnMissingWebXml>                </configuration>            </plugin>        </plugins>        <finalName>project name</finalName>    </build>

同时需要注意,因为我们平时都是用内嵌的tomcat(或者其它web容器),我们在构建的时候不需要将他们打入war包

        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-tomcat</artifactId>            <scope>provided</scope>        </dependency>        <dependency>            <groupId>org.apache.tomcat.embed</groupId>            <artifactId>tomcat-embed-jasper</artifactId>            <scope>provided</scope>        </dependency>

最后运行命令clean package spring-boot:repackage -Dmaven.test.skip即可以打出一个可部署的war包。

0 0
原创粉丝点击