当前位置:   article > 正文

SpringBoot——动态数据源(多数据源自动切换)

动态数据源

前言

日常的业务开发项目中只会配置一套数据源,如果需要获取其他系统的数据往往是通过调用接口, 或者是通过第三方工具比如kettle将数据同步到自己的数据库中进行访问。

但是也会有需要在项目中引用多数据源的场景。比如如下场景:

  • 自研数据迁移系统,至少需要新、老两套数据源,从老库读取数据写入新库
  • 自研读写分离中间件,系统流量增加,单库响应效率降低,引入读写分离方案,写入数据是一个数据源,读取数据是另一个数据源

某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库。

为了在开发中以最简单的方法使用,本文基于注解和AOP的方法实现,在spring boot框架的项目中,添加本文实现的代码类后,只需要配置好数据源就可以直接通过注解使用,简单方便。

一、原理

关键类说明

忽略掉controller/service/entity/mapper/xml介绍。

  • jdbc.properties: 数据源配置文件。虽然可以配置到Spring boot的默认配置文件application.properties/application.yml文件当中,但是如果数据源比较多的话,根据实际使用,最佳的配置方式还是独立配置比较好。
  • DynamicDataSourceConfig:数据源配置类
  • DynamicDataSource:动态数据源配置类
  • DataSourceRouting:动态数据源注解
  • DynamicDataSourceAspect:动态数据源设置切面
  • DynamicDataSourceContextHolder:当前线程持有的数据源key
  • DataSourceConstants:数据源key常量类

开发流程

 

动态数据源流程

Spring Boot 的动态数据源,本质上是把多个数据源存储在一个 Map 中,当需要使用某个数据源时,从 Map 中获取此数据源进行处理。

 

在 Spring 中已提供了抽象类 AbstractRoutingDataSource 来实现此功能,继承AbstractRoutingDataSource类并覆写其determineCurrentLookupKey()方法即可,该方法只需要返回数据源key即可,也就是存放数据源的Map的key。

因此,我们在实现动态数据源的,只需要继承它,实现自己的获取数据源逻辑即可。AbstractRoutingDataSource顶级继承了DataSource,所以它也是可以做为数据源对象,因此项目中使用它作为主数据源。

 

AbstractRoutingDataSource原理

AbstractRoutingDataSource中有一个重要的属性:

  • argetDataSources:目标数据源,即项目启动的时候设置的需要通过AbstractRoutingDataSource管理的数据源。
  • defaultTargetDataSource:默认数据源,项目启动的时候设置的默认数据源,如果没有指定数据源,默认返回改数据源。
  • resolvedDataSources:也是存放的数据源,是对targetDataSources进行处理后进行存储的。可以看一下源码。

 

 

  • resolvedDefaultDataSource: 对默认数据源进行了二次处理,源码如上图最后的两行代码。

AbstractRoutingDataSource中所有的方法和属性:

 

比较重要的是determineTargetDataSource方法。

  1. protected DataSource determineTargetDataSource() {
  2. Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
  3. Object lookupKey = determineCurrentLookupKey();
  4. DataSource dataSource = this.resolvedDataSources.get(lookupKey);
  5. if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
  6. dataSource = this.resolvedDefaultDataSource;
  7. }
  8. if (dataSource == null) {
  9. throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
  10. }
  11. return dataSource;
  12. }
  13. /**
  14. * Determine the current lookup key. This will typically be
  15. * implemented to check a thread-bound transaction context.
  16. * <p>Allows for arbitrary keys. The returned key needs
  17. * to match the stored lookup key type, as resolved by the
  18. * {@link #resolveSpecifiedLookupKey} method.
  19. */
  20. @Nullable
  21. protected abstract Object determineCurrentLookupKey();

这个方法主要就是返回一个DataSource对象,主要逻辑就是先通过方法determineCurrentLookupKey获取一个Object对象的lookupKey,然后通过这个lookupKey到resolvedDataSources中获取数据源(resolvedDataSources就是一个Map,上面已经提到过了);如果没有找到数据源,就返回默认的数据源。determineCurrentLookupKey就是程序员配置动态数据源需要自己实现的方法。

二、实现

引入Maven依赖

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>2.1.10.RELEASE</version>
  5. <relativePath/> <!-- lookup parent from repository -->
  6. </parent>
  7. <properties>
  8. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  9. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  10. <java.version>1.8</java.version>
  11. </properties>
  12. <dependencies>
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-web</artifactId>
  16. </dependency>
  17. <!--如果要用传统的xml或properties配置,则需要添加此依赖-->
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-configuration-processor</artifactId>
  21. </dependency>
  22. <dependency>
  23. <groupId>tk.mybatis</groupId>
  24. <artifactId>mapper-spring-boot-starter</artifactId>
  25. <version>2.1.5</version>
  26. </dependency>
  27. <dependency>
  28. <groupId>mysql</groupId>
  29. <artifactId>mysql-connector-java</artifactId>
  30. <scope>runtime</scope>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-starter-jdbc</artifactId>
  35. </dependency>
  36. <dependency>
  37. <groupId>org.springframework.boot</groupId>
  38. <artifactId>spring-boot-starter-aop</artifactId>
  39. </dependency>
  40. <!-- swagger -->
  41. <dependency>
  42. <groupId>io.springfox</groupId>
  43. <artifactId>springfox-swagger-ui</artifactId>
  44. <version>2.9.2</version>
  45. </dependency>
  46. <dependency>
  47. <groupId>io.springfox</groupId>
  48. <artifactId>springfox-swagger2</artifactId>
  49. <version>2.9.2</version>
  50. </dependency>
  51. <!-- spring security -->
  52. <dependency>
  53. <groupId>org.springframework.boot</groupId>
  54. <artifactId>spring-boot-starter-security</artifactId>
  55. </dependency>
  56. <!-- jwt -->
  57. <dependency>
  58. <groupId>io.jsonwebtoken</groupId>
  59. <artifactId>jjwt</artifactId>
  60. <version>0.9.1</version>
  61. </dependency>
  62. <!-- fastjson -->
  63. <dependency>
  64. <groupId>com.alibaba</groupId>
  65. <artifactId>fastjson</artifactId>
  66. <version>1.2.70</version>
  67. </dependency>
  68. <dependency>
  69. <groupId>org.projectlombok</groupId>
  70. <artifactId>lombok</artifactId>
  71. <optional>true</optional>
  72. </dependency>
  73. </dependencies>
  74. <build>
  75. <plugins>
  76. <plugin>
  77. <groupId>org.springframework.boot</groupId>
  78. <artifactId>spring-boot-maven-plugin</artifactId>
  79. </plugin>
  80. <plugin>
  81. <groupId>org.mybatis.generator</groupId>
  82. <artifactId>mybatis-generator-maven-plugin</artifactId>
  83. <version>1.3.6</version>
  84. <configuration>
  85. <configurationFile>
  86. ${basedir}/src/main/resources/generator/generatorConfig.xml
  87. </configurationFile>
  88. <overwrite>true</overwrite>
  89. <verbose>true</verbose>
  90. </configuration>
  91. <dependencies>
  92. <dependency>
  93. <groupId>mysql</groupId>
  94. <artifactId>mysql-connector-java</artifactId>
  95. <version>5.1.41</version>
  96. </dependency>
  97. <dependency>
  98. <groupId>tk.mybatis</groupId>
  99. <artifactId>mapper</artifactId>
  100. <version>4.1.5</version>
  101. </dependency>
  102. </dependencies>
  103. </plugin>
  104. </plugins>
  105. </build>

主要实现步骤:一配置二使用

  • 启动类注册动态数据源
  • 配置文件中配置多个数据源
  • 在需要的方法上使用注解指定数据源
  • 1、在启动类添加 @Import({DynamicDataSourceRegister.class, MProxyTransactionManagementConfiguration.class})

  1. // 注册动态多数据源
  2. @Import({DynamicDataSourceRegister.class})
  3. @MapperScan("com.yibo.mapper")//扫描Mapper接口
  4. @SpringBootApplication
  5. public class Application {
  6. public static void main(String[] args) {
  7. SpringApplication.run(Application.class,args);
  8. }
  9. }
  • 2、配置文件配置内容为:
  1. # 默认数据源
  2. spring.datasource.url=jdbc:mysql://localhost:3306/user_center?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
  3. spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
  4. spring.datasource.hikari.username=root
  5. spring.datasource.hikari.password=yibo
  6. # 更多数据源
  7. custom.datasource.names=ds1,ds2
  8. custom.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
  9. custom.datasource.ds1.url=jdbc:mysql://localhost:3306/content_center?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
  10. custom.datasource.ds1.username=root
  11. custom.datasource.ds1.password=yibo
  12. custom.datasource.ds2.driver-class-name=com.mysql.cj.jdbc.Driver
  13. custom.datasource.ds2.url=jdbc:mysql://localhost:3306/trade?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
  14. custom.datasource.ds2.username=root
  15. custom.datasource.ds2.password=yibo
  16. mybatis.type-aliases-package: com.yibo.center.domain.entity
  17. mybatis.mapper-locations: classpath:mapper/*.xml
  18. mapper.identity: MYSQL
  19. mapper.not-empty: false
  20. #是否激活 swagger true or false
  21. swagger.enable=true
  • 3、使用方法
  1. import com.yibo.center.domain.entity.Share;
  2. import com.yibo.datasource.anno.TargetDataSource;
  3. import com.yibo.mapper.ShareMapper;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.stereotype.Service;
  6. import org.springframework.transaction.annotation.Transactional;
  7. import java.util.List;
  8. @Service
  9. public class ShareService {
  10. @Autowired
  11. private ShareMapper shareMapper;
  12. @TargetDataSource(name = "ds1")
  13. @Transactional
  14. public List<Share> findAll(){
  15. return shareMapper.selectAll();
  16. }
  17. }

  1. import com.yibo.center.domain.entity.TradeGoods;
  2. import com.yibo.center.domain.vo.TradeGoodsAO;
  3. import com.yibo.datasource.anno.TargetDataSource;
  4. import com.yibo.mapper.TradeGoodsMapper;
  5. import org.springframework.beans.BeanUtils;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Service;
  8. import org.springframework.transaction.annotation.Transactional;
  9. import java.util.Date;
  10. import java.util.List;
  11. /**
  12. * @Description:
  13. */
  14. @Service
  15. public class TradeGoodsService {
  16. @Autowired
  17. private TradeGoodsMapper tradeGoodsMapper;
  18. @TargetDataSource(name = "ds2")
  19. @Transactional
  20. public List<TradeGoods> findAll(){
  21. return tradeGoodsMapper.selectAll();
  22. }
  23. @TargetDataSource(name = "ds2")
  24. @Transactional
  25. public String addTradeGoods(TradeGoodsAO tradeGoodsAO){
  26. TradeGoods tradeGoods = new TradeGoods();
  27. BeanUtils.copyProperties(tradeGoodsAO,tradeGoods);
  28. tradeGoods.setAddTime(new Date());
  29. tradeGoodsMapper.insert(tradeGoods);
  30. return "SUCCESS";
  31. }
  32. }

  1. import com.yibo.center.domain.entity.User;
  2. import com.yibo.center.domain.vo.UserAo;
  3. import com.yibo.mapper.UserMapper;
  4. import org.springframework.beans.BeanUtils;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.stereotype.Service;
  7. import org.springframework.transaction.annotation.Transactional;
  8. import java.util.Date;
  9. import java.util.List;
  10. /**
  11. * @Description:
  12. */
  13. @Service
  14. public class UserService {
  15. @Autowired
  16. private UserMapper userMapper;
  17. public List<User> findAll(){
  18. return userMapper.selectAll();
  19. }
  20. @Transactional
  21. public User findById(Integer id){
  22. User user = new User();
  23. user.setId(id);
  24. return userMapper.selectOne(user);
  25. }
  26. @Transactional
  27. public String addUser(UserAo userAo){
  28. User user = new User();
  29. BeanUtils.copyProperties(userAo,user);
  30. user.setCreateTime(new Date());
  31. user.setUpdateTime(new Date());
  32. userMapper.insert(user);
  33. return "SUCCESS";
  34. }
  35. }

要注意的是,在使用MyBatis时,注解@TargetDataSource 不能直接在接口类Mapper上使用。

请将下面几个类放到Spring Boot项目中。

  • DynamicDataSource.java
  • DynamicDataSourceAspect.java
  • DynamicDataSourceContextHolder.java
  • DynamicDataSourceRegister.java
  • TargetDataSource.java

  1. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
  2. /**
  3. * @Description: 继承Spring AbstractRoutingDataSource实现路由切换
  4. */
  5. public class DynamicDataSource extends AbstractRoutingDataSource {
  6. @Override
  7. protected Object determineCurrentLookupKey() {
  8. return DynamicDataSourceContextHolder.getDataSourceType();
  9. }
  10. }

  1. import com.yibo.datasource.DynamicDataSourceContextHolder;
  2. import com.yibo.datasource.anno.TargetDataSource;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.aspectj.lang.JoinPoint;
  5. import org.aspectj.lang.annotation.After;
  6. import org.aspectj.lang.annotation.Aspect;
  7. import org.aspectj.lang.annotation.Before;
  8. import org.springframework.core.annotation.Order;
  9. import org.springframework.stereotype.Component;
  10. /**
  11. * @Description: 动态数据源通知
  12. */
  13. @Aspect
  14. //保证该AOP在@Transactional之前执行
  15. @Order(-1)
  16. @Component
  17. @Slf4j
  18. public class DynamicDataSourceAspect {
  19. /**
  20. * @Description 在方法执行之前执行 @annotation(ds) 会拦截有ds这个注解的方法即有 TargetDataSource这个注解的
  21. * @param @param point
  22. * @param @param ds
  23. * @param @throws Throwable 参数
  24. * @return void 返回类型
  25. * @throws
  26. */
  27. @Before("@annotation(ds)")
  28. public void changeDataSource(JoinPoint point, TargetDataSource ds)
  29. throws Throwable {
  30. String dsId = ds.name();
  31. if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
  32. log.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature());
  33. }
  34. else {
  35. log.debug("Use DataSource : {} > {}", ds.name(),point.getSignature());
  36. DynamicDataSourceContextHolder.setDataSourceType(ds.name());
  37. }
  38. }
  39. /**
  40. * @Description 在方法执行之后执行 @annotation(ds) 会拦截有ds这个注解的方法即有 TargetDataSource这个注解的
  41. * @param @param point
  42. * @param @param ds 参数
  43. * @return void 返回类型
  44. * @throws
  45. */
  46. @After("@annotation(ds)")
  47. public void restoreDataSource(JoinPoint point, TargetDataSource ds) {
  48. log.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());
  49. DynamicDataSourceContextHolder.clearDataSourceType();
  50. }
  51. }

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. /**
  4. * @Description: 动态数据源上下文管理
  5. */
  6. public class DynamicDataSourceContextHolder {
  7. //存放当前线程使用的数据源类型信息
  8. private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
  9. //存放数据源id
  10. public static List<String> dataSourceIds = new ArrayList<String>();
  11. //设置数据源
  12. public static void setDataSourceType(String dataSourceType) {
  13. contextHolder.set(dataSourceType);
  14. }
  15. //获取数据源
  16. public static String getDataSourceType() {
  17. return contextHolder.get();
  18. }
  19. //清除数据源
  20. public static void clearDataSourceType() {
  21. contextHolder.remove();
  22. }
  23. /**
  24. * 判断指定DataSrouce当前是否存在
  25. *
  26. * @param dataSourceId
  27. * @return
  28. */
  29. public static boolean containsDataSource(String dataSourceId){
  30. return dataSourceIds.contains(dataSourceId);
  31. }
  32. }

  1. import lombok.extern.slf4j.Slf4j;
  2. import org.springframework.beans.MutablePropertyValues;
  3. import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  4. import org.springframework.beans.factory.support.GenericBeanDefinition;
  5. import org.springframework.boot.jdbc.DataSourceBuilder;
  6. import org.springframework.context.EnvironmentAware;
  7. import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
  8. import org.springframework.core.env.Environment;
  9. import org.springframework.core.type.AnnotationMetadata;
  10. import javax.sql.DataSource;
  11. import java.util.HashMap;
  12. import java.util.Map;
  13. /**
  14. * @Description: 注册动态数据源
  15. * 初始化数据源和提供了执行动态切换数据源的工具类
  16. * EnvironmentAware(获取配置文件配置的属性值)
  17. */
  18. @Slf4j
  19. public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
  20. //指定默认数据源(springboot2.0默认数据源是hikari如何想使用其他数据源可以自己配置)
  21. private static final String DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource";
  22. //默认数据源
  23. private DataSource defaultDataSource;
  24. //用户自定义数据源
  25. private Map<String, DataSource> customDataSources = new HashMap<>();
  26. /**
  27. * 加载多数据源配置
  28. * @param env
  29. */
  30. @Override
  31. public void setEnvironment(Environment env) {
  32. initDefaultDataSource(env);
  33. initCustomDataSources(env);
  34. }
  35. /**
  36. * 初始化主数据源
  37. * @param env
  38. */
  39. private void initDefaultDataSource(Environment env) {
  40. // 读取主数据源
  41. Map<String, Object> dsMap = new HashMap<>();
  42. dsMap.put("driver", env.getProperty("spring.datasource.hikari.driver-class-name"));
  43. dsMap.put("url", env.getProperty("spring.datasource.url"));
  44. dsMap.put("username", env.getProperty("spring.datasource.hikari.username"));
  45. dsMap.put("password", env.getProperty("spring.datasource.hikari.password"));
  46. defaultDataSource = buildDataSource(dsMap);
  47. }
  48. /**
  49. * 初始化更多数据源
  50. * @param env
  51. */
  52. private void initCustomDataSources(Environment env) {
  53. // 读取配置文件获取更多数据源
  54. String dsPrefixs = env.getProperty("custom.datasource.names");
  55. for (String dsPrefix : dsPrefixs.split(",")) {
  56. // 多个数据源
  57. Map<String, Object> dsMap = new HashMap<>();
  58. dsMap.put("driver", env.getProperty("custom.datasource." + dsPrefix + ".driver-class-name"));
  59. dsMap.put("url", env.getProperty("custom.datasource." + dsPrefix + ".url"));
  60. dsMap.put("username", env.getProperty("custom.datasource." + dsPrefix + ".username"));
  61. dsMap.put("password", env.getProperty("custom.datasource." + dsPrefix + ".password"));
  62. DataSource ds = buildDataSource(dsMap);
  63. customDataSources.put(dsPrefix, ds);
  64. }
  65. }
  66. @Override
  67. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  68. Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
  69. // 将主数据源添加到更多数据源中
  70. targetDataSources.put("dataSource", defaultDataSource);
  71. DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
  72. // 添加更多数据源
  73. targetDataSources.putAll(customDataSources);
  74. for (String key : customDataSources.keySet()) {
  75. DynamicDataSourceContextHolder.dataSourceIds.add(key);
  76. }
  77. // 创建DynamicDataSource
  78. GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
  79. beanDefinition.setBeanClass(DynamicDataSource.class);
  80. beanDefinition.setSynthetic(true);
  81. MutablePropertyValues mpv = beanDefinition.getPropertyValues();
  82. mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
  83. mpv.addPropertyValue("targetDataSources", targetDataSources);
  84. registry.registerBeanDefinition("dataSource", beanDefinition); // 注册到Spring容器中
  85. log.info("Dynamic DataSource Registry");
  86. }
  87. /**
  88. * 创建DataSource
  89. * @param dsMap
  90. * @return
  91. */
  92. @SuppressWarnings("unchecked")
  93. public DataSource buildDataSource(Map<String, Object> dsMap) {
  94. try {
  95. Object type = dsMap.get("type");
  96. if (type == null)
  97. type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
  98. Class<? extends DataSource> dataSourceType;
  99. dataSourceType = (Class<? extends DataSource>)Class.forName((String)type);
  100. log.info("dsMap:{}",dsMap);
  101. System.out.println(dsMap);
  102. String driverClassName = dsMap.get("driver").toString();
  103. String url = dsMap.get("url").toString();
  104. String username = dsMap.get("username").toString();
  105. String password = dsMap.get("password").toString();
  106. // 自定义DataSource配置
  107. DataSourceBuilder factory = DataSourceBuilder.create()
  108. .driverClassName(driverClassName)
  109. .url(url)
  110. .username(username)
  111. .password(password)
  112. .type(dataSourceType);
  113. return factory.build();
  114. }catch (ClassNotFoundException e) {
  115. e.printStackTrace();
  116. }
  117. return null;
  118. }
  119. }

  1. import java.lang.annotation.*;
  2. /**
  3. * @Description: 作用于类、接口或者方法上
  4. */
  5. @Target({ElementType.METHOD, ElementType.TYPE})
  6. @Retention(RetentionPolicy.RUNTIME)
  7. @Documented
  8. public @interface TargetDataSource {
  9. String name();
  10. }

本文代码博主是经过测试后没有问题才发出来共享给大家的。对于连接池参数配置会应用到所有数据源上。

比如配置一个:

spring.datasource.maximum-pool-size=80

那么我们所有的数据源都会自动应用上。

补充:

如果你使用的是SpringMVC,并集成了Shiro,一般按网上的配置你可能是:

  1. <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
  2. <property name="proxyTargetClass" value="true" />
  3. </bean>
  4. <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
  5. <property name="securityManager" ref="securityManager"/>
  6. </bean>

那么你请不要这样做,请按下面方法配置:

  1. <!-- AOP式方法级权限检查 -->
  2. <!-- 不要使用 DefaultAdvisorAutoProxyCreator 会出现二次代理的问题,这里不详述。 mark by shanhy 2016-05-15 -->
  3. <aop:config proxy-target-class="true"/>
  4. <!-- 或者你使用了 <aop:aspectj-autoproxy proxy-target-class="true" /> 也可以。 -->
  5. <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
  6. <property name="securityManager" ref="securityManager"/>
  7. </bean>

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/677984
推荐阅读
相关标签
  

闽ICP备14008679号