首页 > 编程语言 > 详细

Spring Boot 学习之项目构建

时间:2017-09-27 09:23:52      阅读:352      评论:0      收藏:0      [点我收藏+]

最近做了外包,都是工程专业术语,前期熟悉项目看文档看的挺累的,闲暇时间自己学习一下Spring Cloud,找点乐趣.

就有了下面的小项目.

本项目是一个Spring boot项目.

一、nginx做LB

二、前后分离通过JSON交互数据

三、Controller层使用适配器

四、Service层很常规

五、缓存使用ehcache

六、dao层使用JPA简化开发

七、连接池使用dbcp2

八、redis缓存

九、WebMvcConfigurerAdapter拦截器

十、CommandLineRunner启动任务加载基础数据

十一、ApplicationListener监听器

十二、数据库使用mysql

十三、线程池用来执行task定时任务

十四、统一异常处理

 

下面通过代码示例,演示该项目

技术分享

 

 

1,nignx编译安装与增加openssl模块随后单独整理

2,SpringBoot通过@RestController默认返回json数据

3,使用适配器是为了方便业务类开发规范,@PathVariable注解获取请求路径中的参数,通过WebCpplicationContext获取业务类Bean示例

package com.controller;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;

import com.base.BaseController;
import com.base.StringTools;
import com.businesses.BaseBusiness;

@RestController
@Scope("prototype")
@RequestMapping("/adapter")
public class AdapterController extends BaseController {
    
    @Autowired  
    WebApplicationContext context;
    
    @RequestMapping("/{businessName}/{methodName}")
    public void executeAPI( @PathVariable String businessName,
            @PathVariable String methodName) throws Exception {
        Map<String, Object> map = new HashMap<String, Object>();
        String status = "0";
        String message = "success";
        String excep = "";
        businessName += "BusinessImpl";
        String request_json = null;
        try {
            request_json = StringTools.getStreamString(request.getInputStream());
            BaseBusiness business = (BaseBusiness) context
                    .getBean(businessName);
            Method method = null;
            Object result = null;
            method = business.getClass().getMethod(methodName, String.class);
            result = method.invoke(business, request_json);
            map.put("result", result);

        } catch (InvocationTargetException e) {
            message = e.getTargetException().getMessage();
            status = "500";
            excep = e.getTargetException().getMessage();
            log.error("excuteAPI Exception...", e);
            //此处设置后台全局异常处理使用
            //throw new RuntimeException();
        } catch (Exception e) {
            status = "500";
            message = e.getMessage();
            excep = e.getMessage();
            log.error("excuteAPI Exception...", e);
            //此处设置后台全局异常处理使用
            //throw new RuntimeException();
        }
        map.put("status", status);
        map.put("message", message);
        map.put("message_detail", excep);
        super.ajaxResponse(map);
    }
}

4,Business(Service)实现比较常规,是具体的业务实现

接口

技术分享
package com.businesses;

import com.base.CommonException;
import com.bean.base.BaseResponse;

public interface MemberBusiness extends BaseBusiness{
    void addMember(String reqJson)throws CommonException;
    
    BaseResponse loginMember(String reqJson)throws CommonException;
    
    BaseResponse findMemberById(String reqJson)throws CommonException;
}
View Code

实现类

技术分享
package com.businesses.impl;

import javax.servlet.http.HttpSession;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.base.CommonException;
import com.base.GsonTools;
import com.bean.base.BaseResponse;
import com.bean.request.MemberFindById;
import com.bean.request.MemberLoginRequest;
import com.bean.request.MemberSaveRequest;
import com.bean.response.MemberResponse;
import com.businesses.MemberBusiness;
import com.dao.MemberRepository;
import com.enums.Constants;
import com.model.Member;

@Component("memberBusinessImpl")
public class MemberBusinessImpl implements MemberBusiness{

    @Autowired
    MemberRepository memberRepository;
    
    @Autowired
    BaseBusinessImpl baseBusinessImpl;
    @Override
    public void addMember(String reqJson) throws CommonException {
        MemberSaveRequest saveReq = GsonTools.str2T(reqJson, MemberSaveRequest.class);
        Member member= new Member();
        BeanUtils.copyProperties(saveReq, member);
        memberRepository.save(member);
    }

    @Override
    public BaseResponse loginMember(String reqJson) throws CommonException {
        MemberLoginRequest loginReq = GsonTools.str2T(reqJson, MemberLoginRequest.class);
        BaseResponse resp = new BaseResponse();
        Member member = memberRepository.findByMobile(loginReq.getMobile());
        if(member == null) {
            throw new CommonException(Constants.STRING_MOBILE_ERROR);
        }
        if(!member.getPassword().equals(loginReq.getPassword())) {
            throw new CommonException(Constants.STRING_PASSW_ERROR);
        }
        HttpSession session = baseBusinessImpl.getSession();
        session.setAttribute("member", member);
        resp.setFalg(true);
        resp.setMsg(Constants.STRING_LOGIN_SUCCESS);
        return resp;
    }


    @Override
    public BaseResponse findMemberById(String reqJson) throws CommonException {
        MemberFindById req = GsonTools.str2T(reqJson, MemberFindById.class);
        MemberResponse resp = new MemberResponse();
        Member member = memberRepository.findById(req.getId());
        if(null == member) {
            throw new CommonException("信息不存在,请核实后再查询");
        }
        resp.setMember(member);
        return resp;
    }

}
View Code

5,6JPA与二级缓存

JPA主要是实现JpaRepository接口,可以直接通过类似ByName一样查询语句自动实现查询

二级缓存是通过@CacheConfig(cacheNames = "memberCache"),@Cacheable实现的,这里要注意在Application启动类上添加@EnableCache注解告诉应用启用缓存

package com.dao;

import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.jpa.repository.JpaRepository;

import com.model.Member;
@CacheConfig(cacheNames = "memberCache")
public interface MemberRepository extends JpaRepository<Member,Long>{
    @Cacheable
    Member findByMobile(String mobile);
    
    @Cacheable
    Member findById(Long id);
}

7,dbcp2连接池,在properties配置文件中配置

#dbcp2连接配置
spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
#初始化连接数量,默认值0
spring.datasource.dbcp2.initial-size=10
#最大空闲连接:连接池中容许保持空闲状态的最大连接数量,超过的空闲连接将被释放,如果设置为负数表示不限制,默认值8
spring.datasource.dbcp2.max-idle=20
#最小空闲连接:连接池中容许保持空闲状态的最小连接数量,低于这个数量将创建新的连接,如果设置为0则不创建,默认值0
spring.datasource.dbcp2.min-idle=10
#最大等待时间:当没有可用连接时,连接池等待连接被归还的最大时间(以毫秒计数),超过时间则抛出异常,如果设置为-1表示无限等待
spring.datasource.dbcp2.max-wait-millis=-1
#连接池创建的连接的默认的TransactionIsolation状态.
spring.datasource.dbcp2.default-transaction-isolation=1
#连接池创建的连接的默认的auto-commit状态
spring.datasource.dbcp2.default-auto-commit=true
#连接池创建的连接的默认的read-only状态. 如果没有设置则setReadOnly方法将不会被调用.(某些驱动不支持只读模式,比如:Informix)
spring.datasource.dbcp2.default-read-only=false

#指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个.
#注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串
spring.datasource.dbcp2.test-on-borrow=false

#指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.
#注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串
spring.datasource.dbcp2.test-while-idle=true
#在空闲连接回收器线程运行期间休眠的时间值,以毫秒为单位.如果设置为非正数,则不运行空闲连接回收器线程
spring.datasource.dbcp2.time-between-eviction-runs-millis=-1
#在每次空闲连接回收器线程(如果有)运行时检查的连接数量
spring.datasource.dbcp2.num-tests-per-eviction-run=5

8,redis缓存配置

技术分享
1 . 下载Redis 
目前,最新的Redist版本为3.0,使用wget下载,命令如下:
# wget http://download.redis.io/releases/redis-3.0.4.tar.gz

2 . 解压Redis 
下载完成后,使用tar命令解压下载文件:
# tar -xzvf redis-3.0.4.tar.gz

3 . 编译安装Redis 
切换至程序目录,并执行make命令编译:
# cd redis-3.0.4
# make
执行安装命令
# make install
make install安装完成后,会在/usr/local/bin目录下生成下面几个可执行文件,它们的作用分别是:
redis-server:Redis服务器端启动程序 
redis-cli:Redis客户端操作工具。也可以用telnet根据其纯文本协议来操作 
redis-benchmark:Redis性能测试工具 
redis-check-aof:数据修复工具 
redis-check-dump:检查导出工具

4 . 配置Redis 
复制配置文件到/etc/目录:
# cp redis.conf /etc/
为了让Redis后台运行,一般还需要修改redis.conf文件:
vi /etc/redis.conf
修改daemonize配置项为yes,使Redis进程在后台运行:
daemonize yes

5 . 启动Redis 
配置完成后,启动Redis:
# cd /usr/local/bin
# ./redis-server /etc/redis.conf

7 . Redis配置参数 
在 前面的操作中,我们用到了使Redis进程在后台运行的参数,下面介绍其它一些常用的Redis启动参数:
daemonize:是否以后台daemon方式运行
pidfile:pid文件位置
port:监听的端口号
timeout:请求超时时间
loglevel:log信息级别
logfile:log文件位置
databases:开启数据库的数量
save * *:保存快照的频率,第一个*表示多长时间,第三个*表示执行多少次写操作。在一定时间内执行一定数量的写操作时,自动保存快照。可设置多个条件。
rdbcompression:是否使用压缩
dbfilename:数据快照文件名(只是文件名)
dir:数据快照的保存目录(仅目录)
appendonly:是否开启appendonlylog,开启的话每次写操作会记一条log,这会提高数据抗风险能力,但影响效率。
appendfsync:appendonlylog如何同步到磁盘。三个选项,分别是每次写都强制调用fsync、每秒启用一次fsync、不调用fsync等待系统自己同步
View Code

9,拦截器我们通过实现WebMvcConfigurerAdapter,具体的拦截器功能在下面,执行的时机也在代码中详细标注了

package com.interceptor;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
//添加拦截器,设置拦截范围,排除拦截范围
@Configuration
public class InterceptorHandler extends WebMvcConfigurerAdapter {
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CustomInterceptor())
            .addPathPatterns("/**")
            .excludePathPatterns("/adapter/member/addMember");
    }
}
技术分享
package com.interceptor;

import static java.lang.System.out;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class CustomInterceptor implements HandlerInterceptor{

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        //controller之前执行
        out.println("--------CustomInterceptor-------before Controller--------");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        //controller之后view之前执行
        out.println("--------CustomInterceptor-------after Controller  And  before  modelAndView--------");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        //view渲染以后执行
        out.println("--------CustomInterceptor-------after modelAndView-----------");
    }

}
View Code

10,在项目中我们经常需要把数据在启动时加载到jvm中或redis中,我们可以使用CommandLineRunner启动任务加载基础数据@Order(1)是在有多个自定义CommandLineRunner时指定加载顺序的.

package com.linerunner;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.client.RedisClient;
import com.dao.MemberRepository;
import com.model.Member;

@Component
@Order(1)
public class CustomCommandLineRunner implements CommandLineRunner {
    
    @Autowired
    MemberRepository memberRepository;
    
    @Autowired  
    private RedisClient redisClinet;
    
    @Override
    public void run(String... arg0) throws Exception {
        System.out.println("--------第一个看看什么时候执行-----------");
        
        List<Member> list = memberRepository.findAll();
        for (Member member : list) {
            redisClinet.set(member.getId().toString(), member.getMobile());
            System.out.println("redis 服务器获取到    --- " + redisClinet.get(member.getId().toString()));
        }
    }

}

11,监听器ApplicationListener

package com.listener;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.web.context.WebApplicationContext;

public class CustomApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>{

    @Autowired
    WebApplicationContext wac;
    
    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        System.out.println("---------启动前的listener-----------");
    }


}

12,mysql数据库,说下乱码解决吧,一般出现乱码,先查看查看workspace编码-->前台页面传过来的数据是否乱码-->数据库是否乱码    数据库出现乱码的话在my.ini中设置一下

[mysqld]设置项下的character-set-server=utf8,一般是此处问题

13,线程池用来执行task定时任务,线程池明天详细补充一下吧.

14,统一异常处理(当然要体验这个的话要在AdapterController把注解注释掉哦)

package com.exception;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import com.bean.response.ExceptionResponse;

@RestControllerAdvice  
public class GlobalExceptionHandler { 
    
    @ExceptionHandler(RuntimeException.class)  
    public ExceptionResponse exceptionHandler(RuntimeException e, HttpServletResponse response) {  
        ExceptionResponse resp = new ExceptionResponse();
        resp.setCoed("100");
        resp.setMessage("全局异常处理");
        return resp;  
    }
    
} 

 

15,RedisClient

package com.client;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Component;  
  
import redis.clients.jedis.Jedis;  
import redis.clients.jedis.JedisPool;  
  
@Component  
public class RedisClient {  
  
    Logger log = Logger.getLogger(RedisClient.class);
    
    @Autowired  
    private JedisPool jedisPool;  
      
    public void set(String key, String value) {  
        try(Jedis jedis =jedisPool.getResource()) {  
            jedis.set(key, value);  
        } catch(Exception e) {
            log.error("jedisPoll 存储数据   出错了  ....", e);
        }
    }  
    
    public String get(String key) throws Exception  {  
  
        try(Jedis jedis =jedisPool.getResource()) {  
            return jedis.get(key);  
        } catch(Exception e) {
            log.error("jedisPoll 存储数据   出错了  ....", e);
            return null;
        }
    } 
}  

 

补充

配置文件加载顺序

//1命令行参数。
//2properties
//yml
//昂,还需要研究一下,待补充

详细配置文件

技术分享
spring.datasource.url=jdbc:mysql://localhost:3306/store?useUnicode=true&amp;characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#自动更新表结构,保留数据
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.show_sql=true
spring.thymeleaf.cache=false

#dbcp2连接配置
spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
#初始化连接数量,默认值0
spring.datasource.dbcp2.initial-size=10
#最大空闲连接:连接池中容许保持空闲状态的最大连接数量,超过的空闲连接将被释放,如果设置为负数表示不限制,默认值8
spring.datasource.dbcp2.max-idle=20
#最小空闲连接:连接池中容许保持空闲状态的最小连接数量,低于这个数量将创建新的连接,如果设置为0则不创建,默认值0
spring.datasource.dbcp2.min-idle=10
#最大等待时间:当没有可用连接时,连接池等待连接被归还的最大时间(以毫秒计数),超过时间则抛出异常,如果设置为-1表示无限等待
spring.datasource.dbcp2.max-wait-millis=-1
#连接池创建的连接的默认的TransactionIsolation状态.
spring.datasource.dbcp2.default-transaction-isolation=1
#连接池创建的连接的默认的auto-commit状态
spring.datasource.dbcp2.default-auto-commit=true
#连接池创建的连接的默认的read-only状态. 如果没有设置则setReadOnly方法将不会被调用.(某些驱动不支持只读模式,比如:Informix)
spring.datasource.dbcp2.default-read-only=false

#指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个.
#注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串
spring.datasource.dbcp2.test-on-borrow=false

#指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.
#注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串
spring.datasource.dbcp2.test-while-idle=true
#在空闲连接回收器线程运行期间休眠的时间值,以毫秒为单位.如果设置为非正数,则不运行空闲连接回收器线程
spring.datasource.dbcp2.time-between-eviction-runs-millis=-1
#在每次空闲连接回收器线程(如果有)运行时检查的连接数量
spring.datasource.dbcp2.num-tests-per-eviction-run=5
View Code

Redis配置

package com.config;
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Qualifier;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
  
import redis.clients.jedis.JedisPool;  
import redis.clients.jedis.JedisPoolConfig;  
  
@Configuration  
public class RedisConfiguration {  
      
    @Bean(name= "jedis.pool")  
    @Autowired  
    public JedisPool jedisPool(@Qualifier("jedis.pool.config") JedisPoolConfig config,   
                @Value("${jedis.pool.host}")String host,   
                @Value("${jedis.pool.port}")int port) {  
        return new JedisPool(config, host, port);  
    }  
      
    @Bean(name= "jedis.pool.config")  
    public JedisPoolConfig jedisPoolConfig (@Value("${jedis.pool.config.maxTotal}")int maxTotal,  
                                @Value("${jedis.pool.config.maxIdle}")int maxIdle,  
                                @Value("${jedis.pool.config.maxWaitMillis}")int maxWaitMillis) {  
        JedisPoolConfig config = new JedisPoolConfig();  
        config.setMaxTotal(maxTotal);  
        config.setMaxIdle(maxIdle);  
        config.setMaxWaitMillis(maxWaitMillis);
        config.setTestOnBorrow(false);
        config.setTestOnReturn(false);
        return config;  
    }  
      
}  

昂,好乱,好晚了,睡觉睡觉,明天晚上再整理

 

后续会做动静分离,应用拆分,容错机制,高可用注册中心,哇,还有很长的啊....

Spring Boot 学习之项目构建

原文:http://www.cnblogs.com/loginloading/p/7599729.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!