后台管理系统
业务场景
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参数@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配置信息。