SpringCache自定义过期时间及自动刷新

前端开发 作者: 2024-08-22 13:05:01
背景前提 阅读说明(十分重要) 对于Cache和SpringCache原理不太清楚的朋友,可以看我之前写的文章:Springboot中的缓存Cache和CacheManager原理介绍 可能有人会问为

背景前提

坑一:自己造轮子

坑二:Cache的设计思想不对(最重要)



坑三:对@Cacheble的理解太浅

代码展示

 1 /**
 2  * @author NiceBin
 3  * @description: 缓存更新接口,在Cache实现类的get方法上注解即可
 4  * @date 2019/11/18 8:56
 5  */
 6 @Target({ElementType.METHOD,ElementType.TYPE})
 7 @Retention(RetentionPolicy.RUNTIME)
 8 @Inherited
 9 public @interface UpdateCache {
10 }
 * 本系统的缓存接口,SystemCacheMgr统一保存数据记录的时间和控制缓存自动刷新流程
 *
 * 为了实现数据快过期前的自动刷新,需要以下操作:
 5  * 1.实现此接口
 *   如果用如RedisCacheManager这种写好的类,需要子类继承再实现此接口
 *   如果Cache是CacheManager内部生成的,还需要重写createCache方法
 *   使生成的Cache走一遍Spring初始化Bean的过程,交给Spring管理
 *   这里主要为了Spring帮忙生成代理类,让注解生效
10  * 2.实现了 {@link Cache} 接口的类在get方法上加上注解 { UpdateCache} 才有更新效果,所以如果要用如RedisCache
11  *   这种写好的类,需要子类继承,并重写get方法
12  *   然后在get方法上加@UpdateCache
13  14 public interface I_SystemCacheMgr extends CacheManager{
15     16      * 该数据是否过期
17      * true为已经过期
18      * @param cacheName 缓存名字
19  id 数据id
20  saveTime 该缓存内该数据的存储时间
21 @return
22 @throws Exception
23      24     boolean isApproachExpire(String cacheName,Object id,Timestamp saveTime) throws Exception;
25 
26     27      * 删除指定Cache里的指定数据
28  cacheName
29  id
30 31      32     void remove(String cacheName,Object id) 33 
34     35      * 清除所有缓存内容
36 37      38     void clearAll() 39 
40     41      * 获得所有的Cache
42 43      44     ConcurrentMap<String,Cache> getAllCaches();
45 }
 * @description:    增强RedisCache
 *                  为了能在get方法写上@Update注解,实现自动刷新
 * @date 2019/7/4 13:24
 6  class RedisCacheEnhance  RedisCache {
 8 
 9          * Create new { RedisCacheEnhance}.
     *
 name        must not be {@literal null}.
13  cacheWriter must not be { cacheConfig must not be {15      16     protected RedisCacheEnhance(String name,RedisCacheWriter cacheWriter,RedisCacheConfiguration cacheConfig) {
17         super(name,cacheWriter,cacheConfig);
    }
19 
    @UpdateCache
21     public ValueWrapper get(Object key){
22         System.out.println("进入get方法");
23         return .get(key);
24 26 27     public <T> T get(Object key,@Nullable Class<T> type){
28         .get(key,type);
30 
31  valueLoader){
33         34     }
  1   2   3  * @description: RedisCacheManager增强类,为了实现本系统缓存自动更新功能
  4  * @date 2019/11/25 9:07
  5    6 class RedisCacheMgr extends RedisCacheManager implements I_SystemCacheMgr {
  7 
  8     private final RedisCacheWriter cacheWriter;
  9     private ConcurrentMap<String,Cache> caches = new ConcurrentHashMap<>();
 10 
 11     private DefaultListableBeanFactory defaultListableBeanFactory;
 12 
 13     public RedisCacheMgr(RedisCacheWriter cacheWriter,RedisCacheConfiguration defaultCacheConfiguration,Map<String,RedisCacheConfiguration> initialCacheConfigurations,boolean allowInFlightCacheCreation) {
 14         (cacheWriter,defaultCacheConfiguration,initialCacheConfigurations,allowInFlightCacheCreation);
 15         this.cacheWriter = cacheWriter;
 16 
 17  18 
 19      20      * 重写createRedisCache的方法,生成自己定义的Cache
 21      * 这里主要要让Spring来生成代理Cache,不然在Cache上的注解是无效的
 22  name
 23  cacheConfig
 24  25       26     @Override
 27      RedisCacheEnhance createRedisCache(String name,@Nullable RedisCacheConfiguration cacheConfig) {
 28         //利用Spring生成代理Cache
 29         BeanDefinition beanDefinition = new RootBeanDefinition(RedisCacheEnhance.class 30         因为只有有参构造方法,所以要添加参数
 31         ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues();
 32         constructorArgumentValues.addIndexedArgumentValue(0,name);
 33         constructorArgumentValues.addIndexedArgumentValue(1 34         constructorArgumentValues.addIndexedArgumentValue(2 35 
 36         如果有属性需要设置,还能这样做,不过需要有对应属性名的set方法
 37         definition.getPropertyValues().add("propertyName",beanDefinition.getBeanClassName());
 38 
 39         ApplicationContext applicationContext = SystemContext.getSystemContext()
 40                 .getApplicationContext();
 41         需要这样获取的DefaultListableBeanFactory类才能走一遍完整的Bean初始化流程!!
 42         像applicationContext.getBean(DefaultListableBeanFactory.class)都不好使!!
 43         DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory)applicationContext.getAutowireCapableBeanFactory();
 44         defaultListableBeanFactory.registerBeanDefinition(name,beanDefinition);
 45 
 46         RedisCacheEnhance redisCacheEnhance = (RedisCacheEnhance)applicationContext.getBean(name);
 47         caches.put(name,redisCacheEnhance);
 48         return redisCacheEnhance;
 49  50 
 51      52      * 过期规则为:缓存有效时间-(目前时间-记录时间)<= 随机时间
 53      * 随机时间是防止同一时刻过期时间太多,造成缓存雪崩,在SystemStaticValue中缓存项里配置
 54      * true为将要过期(可以刷新了)
 55  56  cacheName 缓存名称
 57  58  saveTime 储存时间
 59  60       61  62      NoSuchAlgorithmException {
 63         long ttl = -1;
 64 
 65         RedisCacheConfiguration configuration = this.getCacheConfigurations().get(cacheName);
 66         ttl = configuration.getTtl().getSeconds();
 67 
 68         if (ttl != -1 && saveTime!=null) {
 69                 int random = Tool.getSecureRandom(SystemStaticValue.CACHE_MIN_EXPIRE,SystemStaticValue.CACHE_MAX_EXPIRE);
 70                 Date date = new Date();
 71                 long theNowTime = date.getTime() / 1000 72                 long theSaveTime = saveTime.getTime() / 1000 73                 if (ttl - (theNowTime - theSaveTime) <= random) {
 74                     true 75                 }
 76         }
 77         false 78  79 
 80  81      Exception {
 82         Cache cache = .getCache(cacheName);
 83         cache.evict(id);
 84  85 
 86 
 87      88  89  90  91       92  93      94         Collection<String> cacheNames = .getCacheNames();
 95         Iterator<String> iterator = cacheNames.iterator();
 96         while (iterator.hasNext()) {
 97             String cacheName = iterator.next();
 98             Cache redisCache =  99             redisCache.clear();
100 101 102 
103 104     public ConcurrentMap<String,1)"> getAllCaches() {
105          caches;
106 107 }

知识点:如何阅读源码来帮助自己注册目标类

1 RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
2                 .cacheDefaults(defaultCacheConfig)  默认配置(强烈建议配置上)。  比如动态创建出来的都会走此默认配置
3                 .withInitialCacheConfigurations(initialCacheConfiguration)  不同cache的个性化配置
4                 .build();
* Create new instance of { RedisCacheManager} with configuration options applied.
*
* @return new instance of { RedisCacheManager}.
 6  RedisCacheManager build() {
 7 
 8   RedisCacheManager cm =  RedisCacheManager(cacheWriter,initialCaches,                    allowInFlightCacheCreation);
10 
  cm.setTransactionAware(enableTransactions);
12 
13    cm;
14 }
1  RedisCacheManager(RedisCacheWriter cacheWriter,1)">2             Map<String,1)">3 
4         5 
6         Assert.notNull(initialCacheConfigurations,"InitialCacheConfigurations must not be null!"7 
8         .initialCacheConfiguration.putAll(initialCacheConfigurations);
9     }
 RedisCacheManagerBuilder(RedisCacheWriter cacheWriter) {
2             3         }
1         static RedisCacheManagerBuilder fromConnectionFactory(RedisConnectionFactory connectionFactory) {
2 
3             Assert.notNull(connectionFactory,"ConnectionFactory must not be null!"4 
5             return builder( DefaultRedisCacheWriter(connectionFactory));
6         }
 RedisCacheWriter {
 2 
 3      RedisCacheWriter} without locking behavior.
 connectionFactory must not be { DefaultRedisCacheWriter}.
 8       RedisCacheWriter nonLockingRedisCacheWriter(RedisConnectionFactory connectionFactory) {
11         Assert.notNull(connectionFactory,1)">13          DefaultRedisCacheWriter(connectionFactory);
15 
 RedisCacheWriter} with locking behavior.
21      22      RedisCacheWriter lockingRedisCacheWriter(RedisConnectionFactory connectionFactory) {
23 
24         Assert.notNull(connectionFactory,1)">26         new DefaultRedisCacheWriter(connectionFactory,Duration.ofMillis(50));
27     }

知识点:用代码动态向Spring注册Bean

1 ApplicationContext applicationContext =2 3         5         DefaultListableBeanFactory defaultListableBeanFactory =6         defaultListableBeanFactory.registerBeanDefinition(name,beanDefinition);
 * @description: CacheManager初始化
 *               目前系统只用一个Manager,使用RedisCacheManager
 *               根据SystemStaticValue中的SystemCache枚举内容进行Cache的注册
 *               配置启动前需要DefaultListableBeanFactory.class先加载完成
 *               不然CacheManager或者Cache想用的时候会报错
 * @date 2019/11/13 17:02
 9  @Configuration
11 @Import(DefaultListableBeanFactory.)
 CacheConfig {
13 
    @Autowired
15     RedisConnectionFactory redisConnectionFactory;
16 
    @Bean
18      RedisCacheMgr cacheManager() {
20         创建Json自定义序列化器
21         FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.22         包装成SerializationPair类型
23         RedisSerializationContext.SerializationPair serializationPair = RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer);
24 
25         RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
26                 .entryTtl(Duration.ofDays(1))
27                 .computePrefixWith(cacheName -> "Cache"+cacheName);
 针对不同cacheName,设置不同的过期时间,用了双括号初始化方法~
29         Map<String,RedisCacheConfiguration> initialCacheConfiguration = new HashMap<String,RedisCacheConfiguration>() {{
30             SystemStaticValue.SystemCache[] systemCaches = SystemStaticValue.SystemCache.values();
31             Arrays.asList(systemCaches).forEach((systemCache)->
32                     put(systemCache.getCacheName(),RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(systemCache.getSurviveTime()))
33                     .serializeValuesWith(serializationPair)));
34         }};
35         RedisCacheMgr redisCacheMgr = new RedisCacheMgr(RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory),defaultCacheConfig,initialCacheConfiguration,1)">36 
37         设置白名单---非常重要********
38         /*
39         使用fastjson的时候:序列化时将class信息写入,反解析的时候,
40         fastjson默认情况下会开启autoType的检查,相当于一个白名单检查,
        如果序列化信息中的类路径不在autoType中,autoType会默认开启
        反解析就会报com.alibaba.fastjson.JSONException: autoType is not support的异常
43         44         ParserConfig.getGlobalInstance().addAccept("com.tophousekeeper"45          redisCacheMgr;
46 47 }
要实现对象的缓存,定义自己的序列化和反序列化器。使用阿里的fastjson来实现的方便多。
 3  class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
 5     static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8" 6     private Class<T> clazz;
 8     public FastJsonRedisSerializer(Class<T> clazz) {
 9         10         this.clazz =14     byte[] serialize(T t)  SerializationException {
15         if (null == t) {
16             new byte[0];
18          JSON.toJSONString(t,SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
20 
public T deserialize(byte[] bytes) null == bytes || bytes.length <= 024             25 26         String str =  String(bytes,DEFAULT_CHARSET);
27          (T) JSON.parSEObject(str,clazz);
29 }
 SystemStaticValue {
   .....
以下为缓存信息的配置(CACHE开头)--------------------------------------------------------
 4     系统缓存名称及过期时间(秒)
enum SystemCache{
 6         每日缓存,有效时间24小时
 7         DAY("dailyCache",60),1)"> 8         半日缓存,有效时间12小时
 9         HALF_DAY("halfDayCache",12*60*601小时缓存
11         ONE_HOUR("oneHour",1*60*6012         半小时缓存
13         HALF_HOUR("halfHour",30*6014          String cacheName;
long surviveTime;
17         SystemCache(String cacheName,1)"> surviveTime){
18             this.cacheName = cacheName;
19             this.surviveTime =21 
 String getCacheName() {
23             void setCacheName(String cacheName) {
27             29 
30          getSurviveTime() {
31             34         void setSurviveTime( surviveTime) {
35             37 38 }

知识点:@Import的重要性

知识点:自定义序列化

知识点:Lambda表达式

 * @description: 记录被 { Cacheable} 注解过的方法信息,为了主动更新缓存去调用对应方法
 * @date 2019/11/26 16:28
 CacheInvocation {
 7      Object key;
 Object targetBean;
 Method targetMethod;
10      Object[] arguments;
11 
12      CacheInvocation(Object key,Object targetBean,Method targetMethod,Object[] arguments) {
this.key = key;
this.targetBean = targetBean;
this.targetMethod = targetMethod;
16         反射时不用检查修饰符,略微提高性能
this.targetMethod.setAccessible(if (arguments != null && arguments.length != 0this.arguments = Arrays.copyOf(arguments,arguments.length);
22 
23      Object[] getArguments() {
24          arguments;
26 
 Object getTargetBean() {
31      Method getTargetMethod() {
32         34 
35      Object getKey() {
36         38 }
 * @description: 刷新缓存某个数据的任务
 * @date 2019/11/29 15:29
class UpdateDataTask  Callable {
将要执行的方法信息
 CacheInvocation cacheInvocation;
对应要操作的缓存
 Cache cache;
11     对应要更新的数据id
 Object id;
     * 初始化任务
 cacheInvocation
 cache
19      20      UpdateDataTask(CacheInvocation cacheInvocation,Cache cache,Object id){
21         this.cacheInvocation = cacheInvocation;
this.cache = cache;
this.id = id;
public Object call() if(cacheInvocation == ){
29             throw new SystemException(SystemStaticValue.CACHE_EXCEPTION_CODE,"更新数据线程方法信息不能为null"        cache.put(id,methodInvoke());
     * 代理方法的调用
38      39     private Object methodInvoke()  Exception{
40         MethodInvoker methodInvoker =  MethodInvoker();
        methodInvoker.setArguments(cacheInvocation.getArguments());
        methodInvoker.setTargetMethod(cacheInvocation.getTargetMethod().getName());
43         methodInvoker.setTargetObject(cacheInvocation.getTargetBean());
44         methodInvoker.prepare();
 methodInvoker.invoke();
47 }
 * @description: 本系统的缓存管理器
 * 数据自动刷新功能,要配合 { UpdateCache}才能实现
  5  * 目前没办法实现缓存纯自动更新,必须要使用到该缓存拿数据进行触发
  7  * 纯自动更新没有意义,假设一个数据放了半小时没人访问要过期了,那就过期吧
  8  * 因为缓存前提是一段时间频繁访问的数据,如果都没人访问了,就不能称之为缓存
  9  * 不然就是一个系统长期存在的动态变量,不适用于缓存
 10  * @date 2019/11/14 16:18
 11   12 @Component
 13  SystemCacheMgr {
 14     目前系统只考虑一个CacheManager
 15     必须有一个I_SystemCache的实现类,多个实现类用@Primary注解,类似于Spring的缓存管理器
 16  17      I_SystemCacheMgr defaultCacheMgr;
 18     系统的线程池类
 19  20      SystemThreadPool systemThreadPool;
 21     所有缓存的所有数据记录Map
 22     外部Map中,key为缓存名称,value为该缓存内的数据储存信息Map
 23     内部Map中,key为数据的id,value为记录该数据的储存信息
 24     private ConcurrentHashMap<String,ConcurrentHashMap<Object,DataInfo>> dataInfoMaps =  25 
 26      27      * 储存信息内部类,用于记录
 28      * 获取要调用获取方法,因为加锁了线程才安全
 29       30      DataInfo {
 31         记录该数据的时间
 32          Timestamp saveTime;
 33         获得此数据的方法信息
 34          35         保证只有一个线程提前更新此数据
 ReentrantLock lock;
 37 
 38         synchronized  setSaveTime(Timestamp saveTime) {
 39             this.saveTime = saveTime;
 41 
 42 
 43          setCacheInvocation(CacheInvocation cacheInvocation) {
 44              45  46 
 47          setLock(ReentrantLock lock) {
 48             this.lock = lock;
 50  51 
 52          * 获得DataInfo类,如果为空则创建一个
 57       58      DataInfo getDataInfo(String cacheName,Object id) {
 59         ConcurrentHashMap<Object,DataInfo> dateInfoMap = dataInfoMaps.get((cacheName));
 60         DataInfo dataInfo;
 61         if (dateInfoMap ==  62             简单的锁住了,因为创建这个对象挺快的
 63             synchronized ( 64                 重新获取一次进行判断,因为dateInfoMap是局部变量,不能保证同步
 65                 dateInfoMap = 66                  67                     dateInfoMap =  68                     dataInfo =  DataInfo();
 69                     dataInfo.setLock(new ReentrantLock( 70                     dateInfoMap.put(id,dataInfo);
 71                     dataInfoMaps.put(cacheName,dateInfoMap);
 72  73             }
 74  75         这里不能用else,因为多线程同时进入if,后面进的dataInfo会是null
 76         dataInfo = dateInfoMap.get(id);
 77 
 78          dataInfo;
 79  80 
 82      * 为该数据放入缓存的时间记录
 85       86      recordDataSaveTime(String cacheName,1)"> 87         Date date =  88         Timestamp nowtime =  Timestamp(date.getTime());
 89         DataInfo dataInfo = getDataInfo(cacheName,id);
        dataInfo.setSaveTime(nowtime);
 91  92 
 94      * 记录获得此数据的方法信息,为了主动更新缓存时的调用
 95  96  cacheName    缓存名称
 97  id           数据id
 98  targetBean   目标类
 targetMethod 目标方法
 arguments    目标方法的参数
101      102      recordCacheInvocation(String cacheName,String id,1)">103         DataInfo dataInfo =104         CacheInvocation cacheInvocation =  CacheInvocation(id,targetBean,targetMethod,arguments);
锁在这方法里面有
        dataInfo.setCacheInvocation(cacheInvocation);
107 108 
109     110      * 数据自动刷新功能,要配合 {111      * 原理:先判断数据是否过期,如果数据过期则从缓存删除。
112 113 114  id        数据id
115 116      117     void autoUpdate(String cacheName,1)">118         DataInfo dataInfo =119         Cache cache = defaultCacheMgr.getCache(cacheName);
120 
121 
122         如果没有保存的时间,说明该数据还从未载入过
123         if (dataInfo.saveTime == 124             125 126         if (defaultCacheMgr.isApproachExpire(cacheName,id,dataInfo.saveTime)) {
127              (dataInfo.lock.tryLock()) { //这里用非阻塞的tryLock,有一个用户进来触发缓存过期判断即可,其他用户继续获得当时的缓存值 
128                 获取锁后再次判断数据是否过期
129                 130                     ThreadPoolExecutor threadPoolExecutor = systemThreadPool.getThreadPoolExecutor();
131                     UpdateDataTask updateDataTask =  UpdateDataTask(dataInfo.cacheInvocation,cache,1)">132                     FutureTask futureTask =  FutureTask(updateDataTask);
133 
134                     try135                         threadPoolExecutor.submit(futureTask);
136                         futureTask.get(1137                         如果上一步执行完成没报错,那么重新记录保存时间
138                         recordDataSaveTime(cacheName,1)">139                     } catch (TimeoutException ex) {
140                         如果访问数据库超时
141                         142                     }  (RejectedExecutionException ex) {
143                         如果被线程池拒绝了
144                         145                     } finally146                         dataInfo.lock.unlock();
147                     }
148 149 150 151 152 
153     154 155      156     157         defaultCacheMgr.clearAll();
158 159 
160     以下为Set和Get
161      I_SystemCacheMgr getDefaultCacheMgr() {
162          defaultCacheMgr;
163 164 
165      setDefaultCacheMgr(I_SystemCacheMgr defaultCacheMgr) {
166         this.defaultCacheMgr =167 168 
169     public ConcurrentHashMap<String,DataInfo>> getDataInfoMaps() {
170          dataInfoMaps;
171 172 
173     void setDataInfoMaps(ConcurrentHashMap<String,1)"> dataInfoMaps) {
174         this.dataInfoMaps =175 176 }

知识点:再次说下接口的重要性

 * @description: 处理缓存注解的地方:包括@UpdateCache,@Cacheable
 * @date 2019/11/18 14:57
@Aspect
 CacheAspect {
    SystemCacheMgr systemCacheMgr;
13          * 数据注册到SystemCacheMgr
     * 为数据自动更新做准备
16      17     @Before("@annotation(org.springframework.cache.annotation.Cacheable)" registerCache(JoinPoint joinPoint){
19         System.out.println("拦截了@Cacheable"获取到该方法前的@Cacheable注解,来获取CacheName和key的信息
21         Method method = Tool.getSpecificMethod(joinPoint);
22         Cacheable cacleable = method.getAnnotation(Cacheable.23         String[] cacheNames = cacleable.value()!=null?cacleable.value():cacleable.cacheNames();
24         String theKey = cacleable.key();
25         取出来的字符串是'key',需要去掉''
26         String key = theKey.substring(1,theKey.length()-127         Arrays.stream(cacheNames).forEach(cacheName ->{
28             记录数据保存时间
            systemCacheMgr.recordDataSaveTime(cacheName,key);
30             记录数据对应的方法信息
            systemCacheMgr.recordCacheInvocation(cacheName,key,joinPoint.getTarget(),method,joinPoint.getArgs());
        });
     * 检测该键是否快过期了
     * 如果快过期则进行自动更新
38  joinPoint
39      40     @Before(value = "@annotation(com.tophousekeeper.system.annotation.UpdateCache)&&args(id)"41     void checkExpire(JoinPoint joinPoint,String id) 42         System.out.println("拦截了@UpdateCache"43         RedisCacheEnhance redisCacheEnhance = (RedisCacheEnhance) joinPoint.getTarget();
        systemCacheMgr.autoUpdate(redisCacheEnhance.getName(),1)">45 46 }
 Tool {
     * 获得代理类方法中真实的方法
     * 小知识:
     * ClassUtils.getMostSpecificMethod(Method method,Class<?> targetClass)
     * 该方法是一个有趣的方法,他能从代理对象上的一个方法,找到真实对象上对应的方法。
     * 举个例子,MyComponent代理之后的对象上的someLogic方法,肯定是属于cglib代理之后的类上的method,
     * 使用这个method是没法去执行目标MyComponent的someLogic方法,
     * 这种情况下,就可以使用getMostSpecificMethod,
     * 找到真实对象上的someLogic方法,并执行真实方法
     * BridgeMethodResolver.findBridgedMethod(Method bridgeMethod)
     * 如果当前方法是一个泛型方法,则会找Class文件中实际实现的方法
 poxyMethod 代理的方法
 targetclass 真实的目标类
18      19      Method getSpecificMethod(Method poxyMethod,Class targetclass){
20         Method specificMethod = ClassUtils.getMostSpecificMethod(poxyMethod,targetclass);
21         specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
 specificMethod;
23 25          * AopProxyUtils.ultimateTargetClass()
     * 获取一个代理对象的最终对象类型
 joinPoint 切面的切点类
32      33      Method getSpecificMethod(JoinPoint joinPoint){
34         MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
35         Method poxyMethod = methodSignature.getMethod();
36         Class targetClass = AopProxyUtils.ultimateTargetClass(joinPoint.getTarget());
 Tool.getSpecificMethod(poxyMethod,targetClass);
39 }

总结

原创声明
本站部分文章基于互联网的整理,我们会把真正“有用/优质”的文章整理提供给各位开发者。本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
本文链接:http://www.jiecseo.com/news/show_66680.html