公司项目需要迁移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包上传