公司项目需要迁移Spring Boot上面,迁移过程中遇到一些问题和解决方案,在此记录下方便其他人查看,Spring Boot介绍官方和网上有很多资料可供学习,本文以Maven为例介绍Spring Boot;另外,项目中涉及配置信息等通过config-toolkit集中管理配置;
- Spring Boot基础;
- 配置 config-toolkit;
- 配置 druid、dubbo和redis;
- 主意事项;
1 Spring Boot目录结构
1.1 完整pom.xml内容
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> </parent> <groupId>com.test</groupId> <artifactId>spring-boot-test</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>1.8</java.version> <alibaba.druid.version>1.0.15</alibaba.druid.version> <config-toolkit.version>3.2.2-RELEASE</config-toolkit.version> <dubbo.version>2.5.8</dubbo.version> <zookeeper.version>3.4.6</zookeeper.version> <zkclient.version>0.1</zkclient.version> </properties> <dependencies> <!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--Unit Tests--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--actuator--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>com.dangdang</groupId> <artifactId>config-toolkit</artifactId> <version>${config-toolkit.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- spring-boot-starter-jdbc 模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${alibaba.druid.version}</version> </dependency> <!--devtools--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--dubbo--> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>${dubbo.version}</version> <exclusions> <exclusion> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </exclusion> <exclusion> <artifactId>spring</artifactId> <groupId>org.springframework</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>${zookeeper.version}</version> </dependency> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>${zkclient.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>2.0.4</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <!-- <configuration> <excludeDevtools>false</excludeDevtools> </configuration>--> </plugin> </plugins> </build> </project>
1.2 启动类Application
注:main
方法所在的这个主要的配置类配置在根包名下,否则启动时无法找到相关依赖;
package com.test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author Administrator * @title: * @package com.test * @copyright: Copyright (c) 2018 * @date 2018/1/4 0007 12:02 */ @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
@SpringBootApplication注解 包含@Configuration + @EnableAutoConfiguration + @ComponentScan。其中@EnableAutoConfiguration注释,此注释自动载入应用程序所需的所有Bean。日志级别为debug时,会看到~~
1.3 内嵌 Server 配置
Spring Boot 其默认是集成web容器的,启动方式由像普通Java程序一样,main函数入口启动。其内置Tomcat容器或Jetty容器,具体由配置来决定(默认Tomcat)。通过配置文件(application.yml)的方式类修改相关server配置。
2 配置Config Toolkit
Config Toolkit 参考https://github.com/dangdangdotcom/config-toolkit/wiki
2.1 添加maven依赖
... <dependency> <groupId>com.dangdang</groupId> <artifactId>config-toolkit</artifactId> <version>${config-toolkit.version}</version> </dependency> ...
2.2 添加配置信息application.yml
config-toolkit: connect-str: 127.0.0.1:2181 #zk地址 root-node: /config/test version: 1.0.0
2.3 添加config bean
package com.test.configuration; import com.dangdang.config.service.ConfigGroup; import com.dangdang.config.service.zookeeper.ZookeeperConfigGroup; import com.dangdang.config.service.zookeeper.ZookeeperConfigProfile; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @package com.test.configuration * @copyright: Copyright (c) 2018 * @date 2018/1/4 0005 18:13 */ @Configuration public class ConfigToolkitConfig { @Bean public ZookeeperConfigProfile getConfigProfile(@Value("${config-toolkit.connect-str}") String connectStr, @Value("${config-toolkit.root-node}") String rootNode, @Value("${config-toolkit.version}") String version) { return new ZookeeperConfigProfile(connectStr, rootNode, version); } /** * 数据源等配置 * * @param configProfile * @return */ @Bean("jdbcGroup") public ConfigGroup getApplicationGroup(ZookeeperConfigProfile configProfile) { return new ZookeeperConfigGroup(configProfile, "jdbc"); } /** * redis配置 * * @param configProfile * @return */ @Bean("redisGroup") public ConfigGroup getRedisGroup(ZookeeperConfigProfile configProfile) { return new ZookeeperConfigGroup(configProfile, "redis"); } /** * dubbo 配置 * @param configProfile * @return */ @Bean("dubboGroup") public ConfigGroup getDubboGroup(ZookeeperConfigProfile configProfile) { return new ZookeeperConfigGroup(configProfile, "dubbo"); } }
以上ConfigToolkit配置成功,并且相关配置信息,已导入。然后在需要使用的地方直接注入对应的map
@Value("#{publicConfig}") private Map<String, String> publicConfig;
或者
@Value("#{redisGroup['redis.url']}") private String host;
3 配置Druid
3.1 添加maven依赖
... <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- spring-boot-starter-jdbc 模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${alibaba.druid.version}</version> </dependency> ...
3.2 添加config bean
package com.test.configuration; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.support.http.StatViewServlet; import com.alibaba.druid.support.http.WebStatFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; import java.sql.SQLException; /** * @package com.test.configuration * @copyright: Copyright (c) 2018 * @date 2018/1/4 0006 14:58 */ @Configuration public class DruidConfig { private Logger logger = LoggerFactory.getLogger(DruidConfig.class); @Value("#{jdbcGroup['jdbc.url']}") private String dbUrl; @Value("#{jdbcGroup['jdbc.username']}") private String username; @Value("#{jdbcGroup['jdbc.password']}") private String password; @Value("#{jdbcGroup['druid.initialSize']}") private int initialSize; @Value("#{jdbcGroup['druid.minIdle']}") private int minIdle; @Value("#{jdbcGroup['druid.maxActive']}") private int maxActive; @Value("#{jdbcGroup['druid.maxWait']}") private int maxWait; @Value("#{jdbcGroup['druid.timeBetweenEvictionRunsMillis']}") private int timeBetweenEvictionRunsMillis; @Value("#{jdbcGroup['druid.minEvictableIdleTimeMillis']}") private int minEvictableIdleTimeMillis; @Value("#{jdbcGroup['druid.validationQuery']}") private String validationQuery; @Value("#{jdbcGroup['druid.testWhileIdle']}") private boolean testWhileIdle; @Value("#{jdbcGroup['druid.testOnBorrow']}") private boolean testOnBorrow; @Value("#{jdbcGroup['druid.testOnReturn']}") private boolean testOnReturn; @Value("#{jdbcGroup['druid.filters']}") private String filters; @Bean public ServletRegistrationBean druidServlet() { ServletRegistrationBean reg = new ServletRegistrationBean(); reg.setServlet(new StatViewServlet()); reg.addUrlMappings("/druid/*"); reg.addInitParameter("loginUsername", username); reg.addInitParameter("loginPassword", password); return reg; } @Bean public FilterRegistrationBean filterRegistrationBean() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new WebStatFilter()); filterRegistrationBean.addUrlPatterns("/*"); filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); filterRegistrationBean.addInitParameter("profileEnable", "true"); return filterRegistrationBean; } @Bean public DataSource druidDataSource() { DruidDataSource datasource = new DruidDataSource(); datasource.setUrl(dbUrl); datasource.setUsername(username); datasource.setPassword(password); datasource.setInitialSize(initialSize); datasource.setMinIdle(minIdle); datasource.setMaxActive(maxActive); datasource.setMaxWait(maxWait); datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); datasource.setValidationQuery(validationQuery); datasource.setTestWhileIdle(testWhileIdle); datasource.setTestOnBorrow(testOnBorrow); datasource.setTestOnReturn(testOnReturn); try { datasource.setFilters(filters); } catch (SQLException e) { logger.error("druid configuration initialization filter", e); } return datasource; } }
3.3 数据库查询
package com.test.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.util.List; import java.util.Map; /** * @copyright: Copyright (c) 2018 * @date 2017/1/5 0005 15:08 */ @Repository public class UserRepository { @Autowired private JdbcTemplate jdbcTemplate; public List<Map<String, Object>> findById(String id) { return jdbcTemplate.queryForList("select id,nick_name from user where id=?", new Object[]{id}); } }
4 配置dubbo
在Spring Boot中使用Dubbo,不需要使用xml的方式来配置生产者和消费者,本文使用@Bean注解的方式来进行配置。
4.1 添加maven依赖
... <!--dubbo--> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>${dubbo.version}</version> <exclusions> <exclusion> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </exclusion> <exclusion> <artifactId>spring</artifactId> <groupId>org.springframework</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>${zookeeper.version}</version> </dependency> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>${zkclient.version}</version> </dependency> ...
4.2 添加config bean
package com.test.api.configuration; import com.alibaba.dubbo.config.*; import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Administrator * @title: spring-boot-test * @package com.test.configuration * @copyright: Copyright (c) 2017 * @date 2017/12/15 0015 11:53 */ @Configuration @DubboComponentScan(basePackages = { "com.test.api.controller", "com.test.api.service" }) public class DubboBaseConfig { @Value("#{dubboGroup['dubbo.registry.address']}") private String address; @Value("#{dubboGroup['dubbo.protocol.port']}") private int dubboPort; @Value("#{dubboGroup['dubbo.provider.timeout']}") private int timeout; @Value("#{dubboGroup['dubbo.provider.retries']}") private int retries; @Value("#{dubboGroup['dubbo.provider.loadbalance']}") private String loadbalance; @Value("#{dubboGroup['dubbo.application']}") private String applicationName; @Bean public ApplicationConfig application() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName(applicationName); return applicationConfig; } @Bean public RegistryConfig registry() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress(address); registryConfig.setProtocol("zookeeper"); registryConfig.setClient("curator"); return registryConfig; } @Bean public ProtocolConfig protocol() { ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setPort(dubboPort); protocolConfig.setName("dubbo"); return protocolConfig; } /** * dubbo 监控中心 */ /*@Bean public MonitorConfig monitorConfig() { MonitorConfig mc = new MonitorConfig(); mc.setProtocol("registry"); return mc; } @Bean public ReferenceConfig referenceConfig() { ReferenceConfig rc = new ReferenceConfig(); rc.setMonitor(monitorConfig()); return rc; }*/ }
4.3 Dubbo生产者配置,需要继承Dubbo基础配置
@Configuration public class ExportServiceConfig extends DubboBaseConfig { @Bean public ServiceBean<Person> personServiceExport(Person person) { ServiceBean<Person> serviceBean = new ServiceBean<Person>(); serviceBean.setProxy("javassist"); serviceBean.setVersion("myversion"); serviceBean.setInterface(Person.class.getName()); serviceBean.setRef(person); serviceBean.setTimeout(5000); serviceBean.setRetries(3); return serviceBean; } }
4.4 Dubbo消费者配置,需要继承Dubbo基础配置
消费端由于spring 扫描的时候根本无法识别@Reference ,同一方面,dubbo的扫描也无法识别Spring @Controller ,所以增加@DubboComponentScan(basePackages = { "com.test.api.controller", "com.test.api.service" })预防扫dubbo的服务出现空指针。
@Configuration public class ReferenceConfig extends DubboBaseConfig { @Bean public ReferenceBean<Person> person() { ReferenceBean<Person> ref = new ReferenceBean<>(); ref.setVersion("myversion"); ref.setInterface(Person.class); ref.setTimeout(5000); ref.setRetries(3); ref.setCheck(false); return ref; } }
或者在消费端 通过@Reference注解,注入dubbo服务;
5 配置redis
5.1 添加maven依赖
<!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
5.2添加config bean
package com.test.api.configuration; import com.test.service.JedisService; import com.test.service.impl.JedisServiceImpl; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; /** * @package com.test.configuration * @copyright: Copyright (c) 2017 * @date 2017/12/6 0006 15:34 */ @Configuration public class RedisConfig extends BaseRedisConfig { @Value("#{redisGroup['redis.url']}") private String host; @Value("#{redisGroup['redis.port']}") private int port; @Value("#{redisGroup['redis.timeout']}") private int timeout; @Value("#{redisGroup['redis.maxTotal']}") private int maxTotal; @Value("#{redisGroup['redis.maxIdle']}") private int maxIdle; @Value("#{redisGroup['redis.maxWait']}") private long maxWaitMillis; @Value("#{redisGroup['redis.passwd']}") private String password; private boolean testOnBorrow = true; @Bean public JedisPoolConfig getPoolConfig() { return this.getPoolConfig(maxIdle, maxWaitMillis, testOnBorrow, maxTotal); } @Bean public JedisPool getJedisPool() { return new JedisPool(getPoolConfig(), host, port, timeout, password); } @Bean public JedisConnectionFactory getJedisConnectionFactory() { return getJedisConnectionFactory(getPoolConfig(), host, password); } /** * JedisService 本地的redis服务,JedisServiceImpl实现类 * @return * @throws Exception */ @Bean(name = "jedisService") public JedisService getJedisService() throws Exception { JedisServiceImpl jd = new JedisServiceImpl(); jd.setJedisPool(getJedisPool()); return jd; } }
由于项目中有时会配置多个redis源,需要在ConfigToolkitConfig文件中配置多个reidsAGroup,config redisBean时使用reidsAGroup,同时JedisService需要注入
/** * JedisService 本地的redis服务,JedisServiceImpl实现类 * @return * @throws Exception */ @Bean(name = "jedisAService") public JedisService getAJedisService() throws Exception { JedisServiceImpl jd = new JedisServiceImpl(); jd.setJedisPool(getJedisPool()); return jd; }
6 Spring Boot添加自定义Filter
6.1编写自己的Filter
package com.test.api.aop; import com.test.api.log.LogToEmail; import org.springframework.beans.factory.annotation.Autowired; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebInitParam; import java.io.IOException; @WebFilter(urlPatterns = "/*", filterName = "timingFilter", initParams = { @WebInitParam(name = "encoding", value = "UTF-8"), @WebInitParam(name = "forceEncoding", value = "true") }) @Order(1) public class TimingFilter implements Filter { @Autowired private LogToEmail logToEmail; @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //TODO } @Override public void init(FilterConfig filterConfig) throws ServletException { } }
需要使用@WebFilter注解,其中@Order注解表示执行过滤顺序,值越小,越先执行;另外,war包形式如果自定义Filter如果引用其他服务,@Resource 注入无效,tomcat会识别此注解,so用@Autowired替换;
我们在spring-boot的入口处加上如下注解@ServletComponentScan:
package com.test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author Administrator * @title: * @package com.test * @copyright: Copyright (c) 2018 * @date 2018/1/4 0007 12:02 */ @SpringBootApplication @ServletComponentScan public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
7 注意事项
-
启动类Application所在的这个主要的配置类配置在根包名下,否则启动时无法找到相关依赖;
- springboot war包形式如果自定义Filter如果引用其他服务,@Resource 注入无效,tomcat会识别此注解,用@Autowired替换;
- 消费端由于spring 扫描的时候根本无法识别@Reference ,同一方面,dubbo的扫描也无法识别Spring @Controller ,所以增加@DubboComponentScan(basePackages = { "com.test.api.controller", "com.test.api.service" })预防扫dubbo的服务出现空指针。
-
springboot解决Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 问题jar方式运行,由于springboot默认tomcat 就是将所有的参数都进行编码;例外一种,换容器jetty;
后续会把相关demo包上传