springboot1.5.9 + mybatis + layui + shiro后台权限管理系统

后台管理系统

业务场景

spring boot + mybatis后台管理系统框架;

layUI前端界面;

shiro权限控制,ehCache缓存;

开发背景

maven :3.3.3 

JDK : 1.8 

Intellij IDEA : 2017.2.5 开发工具 

spring boot :1.5.9.RELEASE 

mybatis 3.4.5 :dao层框架 

pageHelper : 5.1.2 

httpClient : 4.5.3

layui 2.2.3 :前端框架 

shiro 1.4.0 :权限控制框架 

druid 1.1.5 :druid连接池,监控数据库性能,记录SQL执行日志 

thymeleaf :2.1.4.RELEASE,thymeleaf前端html页面模版 

log4j2 2.7 :日志框架 

EHCache : 2.5.0 

ztree : 3.5.31

项目框架

spring boot + mybatis + shiro + layui + ehcache 

项目源码:(包含数据库源码) 

github源码: https://github.com/wyait/manage.git 

码云:https://gitee.com/wyait/manage.git

基础框架

spring boot + mybatis的整合,参考博客: 

http://blog.51cto.com/wyait/1969626

整合layui

layui官网:http://www.layui.com 

layui下载地址:https://github.com/sentsin/layui/

将下载的layui解压后,复制到项目的static/目录下: 

在templates/目录下,新建index.html,根据layui官网的API(后台布局代码),引入相关代码: 

==注意: 

html页面中的标签必须要加上对应的闭合标签或标签内加上"/",比如: 或 等; 

在引入static/目录下的css和js等文件时,路径中不需要加"/static/",默认加载的是static/目录下的文件;==

整合shiro权限控制

shiro简介

Apache Shiro是一个功能强大、灵活的,开源的安全框架。它可以干净利落地处理身份验证、授权、企业会话管理和加密。

Apache Shiro的首要目标是易于使用和理解。安全通常很复杂,甚至让人感到很痛苦,但是Shiro却不是这样子的。一个好的安全框架应该屏蔽复杂性,向外暴露简单、直观的API,来简化开发人员实现应用程序安全所花费的时间和精力。

Shiro能做什么呢?

验证用户身份

用户访问权限控制,比如:1、判断用户是否分配了一定的安全角色。2、判断用户是否被授予完成某个操作的权限

在非 web 或 EJB 容器的环境下可以任意使用Session API

可以响应认证、访问控制,或者 Session 生命周期中发生的事件

可将一个或以上用户安全数据源数据组合成一个复合的用户 "view"(视图)

支持单点登录(SSO)功能

支持提供“Remember Me”服务,获取用户关联信息而无需登录

等等——都集成到一个有凝聚力的易于使用的API。根据官方的介绍,shiro提供了“身份认证”、“授权”、“加密”和“Session管理”这四个主要的核心功能

// TODO 百度

引入依赖

pom.xml中引入shiro依赖:

org.apache.shiroshiro-spring${shiro.version}org.apache.shiroshiro-all${shiro.version}

shiro.version版本为:1.3.1

shiro配置实体类

/** * @项目名称:wyait-manage * @包名:com.wyait.manage.config * @类描述: * @创建人:wyait * @创建时间:2017-12-12 18:51 *@version:V1.0 */@ConfigurationpublicclassShiroConfig{privatestaticfinalLogger logger = LoggerFactory            .getLogger(ShiroConfig.class);/**

    * ShiroFilterFactoryBean 处理拦截资源文件过滤器

    * 
1,配置shiro安全管理器接口securityManage;

    * 
2,shiro 连接约束配置filterChainDefinitions;

    */@BeanpublicShiroFilterFactoryBeanshiroFilterFactoryBean(

            org.apache.shiro.mgt.SecurityManager securityManager){//shiroFilterFactoryBean对象ShiroFilterFactoryBean shiroFilterFactoryBean =newShiroFilterFactoryBean();// 配置shiro安全管理器 SecurityManagershiroFilterFactoryBean.setSecurityManager(securityManager);// 指定要求登录时的链接shiroFilterFactoryBean.setLoginUrl("/login");// 登录成功后要跳转的链接shiroFilterFactoryBean.setSuccessUrl("/index");// 未授权时跳转的界面;shiroFilterFactoryBean.setUnauthorizedUrl("/403");// filterChainDefinitions拦截器Map filterChainDefinitionMap =newLinkedHashMap();// 配置不会被拦截的链接 从上向下顺序判断filterChainDefinitionMap.put("/static/**","anon");        filterChainDefinitionMap.put("/templates/**","anon");// 配置退出过滤器,具体的退出代码Shiro已经替我们实现了filterChainDefinitionMap.put("/logout","logout");//add操作,该用户必须有【addOperation】权限filterChainDefinitionMap.put("/add","perms[addOperation]");// filterChainDefinitionMap.put("/user/**","authc");        shiroFilterFactoryBean                .setFilterChainDefinitionMap(filterChainDefinitionMap);        logger.debug("Shiro拦截器工厂类注入成功");returnshiroFilterFactoryBean;    }/**    * shiro安全管理器设置realm认证    *@return*/@Beanpublicorg.apache.shiro.mgt.SecurityManagersecurityManager(){        DefaultWebSecurityManager securityManager =newDefaultWebSecurityManager();// 设置realm.securityManager.setRealm(shiroRealm());// //注入ehcache缓存管理器;securityManager.setCacheManager(ehCacheManager());returnsecurityManager;    }/**    * 身份认证realm; (账号密码校验;权限等)    *    *@return*/@BeanpublicShiroRealmshiroRealm(){        ShiroRealm shiroRealm =newShiroRealm();returnshiroRealm;    }/**    * ehcache缓存管理器;shiro整合ehcache:    * 通过安全管理器:securityManager    *@returnEhCacheManager    */@BeanpublicEhCacheManagerehCacheManager(){        logger.debug("=====shiro整合ehcache缓存:ShiroConfiguration.getEhCacheManager()");        EhCacheManager cacheManager =newEhCacheManager();        cacheManager.setCacheManagerConfigFile("classpath:config/ehcache.xml");returncacheManager;    }}

Filter Chain定义说明:

1、一个URL可以配置多个Filter,使用逗号分隔; 

2、当设置多个过滤器时,全部验证通过,才视为通过; 

3、部分过滤器可指定参数,如perms,roles

Shiro内置的FilterChain:

Filter NameClass

anonorg.apache.shiro.web.filter.authc.AnonymousFilter

authcorg.apache.shiro.web.filter.authc.FormAuthenticationFilter

authcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

permsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter

portorg.apache.shiro.web.filter.authz.PortFilter

restorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilter

rolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFilter

sslorg.apache.shiro.web.filter.authz.SslFilter

userorg.apache.shiro.web.filter.authc.UserFilter

anon : 所有url都都可以匿名访问 

authc : 需要认证才能进行访问 

user : 配置记住我或认证通过可以访问

ShiroRealm认证实体类

/** * @项目名称:wyait-manage * @包名:com.wyait.manage.shiro * @类描述: * @创建人:wyait * @创建时间:2017-12-13 13:53 *@version:V1.0 */publicclassShiroRealmextendsAuthorizingRealm{@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(

            PrincipalCollection principalCollection){//TODOreturnnull;    }@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(

            AuthenticationToken authenticationToken)throwsAuthenticationException{//TODOreturnnull;    }}

shiro使用ehcache缓存

导入依赖;

org.apache.shiroshiro-ehcache1.2.6

  包含支持UI模版(Velocity,FreeMarker,JasperReports),

  邮件服务,

  脚本服务(JRuby),

  缓存Cache(EHCache),

  任务计划Scheduling(uartz)。

-->org.springframeworkspring-context-support

引入ehcache.xml配置文件;

shiro配置类中整合ehcache做缓存管理;【参考:shiro配置实体类】

整合thymeleaf

导入pom依赖

org.springframework.bootspring-boot-starter-thymeleaf

配置中禁用缓存

#关闭thymeleaf缓存spring.thymeleaf.cache=false

shiro功能之记住我

shiro记住我的功能是基于浏览器中的cookie实现的;

在shiroConfig里面增加cookie配置

CookieRememberMeManager配置;

/*** 设置记住我cookie过期时间*@return*/@BeanpublicSimpleCookieremeberMeCookie(){logger.debug("记住我,设置cookie过期时间!");//cookie名称;对应前端的checkbox的name = rememberMeSimpleCookie scookie=newSimpleCookie("rememberMe");//记住我cookie生效时间1小时 ,单位秒  [1小时]scookie.setMaxAge(3600);returnscookie;}

/**

配置cookie记住我管理器

@returnbr/>*/

@Bean

public CookieRememberMeManager rememberMeManager(){

logger.debug("配置cookie记住我管理器!");

CookieRememberMeManager cookieRememberMeManager=new CookieRememberMeManager();

cookieRememberMeManager.setCookie(remeberMeCookie());

return cookieRememberMeManager;

}

- 将CookieRememberMeManager注入SecurityManager

//注入Cookie记住我管理器

securityManager.setRememberMeManager(rememberMeManager());

前端页面新增rememberMe复选框

登录方法更改

//新增rememberMe参数@RequestParam(value="rememberMe",required =false)booleanrememberMe... ...// 1、 封装用户名、密码、是否记住我到token令牌对象  [支持记住我]AuthenticationToken token =newUsernamePasswordToken(            user.getMobile(),  DigestUtils.md5Hex(user.getPassword()),rememberMe);

页面cookie设置 

shiro功能之密码错误次数限制

针对用户在登录时用户名和密码输入错误进行次数限制,并锁定; 

Shiro中用户名密码的验证交给了CredentialsMatcher;

在CredentialsMatcher里面校验用户密码,使用ehcache记录登录失败次数就可以实现。

在验证用户名密码之前先验证登录失败次数,如果超过5次就抛出尝试过多的异常,否则验证用户名密码,验证成功把尝试次数清零,不成功则直接退出。这里依靠Ehcache自带的timeToIdleSeconds来保证锁定时间(帐号锁定之后的最后一次尝试间隔timeToIdleSeconds秒之后自动清除)。

自定义HashedCredentialsMatcher实现类

/** * @项目名称:lyd-channel * @包名:com.lyd.channel.shiro * @类描述:shiro之密码输入次数限制6次,并锁定2分钟 * @创建人:wyait * @创建时间:2018年1月23日17:23:10 *@version:V1.0 */publicclassRetryLimitHashedCredentialsMatcherextendsHashedCredentialsMatcher{//集群中可能会导致出现验证多过5次的现象,因为AtomicInteger只能保证单节点并发//解决方案,利用ehcache、redis(记录错误次数)和mysql数据库(锁定)的方式处理:密码输错次数限制; 或两者结合使用privateCache passwordRetryCache;publicRetryLimitHashedCredentialsMatcher(CacheManager cacheManager){//读取ehcache中配置的登录限制锁定时间passwordRetryCache = cacheManager.getCache("passwordRetryCache");      }/**    * 在回调方法doCredentialsMatch(AuthenticationToken token,AuthenticationInfo info)中进行身份认证的密码匹配,    *
这里我们引入了Ehcahe用于保存用户登录次数,如果登录失败retryCount变量则会一直累加,如果登录成功,那么这个count就会从缓存中移除,    *
从而实现了如果登录次数超出指定的值就锁定。    *@paramtoken    *@paraminfo    *@return*/@OverridepublicbooleandoCredentialsMatch(AuthenticationToken token, 

            AuthenticationInfo info){//获取登录用户名String username = (String) token.getPrincipal();//从ehcache中获取密码输错次数// retryCountAtomicInteger retryCount = passwordRetryCache.get(username);if(retryCount ==null) {//第一次retryCount =newAtomicInteger(0);              passwordRetryCache.put(username, retryCount);          }//retryCount.incrementAndGet()自增:count + 1if(retryCount.incrementAndGet() >5) {// if retry count > 5 throw  超过5次 锁定thrownewExcessiveAttemptsException("username:"+username+" tried to login more than 5 times in period");        }//否则走判断密码逻辑booleanmatches =super.doCredentialsMatch(token, info);if(matches) {// clear retry count  清楚ehcache中的count次数缓存passwordRetryCache.remove(username);          }returnmatches;      }  }

这里的逻辑也不复杂,在回调方法doCredentialsMatch(AuthenticationToken token,AuthenticationInfo info) 

中进行身份认证的密码匹配,这里我们引入了Ehcahe用于保存用户登录次数,如果登录失败retryCount变量则会一直累加,如果登录成功,那么这个count就会从缓存中移除,从而实现了如果登录次数超出指定的值就锁定。

ehcache中新增密码重试次数缓存passwordRetryCache

在shiroConfig配置类中添加HashedCredentialsMatcher凭证匹配器

/**    * 凭证匹配器 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了    * 所以我们需要修改下doGetAuthenticationInfo中的代码,更改密码生成规则和校验的逻辑一致即可; )    *    *@return*/@BeanpublicHashedCredentialsMatcherhashedCredentialsMatcher(){        HashedCredentialsMatcher hashedCredentialsMatcher =newRetryLimitHashedCredentialsMatcher(ehCacheManager());//new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("md5");// 散列算法:这里使用MD5算法;hashedCredentialsMatcher.setHashIterations(1);// 散列的次数,比如散列两次,相当于 // md5(md5(""));returnhashedCredentialsMatcher;    }

设置ShiroRealm密码匹配使用自定义的HashedCredentialsMatcher实现类

//使用自定义的CredentialsMatcher进行密码校验和输错次数限制shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());

更改ShiroRealm类doGetAuthenticationInfo登录认证方法

更改密码加密规则,和自定义的HashedCredentialsMatcher匹配器加密规则保持一致;

// 第一个参数 ,登陆后,需要在session保存数据// 第二个参数,查询到密码(加密规则要和自定义的HashedCredentialsMatcher中的HashAlgorithmName散列算法一致)// 第三个参数 ,realm名字newSimpleAuthenticationInfo(user, DigestUtils.md5Hex(user.getPassword()),                    getName());

login方法的改动;

controller层获取登录失败次数;登录页面新增用户、密码输错次数提醒;

//注入ehcache管理器@AutowiredprivateEhCacheManager ecm;... ...//登录方法中,获取失败次数,并设置友情提示信息Cache passwordRetryCache= ecm.getCache("passwordRetryCache");if(null!=passwordRetryCache){intretryNum=(passwordRetryCache.get(existUser.getMobile())==null?0:passwordRetryCache.get(existUser.getMobile())).intValue();    logger.debug("输错次数:"+retryNum);if(retryNum>0&& retryNum<6){        responseResult.setMessage("用户名或密码错误"+retryNum+"次,再输错"+(6-retryNum)+"次账号将锁定");    }}

后台新增用户解锁操作;清除ehcache中的缓存即可; 

TODO 

用户列表,解锁按钮,点击,弹出输入框,让用户管理员输入需要解锁的用户手机号,进行解锁操作即可;

Cache passwordRetryCache= ecm.getCache("passwordRetryCache");//username是缓存keypasswordRetryCache..remove(username);

thymeleaf整合shiro

html页面使用thymeleaf模版;

导入pom依赖

com.github.theborakompanionithymeleaf-extras-shiro1.2.1

thymeleaf整合shiro的依赖:thymeleaf-extras-shiro最新版本是2.0.0,配置使用报错,所以使用1.2.1版本; 

该jar包的github地址:

https://github.com/theborakompanioni/thymeleaf-extras-shiro

配置shiroDirect

@BeanpublicShiroDialectshiroDialect(){returnnewShiroDialect();}

这段代码放在ShiroConfig配置类里面即可。

页面中使用

... ...

具体用法,参考:https://github.com/theborakompanioni/thymeleaf-extras-shiro

整合pageHelper

导入pom依赖

com.github.pagehelperpagehelper-spring-boot-starter1.2.3

添加配置

# pagehelper参数配置pagehelper.helperDialect=mysqlpagehelper.reasonable=truepagehelper.supportMethodsArguments=truepagehelper.returnPageInfo=checkpagehelper.params=count=countSql

代码中使用

//PageHelper放在查询方法前即可PageHelper.startPage(page, limit);List urList = userMapper.getUsers(userSearch);... ...//获取分页查询后的pageInfo对象数据PageInfo pageInfo =newPageInfo<>(urList);//pageInfo中获取到的总记录数total:pageInfo.getTotal();

PageInfo对象中的数据和用法,详见源码!

整合ztree

详见ztree官网:http://www.treejs.cn/v3/api.php

整合httpClient

导入pom依赖

org.apache.httpcomponentshttpclient4.5.3org.apache.httpcomponentshttpmime4.5.3

配置类

/** * @项目名称:wyait-manage * @包名:com.wyait.manage.config * @类描述: * @创建人:wyait * @创建时间:2018-01-11 9:13 *@version:V1.0 */@ConfigurationpublicclassHttpClientConfig{privatestaticfinalLogger logger = LoggerFactory            .getLogger(ShiroConfig.class);/**

    * 连接池最大连接数

    */@Value("${httpclient.config.connMaxTotal}")privateintconnMaxTotal =20;/**

    *

    */@Value("${httpclient.config.maxPerRoute}")privateintmaxPerRoute =20;/**

    * 连接存活时间,单位为s

    */@Value("${httpclient.config.timeToLive}")privateinttimeToLive =10;/**    * 配置连接池    *@return*/@Bean(name="poolingClientConnectionManager")publicPoolingHttpClientConnectionManagerpoolingClientConnectionManager(){        PoolingHttpClientConnectionManager poolHttpcConnManager =newPoolingHttpClientConnectionManager(60, TimeUnit.SECONDS);// 最大连接数poolHttpcConnManager.setMaxTotal(this.connMaxTotal);// 路由基数poolHttpcConnManager.setDefaultMaxPerRoute(this.maxPerRoute);returnpoolHttpcConnManager;    }@Value("${httpclient.config.connectTimeout}")privateintconnectTimeout =3000;@Value("${httpclient.config.connectRequestTimeout}")privateintconnectRequestTimeout =2000;@Value("${httpclient.config.socketTimeout}")privateintsocketTimeout =3000;/**    * 设置请求配置    *@return*/@BeanpublicRequestConfigconfig(){returnRequestConfig.custom()                .setConnectionRequestTimeout(this.connectRequestTimeout)                .setConnectTimeout(this.connectTimeout)                .setSocketTimeout(this.socketTimeout)                .build();    }@Value("${httpclient.config.retryTime}")// 此处建议采用@ConfigurationProperties(prefix="httpclient.config")方式,方便复用privateintretryTime;/**    * 重试策略    *@return*/@BeanpublicHttpRequestRetryHandlerhttpRequestRetryHandler(){// 请求重试finalintretryTime =this.retryTime;returnnewHttpRequestRetryHandler() {publicbooleanretryRequest(IOException exception,intexecutionCount, HttpContext context){// Do not retry if over max retry count,如果重试次数超过了retryTime,则不再重试请求if(executionCount >= retryTime) {returnfalse;                }// 服务端断掉客户端的连接异常if(exceptioninstanceofNoHttpResponseException) {returntrue;                }// time out 超时重试if(exceptioninstanceofInterruptedIOException) {returntrue;                }// Unknown hostif(exceptioninstanceofUnknownHostException) {returnfalse;                }// Connection refusedif(exceptioninstanceofConnectTimeoutException) {returnfalse;                }// SSL handshake exceptionif(exceptioninstanceofSSLException) {returnfalse;                }                HttpClientContext clientContext = HttpClientContext.adapt(context);                HttpRequest request = clientContext.getRequest();if(!(requestinstanceofHttpEntityEnclosingRequest)) {returntrue;                }returnfalse;            }        };    }/**    * 创建httpClientBuilder对象    *@paramhttpClientConnectionManager    *@return*/@Bean(name ="httpClientBuilder")publicHttpClientBuildergetHttpClientBuilder(@Qualifier("poolingClientConnectionManager")PoolingHttpClientConnectionManager httpClientConnectionManager){returnHttpClients.custom().setConnectionManager(httpClientConnectionManager)                .setRetryHandler(this.httpRequestRetryHandler())//.setKeepAliveStrategy(connectionKeepAliveStrategy())//.setRoutePlanner(defaultProxyRoutePlanner()).setDefaultRequestConfig(this.config());    }/**    * 自动释放连接    *@paramhttpClientBuilder    *@return*/@BeanpublicCloseableHttpClientgetCloseableHttpClient(@Qualifier("httpClientBuilder")HttpClientBuilder httpClientBuilder){returnhttpClientBuilder.build();    }

封装公用类

参考项目源码:HttpService HttpResult

使用

数据校验

本项目中数据校验,前台统一使用自定义的正则校验;后台使用两种校验方式供大家选择使用;

oval注解校验

//TODO 

Google或百度

自定义正则校验

参考:ValidateUtil.java和checkParam.js

数据库设计

表结构

用户user、角色role、权限permission以及中间表(user_role、role_permission)共五张表; 

实现按钮级别的权限控制。 

建表SQL源码:github

数据源配置

单库(数据源)配置

spring boot默认自动加载单库配置,只需要在application.properties文件中添加mysql配置即可;

# mysqlspring.datasource.url=jdbc:mysql://localhost:3306/wyait?useUnicode=true&zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=truespring.datasource.username=rootspring.datasource.password=123456spring.datasource.driver-class-name=com.mysql.jdbc.Driver# 使用druid连接池  需要注意的是:spring.datasource.type旧的spring boot版本是不能识别的。spring.datasource.type=com.alibaba.druid.pool.DruidDataSource# mybatismybatis.type-aliases-package=com.wyait.manage.pojomybatis.mapper-locations=classpath:mapper/*.xml# 开启驼峰映射mybatis.configuration.map-underscore-to-camel-case=true

多数据源配置

方式一:利用spring加载配置,注册bean的逻辑进行多数据源配置

配置文件:

# 多数据源配置slave.datasource.names=test,test1slave.datasource.test.driverClassName =com.mysql.jdbc.Driverslave.datasource.test.url=jdbc:mysql://localhost:3306/test?useUnicode=true&zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=trueslave.datasource.test.username=rootslave.datasource.test.password=123456# test1slave.datasource.test1.driverClassName =com.mysql.jdbc.Driverslave.datasource.test1.url=jdbc:mysql://localhost:3306/test1?useUnicode=true&zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=trueslave.datasource.test1.username=rootslave.datasource.test1.password=123456

配置类

/**

* @项目名称:lyd-channel

* @类名称:MultipleDataSource

* @类描述:创建多数据源注册到Spring中

* @创建人:wyait

* @创建时间:2017年12月19日 下午2:49:34

* @version:

*///@Configuration@SuppressWarnings("unchecked")publicclassMultipleDataSourceimplementsBeanDefinitionRegistryPostProcessor,EnvironmentAware{//作用域对象.private ScopeMetadataResolver scopeMetadataResolver =newAnnotationScopeMetadataResolver();//bean名称生成器.private BeanNameGenerator beanNameGenerator =newAnnotationBeanNameGenerator();//如配置文件中未指定数据源类型,使用该默认值privatestaticfinalObjectDATASOURCE_TYPE_DEFAULT ="com.alibaba.druid.pool.DruidDataSource";// 存放DataSource配置的集合;privateMap> dataSourceMap =newHashMap>();    @Override    publicvoidpostProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {    System.out.println("MultipleDataSourceBeanDefinitionRegistryPostProcessor.postProcessBeanFactory()");//设置为主数据源;beanFactory.getBeanDefinition("dataSource").setPrimary(true);if(!dataSourceMap.isEmpty()){//不为空的时候.BeanDefinition bd =null;Map dsMap =null;            MutablePropertyValues mpv =null;for(Entry> entry : dataSourceMap.entrySet()) {                bd = beanFactory.getBeanDefinition(entry.getKey());                mpv = bd.getPropertyValues();                dsMap = entry.getValue();                mpv.addPropertyValue("driverClassName", dsMap.get("driverClassName"));                mpv.addPropertyValue("url", dsMap.get("url"));                mpv.addPropertyValue("username", dsMap.get("username"));                mpv.addPropertyValue("password", dsMap.get("password"));            }      }    }    @Override    publicvoidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {    System.out.println("MultipleDataSourceBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry()");try{if(!dataSourceMap.isEmpty()){//不为空的时候,进行注册bean.for(Entry> entry:dataSourceMap.entrySet()){Objecttype = entry.getValue().get("type");//获取数据源类型if(type ==null){                    type= DATASOURCE_TYPE_DEFAULT;                  }                  registerBean(registry, entry.getKey(),(Class)Class.forName(type.toString()));              }          }      }catch(ClassNotFoundException  e) {//异常捕捉.e.printStackTrace();      }    }/**

    * 注意重写的方法 setEnvironment 是在系统启动的时候被执行。

    * 这个方法主要是:加载多数据源配置

    * 从application.properties文件中进行加载;

    */@Override    publicvoidsetEnvironment(Environment environment) {    System.out.println("MultipleDataSourceBeanDefinitionRegistryPostProcessor.setEnvironment()");/*

        * 获取application.properties配置的多数据源配置,添加到map中,之后在postProcessBeanDefinitionRegistry进行注册。

        *///获取到前缀是"slave.datasource." 的属性列表值.RelaxedPropertyResolver propertyResolver =newRelaxedPropertyResolver(environment,"slave.datasource.");//获取到所有数据源的名称.StringdsPrefixs = propertyResolver.getProperty("names");String[] dsPrefixsArr = dsPrefixs.split(",");for(StringdsPrefix:dsPrefixsArr){/*

            * 获取到子属性,对应一个map;

            * 也就是这个map的key就是

            * type、driver-class-name等;

            */Map dsMap = propertyResolver.getSubProperties(dsPrefix +".");//存放到一个map集合中,之后在注入进行使用.dataSourceMap.put(dsPrefix, dsMap);      }    }/**

    * 注册Bean到Spring

    */privatevoidregisterBean(BeanDefinitionRegistry registry,Stringname, Class beanClass) {        AnnotatedGenericBeanDefinition abd =newAnnotatedGenericBeanDefinition(beanClass);        ScopeMetadata scopeMetadata =this.scopeMetadataResolver.resolveScopeMetadata(abd);        abd.setScope(scopeMetadata.getScopeName());// 可以自动生成nameStringbeanName = (name !=null? name :this.beanNameGenerator.generateBeanName(abd, registry));        AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);        BeanDefinitionHolder definitionHolder =newBeanDefinitionHolder(abd, beanName);        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);    }}

接口:BeanDefinitionRegistryPostProcessor只要是注入bean, 

接口:接口 EnvironmentAware 重写方法 setEnvironment ; 可以在工程启动时,获取到系统环境变量和application配置文件中的变量。

该配置类的加载顺序是: 

setEnvironment()-->postProcessBeanDefinitionRegistry() --> postProcessBeanFactory()

在setEnvironment()方法中主要是读取了application.properties的配置;

在postProcessBeanDefinitionRegistry()方法中主要注册为spring的bean对象;

在postProcessBeanFactory()方法中主要是注入从setEnvironment方法中读取的application.properties配置信息。

文章属于转载,原文:springboot1.5.9 + mybatis + layui + shiro后台权限管理系统

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,600评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,748评论 6 342
  • 要加“m”说明是MB,否则就是KB了. -Xms:初始值 -Xmx:最大值 -Xmn:最小值 java -Xms8...
    dadong0505阅读 4,806评论 0 53
  • 在我搭建基于Spring Cloud的微服务体系应用的时候所需要或者是常用的属性配置文件,还有这些属性的用途,此配...
    StrongManAlone阅读 3,992评论 0 18
  • 周检视第3周(2017年08月) 健康:1)泡脚2天 2)徒步53000步,平均7600/天 3)冥想6天 ...
    圆圆jXY阅读 180评论 0 0