欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

SpringCache框架加载/拦截原理详解

程序员文章站 2024-02-24 15:14:04
背景 项目a中需要多数据源的实现,比如userdao.getalluserlist() 需要从readonly库中读取,但是userdao.insert() 需要...


背景

项目a中需要多数据源的实现,比如userdao.getalluserlist() 需要从readonly库中读取,但是userdao.insert() 需要插入主(写)库

就需要在dao层的方法调用上面添加注解!

了解后知道-接口通过jdk代理(mybatis的mapper接口就是通过jdk代理动态生成的-> mapperfactorybean.class )的,没办法被aop的拦截(注解配置的拦截)

//dao
  @pointcut("@annotation(com.kaola.cs.data.common.aspect.datasourceselect)")
  public void dao() {
  }

然后碰巧接触了项目b,使用了springcache模块,但是spring的cache模块居然能够拦截(spring-cache也是通过注解拦截!!!)

引起了我的兴趣,就把源码翻了一遍

springcache的用途

与 mybatis 对比

1.  spring-cache 是基于spring的方法级别的,也就是说你方法做了啥不关心,它只负责缓存方法结果

mybatis 的缓存(cachingexecutor / baseexecutor) 是基于数据库查询结果的缓存

 2.  spring-cache 可以配置各种类型的缓存介质(redis , ehcache , hashmap, 甚至db等等) -> 它仅仅是提供接口和默认实现,可以自己拓展

mybatis 的缓存是hashmap,单一!!lowb

springcache 的配置

1.注解(spring-boot) 2.xml配置

这里只讲注解,但是初始化的类都是一样的!!!

定义 cacheconfigure.java 就能直接使用

@enablecaching
@configuration
public class cacheconfigure extends cachingconfigurersupport {
  @override
  @bean
  public cachemanager cachemanager() {
    simplecachemanager result = new simplecachemanager();
    list<cache> caches = new arraylist<>();
    caches.add(new concurrentmapcache("testcache"));
    result.setcaches(caches);
    return result;
  }

  @override
  @bean
  public cacheerrorhandler errorhandler() {
    return new simplecacheerrorhandler();
  }

}

通过 @enablecaching 注解可以找到 spring-cache 初始化的核心类

proxycachingconfiguration.java

@configuration
@role(beandefinition.role_infrastructure)
public class proxycachingconfiguration extends abstractcachingconfiguration {

 @bean(name = cachemanagementconfigutils.cache_advisor_bean_name)
 @role(beandefinition.role_infrastructure)
 public beanfactorycacheoperationsourceadvisor cacheadvisor() {
 beanfactorycacheoperationsourceadvisor advisor = new beanfactorycacheoperationsourceadvisor();
 advisor.setcacheoperationsource(cacheoperationsource());
 advisor.setadvice(cacheinterceptor());
 if (this.enablecaching != null) {
  advisor.setorder(this.enablecaching.<integer>getnumber("order"));
 }
 return advisor;
 }

 @bean
 @role(beandefinition.role_infrastructure)
 public cacheoperationsource cacheoperationsource() {
 return new annotationcacheoperationsource();
 }

 @bean
 @role(beandefinition.role_infrastructure)
 public cacheinterceptor cacheinterceptor() {
 cacheinterceptor interceptor = new cacheinterceptor();
 interceptor.configure(this.errorhandler, this.keygenerator, this.cacheresolver, this.cachemanager);
 interceptor.setcacheoperationsource(cacheoperationsource());
 return interceptor;
 }

}

通过注解,把3个类的bean 实例化: beanfactorycacheoperationsourceadvisor 、cacheoperationsource 、cacheinterceptor
说一下这3个类的作用

beanfactorycacheoperationsourceadvisor.java

/*
 beanfactorycacheoperationsourceadvisor 继承了 abstractbeanfactorypointcutadvisor
 在spring 中的效果就是,在每个bean的初始化时 (每个bean都会被加载成 advised 对象 -> 有 targetsource 和 advisor[] 数组)
 每个bean被调用方法的时候都是先遍历advisor的方法,然后在调用原生bean(也就是targetsource)的方法,实现了aop的效果

 bean 加载的时候 beanfactorycacheoperationsourceadvisor 的 getpointcut()-> 也就是 cacheoperationsourcepointcut 就会被获取,然后调用 
 cacheoperationsourcepointcut.matches()方法, 用来匹配对应的bean 
 假设bean 在 beanfactorycacheoperationsourceadvisor 的扫描中 matchs() 方法返回了true
 结果就是 
  在每个bean的方法被调用的时候 cacheinterceptor 中的 invoke() 方法就会被调用 

 总结:
  spring-cache 也完成了aop一样的实现(spring-aop也是这样做的)

 重点就是在 cacheoperationsourcepointcut.matchs() 方法中,怎么匹配接口的了 这里先不说后面具体介绍!!!!

*/
public class beanfactorycacheoperationsourceadvisor extends abstractbeanfactorypointcutadvisor {

 @nullable
 private cacheoperationsource cacheoperationsource;

 private final cacheoperationsourcepointcut pointcut = new cacheoperationsourcepointcut() {
 @override
 @nullable
 protected cacheoperationsource getcacheoperationsource() {
  return cacheoperationsource;
 }
 };


 /**
 * set the cache operation attribute source which is used to find cache
 * attributes. this should usually be identical to the source reference
 * set on the cache interceptor itself.
 */
 public void setcacheoperationsource(cacheoperationsource cacheoperationsource) {
 this.cacheoperationsource = cacheoperationsource;
 }

 /**
 * set the {@link classfilter} to use for this pointcut.
 * default is {@link classfilter#true}.
 */
 public void setclassfilter(classfilter classfilter) {
 this.pointcut.setclassfilter(classfilter);
 }

 @override
 public pointcut getpointcut() {
 return this.pointcut;
 }

}

cacheoperationsource.java 是个接口

实现类是 -> annotationcacheoperationsource.java 重点是父类 -> abstractfallbackcacheoperationsource.java

讲解一下:

代码量很少,主要是 attributecache 的封装使用,通过把 method - cacheoperation

然后在 cacheinterceptor.invoke() 的时候通过invocation 获取到 method-class 然后调用cacheoperationsource.getcacheoperations() 获取到 cacheoperation
cacheoperation 其实就是触发对应spring-cache 注解的操作-获取缓存的实现了

public abstract class abstractfallbackcacheoperationsource implements cacheoperationsource {

 /**
 * canonical value held in cache to indicate no caching attribute was
 * found for this method and we don't need to look again.
 */
 private static final collection<cacheoperation> null_caching_attribute = collections.emptylist();


 /**
 * logger available to subclasses.
 * <p>as this base class is not marked serializable, the logger will be recreated
 * after serialization - provided that the concrete subclass is serializable.
 */
 protected final log logger = logfactory.getlog(getclass());

 /**
 * cache of cacheoperations, keyed by method on a specific target class.
 * <p>as this base class is not marked serializable, the cache will be recreated
 * after serialization - provided that the concrete subclass is serializable.
 */
 private final map<object, collection<cacheoperation>> attributecache = new concurrenthashmap<>(1024);


 /**
 * determine the caching attribute for this method invocation.
 * <p>defaults to the class's caching attribute if no method attribute is found.
 * @param method the method for the current invocation (never {@code null})
 * @param targetclass the target class for this invocation (may be {@code null})
 * @return {@link cacheoperation} for this method, or {@code null} if the method
 * is not cacheable
 */
 @override
 @nullable
 public collection<cacheoperation> getcacheoperations(method method, @nullable class<?> targetclass) {
 if (method.getdeclaringclass() == object.class) {
  return null;
 }

 object cachekey = getcachekey(method, targetclass);
 collection<cacheoperation> cached = this.attributecache.get(cachekey);

 if (cached != null) {
  return (cached != null_caching_attribute ? cached : null);
 }
 else {
  collection<cacheoperation> cacheops = computecacheoperations(method, targetclass);
  if (cacheops != null) {
  if (logger.istraceenabled()) {
   logger.trace("adding cacheable method '" + method.getname() + "' with attribute: " + cacheops);
  }
  this.attributecache.put(cachekey, cacheops);
  }
  else {
  this.attributecache.put(cachekey, null_caching_attribute);
  }
  return cacheops;
 }
 }

 /**
 * determine a cache key for the given method and target class.
 * <p>must not produce same key for overloaded methods.
 * must produce same key for different instances of the same method.
 * @param method the method (never {@code null})
 * @param targetclass the target class (may be {@code null})
 * @return the cache key (never {@code null})
 */
 protected object getcachekey(method method, @nullable class<?> targetclass) {
 return new methodclasskey(method, targetclass);
 }

 @nullable
 private collection<cacheoperation> computecacheoperations(method method, @nullable class<?> targetclass) {
 // don't allow no-public methods as required.
 if (allowpublicmethodsonly() && !modifier.ispublic(method.getmodifiers())) {
  return null;
 }

 // the method may be on an interface, but we need attributes from the target class.
 // if the target class is null, the method will be unchanged.
 method specificmethod = aoputils.getmostspecificmethod(method, targetclass);

 // first try is the method in the target class.
 collection<cacheoperation> opdef = findcacheoperations(specificmethod);
 if (opdef != null) {
  return opdef;
 }

 // second try is the caching operation on the target class.
 opdef = findcacheoperations(specificmethod.getdeclaringclass());
 if (opdef != null && classutils.isuserlevelmethod(method)) {
  return opdef;
 }

 if (specificmethod != method) {
  // fallback is to look at the original method.
  opdef = findcacheoperations(method);
  if (opdef != null) {
  return opdef;
  }
  // last fallback is the class of the original method.
  opdef = findcacheoperations(method.getdeclaringclass());
  if (opdef != null && classutils.isuserlevelmethod(method)) {
  return opdef;
  }
 }

 return null;
 }


 /**
 * subclasses need to implement this to return the caching attribute for the
 * given class, if any.
 * @param clazz the class to retrieve the attribute for
 * @return all caching attribute associated with this class, or {@code null} if none
 */
 @nullable
 protected abstract collection<cacheoperation> findcacheoperations(class<?> clazz);

 /**
 * subclasses need to implement this to return the caching attribute for the
 * given method, if any.
 * @param method the method to retrieve the attribute for
 * @return all caching attribute associated with this method, or {@code null} if none
 */
 @nullable
 protected abstract collection<cacheoperation> findcacheoperations(method method);

 /**
 * should only public methods be allowed to have caching semantics?
 * <p>the default implementation returns {@code false}.
 */
 protected boolean allowpublicmethodsonly() {
 return false;
 }

}

!!!!  cacheoperationsourcepointcut.java 的 matchs() 方法

用来判断类是不是符合spring-cache 拦截条件 也就是 @cachable @cacheput 等等的注解怎么识别的地方

经过跟踪代码发现是 annotationcacheoperationsource.findcacheoperations() 调用的

省略部分代码....

public class annotationcacheoperationsource extends abstractfallbackcacheoperationsource implements serializable {


 private final set<cacheannotationparser> annotationparsers;


 @override
 @nullable
 protected collection<cacheoperation> findcacheoperations(class<?> clazz) {
 return determinecacheoperations(parser -> parser.parsecacheannotations(clazz));
 }

 @override
 @nullable
 protected collection<cacheoperation> findcacheoperations(method method) {
 return determinecacheoperations(parser -> parser.parsecacheannotations(method));
 }

 /**
 * determine the cache operation(s) for the given {@link cacheoperationprovider}.
 * <p>this implementation delegates to configured
 * {@link cacheannotationparser cacheannotationparsers}
 * for parsing known annotations into spring's metadata attribute class.
 * <p>can be overridden to support custom annotations that carry caching metadata.
 * @param provider the cache operation provider to use
 * @return the configured caching operations, or {@code null} if none found
 */
 @nullable
 protected collection<cacheoperation> determinecacheoperations(cacheoperationprovider provider) {
 collection<cacheoperation> ops = null;
 for (cacheannotationparser annotationparser : this.annotationparsers) {
  collection<cacheoperation> annops = provider.getcacheoperations(annotationparser);
  if (annops != null) {
  if (ops == null) {
   ops = annops;
  }
  else {
   collection<cacheoperation> combined = new arraylist<>(ops.size() + annops.size());
   combined.addall(ops);
   combined.addall(annops);
   ops = combined;
  }
  }
 }
 return ops;
 }
}

然后就是注解的解析方法 springcacheannotationparser.java

代码很简单-就不多说了

@nullable
 private collection<cacheoperation> parsecacheannotations(
  defaultcacheconfig cachingconfig, annotatedelement ae, boolean localonly) {

 collection<? extends annotation> anns = (localonly ?
  annotatedelementutils.getallmergedannotations(ae, cache_operation_annotations) :
  annotatedelementutils.findallmergedannotations(ae, cache_operation_annotations));
 if (anns.isempty()) {
  return null;
 }

 final collection<cacheoperation> ops = new arraylist<>(1);
 anns.stream().filter(ann -> ann instanceof cacheable).foreach(
  ann -> ops.add(parsecacheableannotation(ae, cachingconfig, (cacheable) ann)));
 anns.stream().filter(ann -> ann instanceof cacheevict).foreach(
  ann -> ops.add(parseevictannotation(ae, cachingconfig, (cacheevict) ann)));
 anns.stream().filter(ann -> ann instanceof cacheput).foreach(
  ann -> ops.add(parseputannotation(ae, cachingconfig, (cacheput) ann)));
 anns.stream().filter(ann -> ann instanceof caching).foreach(
  ann -> parsecachingannotation(ae, cachingconfig, (caching) ann, ops));
 return ops;
 }
 

总结

1.spring-cache 实现了 abstractbeanfactorypointcutadvisor 提供 cacheoperationsourcepointcut (pointcut) 作切点判断,提供 cacheinterceptor (methodinterceptor) 作方法拦截

2.spring-cache 提供 cacheoperationsource 作为 method 对应 cacheoperation(缓存操作) 的查询和加载

3.spring-cache 通过 springcacheannotationparser 来解析自己定义的 @cacheable @cacheevict @caching 等注解类
所以 spring-cache 不使用 aspectj 的方式,通过 cacheoperationsource.getcacheoperations() 方式可以使jdk代理的类也能匹配到

jdk代理的类的匹配

代码类在 cacheoperationsource.getcacheoperations()

重点在于 targetclass 和 method ,如果是对应的 dao.xxx() 就能matchs() 并且拦截

cacheinterceptor -> cacheaspectsupport.execute() 方法

// 代码自己看吧。也很简单 -> 结果就是spring-cache 也可以拦截到mybatis的dao层接口,进行缓存

 @nullable
 protected object execute(cacheoperationinvoker invoker, object target, method method, object[] args) {
 // check whether aspect is enabled (to cope with cases where the aj is pulled in automatically)
 if (this.initialized) {
  class<?> targetclass = gettargetclass(target);
  cacheoperationsource cacheoperationsource = getcacheoperationsource();
  if (cacheoperationsource != null) {
  collection<cacheoperation> operations = cacheoperationsource.getcacheoperations(method, targetclass);
  if (!collectionutils.isempty(operations)) {
   return execute(invoker, method,
    new cacheoperationcontexts(operations, method, args, target, targetclass));
  }
  }
 }

 return invoker.invoke();
 }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。