Spring Boot 学习


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

猜你喜欢

转载自fuyu.iteye.com/blog/2406769