对于指定bean的增强方法的获取一定是包含两个步骤的:

  1. 获取所有的增强
  2. 寻找所有增强中使用于bean的增强并应用

那么findCandidateAdvisors与findAdvisorsThatCanApply便是做了这两件事情。当然,如果无法找到对应的增强器便返回DO_NOT_PROXY,其中DO_NOT_PROXY=null.

  1. protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource targetSource)
  2. {
  3. List advisors = findEligibleAdvisors(beanClass, beanName);
  4. if(advisors.isEmpty())
  5. return DO_NOT_PROXY;
  6. else
  7. return advisors.toArray();
  8. }
  9. protected List findEligibleAdvisors(Class beanClass, String beanName)
  10. {
      //获取所有的增强器
  11. List candidateAdvisors = findCandidateAdvisors();
      //获取适合beanname的增强器
  12. List eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
  13. extendAdvisors(eligibleAdvisors);
  14. if(!eligibleAdvisors.isEmpty())
  15. eligibleAdvisors = sortAdvisors(eligibleAdvisors);
  16. return eligibleAdvisors;
  17. }

所有增强器的获取

由于分析的是使用注解进行的AOP,所以对于findCandidateAdvisors的实现其实是由AnnotationAwareAspectJAutoProxyCreator类完成的,我们继续跟踪AnnotationAwareAspectJAutoProxyCreator的findCandidateAdvisors方法。

  1. protected List findCandidateAdvisors()
  2. {
  3. //当使用注解方式配置AOP的时候并不是丢弃了对XML配置的支持。
  4. //在这里调用父类方法加载配置文件中的AOP声明
  5. List advisors = super.findCandidateAdvisors();
  6. //Build Advisors for all AspectJ aspects in the bean factory
  7. advisors.addAll(aspectJAdvisorsBuilder.buildAspectJAdvisors());
  8. return advisors;
  9. }

AnnotationAwareAspectJAutoProxyCreator间接继承了AbstractAdvisorAutoProxyCreator,在实现获取增强的方法中除了保留父类的获取配置文件中定义的增强外,同时添加了获取Bean的注解增强的功能,那么其实现正是由this.aspectJAdvisorsBuilder.buildAspectJAdvisors()来实现的。

(1)获取所有beanName,这一步骤中所有在beanFactory中注册的Bean都会被提取出来。

(2)遍历所有beanName,并找出声明AspectJ注解的类,进行进一步的处理。

(3)对标记为AspectJ注解的类进行增强器的提取。

(4)将提取结果加入缓存。

提取Advisor

现在来看看函数实现,对Spirng中所有的类进行分析,提取Advisor。

  1. public List buildAspectJAdvisors()
  2. {
  3. List aspectNames = null;
  4. synchronized(this){
  5. aspectNames = aspectBeanNames;
  6. if(aspectNames == null)
  7. {
  8. List advisors = new LinkedList();
  9. aspectNames = new LinkedList();
  10. //获取所有的beanName
  11. String beanNames[] = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, java/lang/Object, true, false);
  12. //循环所有的beanName找出对应的增强方法
  13. for(String beanName : beanNames)
  14. {
  15. //不合法的bean则略过,由子类定义规则,默认返回true
  16. if(!isEligibleBean(beanName))
  17. continue;
  18. //获取对应的bean的类型
  19. Class beanType = beanFactory.getType(beanName);
  20. //如果存在Aspect注解
  21. if(beanType == null || !advisorFactory.isAspect(beanType))
  22. continue;
  23. aspectNames.add(beanName);
  24. AspectMetadata amd = new AspectMetadata(beanType, beanName);
  25. MetadataAwareAspectInstanceFactory factory;
  26. if(amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON)
  27. {
  28. factory = new BeanFactoryAspectInstanceFactory(beanFactory, beanName);
  29. //解析标记AspectJ注解中的增强方法
  30. List classAdvisors = advisorFactory.getAdvisors(factory);
  31. if(beanFactory.isSingleton(beanName))
  32. advisorsCache.put(beanName, classAdvisors);
  33. else
  34. aspectFactoryCache.put(beanName, factory);
  35. advisors.addAll(classAdvisors);
  36. continue;
  37. }
  38. if(beanFactory.isSingleton(beanName))
  39. throw new IllegalArgumentException((
                        new StringBuilder()).append("Bean with name '")
                                    .append(beanName)
                                    .append("' is a singleton, but aspect instantiation model is not singleton")
                                    .toString());
  40. factory = new PrototypeAspectInstanceFactory(beanFactory, beanName);
  41. aspectFactoryCache.put(beanName, factory);
  42. advisors.addAll(advisorFactory.getAdvisors(factory));
  43. }
  44.  
  45. aspectBeanNames = aspectNames;
  46. return advisors;
  47.  
  48. }

至此,已经完成了Advisor的提取。

切点信息的获取

在上面的步骤中最为重要也最为繁杂的就是增强器的获取,而这一切功能委托给了getAdvisors方法去实现(this.advisorFactory.getAdvisors(factory))。

  1. public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory maaif) {
  2.      //获取标记为AspectJ的类
  3. final Class<?> aspectClass = maaif.getAspectMetadata().getAspectClass();
  4.      //获取标记为AspectJ的name
  5. final String aspectName = maaif.getAspectMetadata().getAspectName();
  6.      //验证
  7. validate(aspectClass);
  8. final MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
  9. new LazySingletonAspectInstanceFactoryDecorator(maaif);
  10. final List<Advisor> advisors = new LinkedList<Advisor>();
  11. for (Method method : getAdvisorMethods(aspectClass)) {
  12. Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
  13. if (advisor != null) {
  14. advisors.add(advisor);
  15. }
  16. }
  17.      //如果寻找的增强器不为空而且又配置了增强延迟初始化那么需要在首位加入同步实例化增强器
  18. if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
  19. Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
  20. advisors.add(0, instantiationAdvisor);
  21. }
  22.      //对DeclareParents注解的获取
  23. for (Field field : aspectClass.getDeclaredFields()) {
  24. Advisor advisor = getDeclareParentsAdvisor(field);
  25. if (advisor != null) {
  26. advisors.add(advisor);
  27. }
  28. }
  29. return advisors;
  30. }

函数中首先完成了增强器的获取,包括获取注解以及根据注解生成增强的步骤,然后考虑到在配置中可能会将增强配置成延迟初始化,那么需要在首位加入同步实例化增强器以保证增强使用之前的实例化,最后是对DeclareParents注解的获取。

普通增强器的获取逻辑通过getAdvisor方法实现,实现步骤包括对切点的注解的获取及根据注解信息生成增强。

(1)切点信息的获取。所谓获取切点信息就是指定注解的表达式信息的获取,如@Before("test()")

  1. public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aif, int declarationOrderInAspect, String aspectName)
  2. {
  3. validate(aif.getAspectMetadata().getAspectClass());
  4. //切点信息的获取
  5. AspectJExpressionPointcut ajexp = getPointcut(candidateAdviceMethod, aif.getAspectMetadata().getAspectClass());
  6. if(ajexp == null)
  7. return null;
  8. else
  9. //根据切点信息生成增强器
  10. return new InstantiationModelAwarePointcutAdvisorImpl(this, ajexp, aif, candidateAdviceMethod, declarationOrderInAspect, aspectName);
  11. }
  12.  
  13. private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
  14. //获取方法上的注解
  15. AspectJAnnotation<?> aspectJAnnotation =
  16. AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
  17. if (aspectJAnnotation == null) {
  18. return null;
  19. }
  20. //使用AspectJExpressionPointcut实例封装获取的信息
  21. AspectJExpressionPointcut ajexp =
  22. new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]);
  23. //提取得到的注解中的表达式如:@Pointcut("execution(* *.*test*(..))")中的execution(* *.*test*(..))
  24. ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
  25. return ajexp;
  26. }
  27.  
  28. protected static AspectJAnnotation findAspectJAnnotationOnMethod(Method method) {
  29. //设置敏感的注解类
  30. Class<? extends Annotation>[] classesToLookFor = new Class[] {
  31. Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};
  32. for (Class<? extends Annotation> c : classesToLookFor) {
  33. AspectJAnnotation foundAnnotation = findAnnotation(method, c);
  34. if (foundAnnotation != null) {
  35. return foundAnnotation;
  36. }
  37. }
  38. return null;
  39. }
  40. //获取指定方法上的注解并使用AspectJAnnotation封装
  41. private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) {
  42. A result = AnnotationUtils.findAnnotation(method, toLookFor);
  43. if (result != null) {
  44. return new AspectJAnnotation<A>(result);
  45. }
  46. else {
  47. return null;
  48. }
  49. }

(2)根据切点信息生成增强。所有的增强都有Advisor实现类InstantiationModelAwarePontcutAdvisorImpl统一封装。

  1. public InstantiationModelAwarePointcutAdvisorImpl(AspectJAdvisorFactory af, AspectJExpressionPointcut ajexp,
  2. MetadataAwareAspectInstanceFactory aif, Method method, int declarationOrderInAspect, String aspectName) {
  3.  
  4. this.declaredPointcut = ajexp;
  5. this.method = method;
  6. this.atAspectJAdvisorFactory = af;
  7. this.aspectInstanceFactory = aif;
  8. this.declarationOrder = declarationOrderInAspect;
  9. this.aspectName = aspectName;
  10.  
  11. if (aif.getAspectMetadata().isLazilyInstantiated()) {
  12. Pointcut preInstantiationPointcut =
  13. Pointcuts.union(aif.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);
  14. this.pointcut = new PerTargetInstantiationModelPointcut(this.declaredPointcut, preInstantiationPointcut, aif);
  15. this.lazy = true;
  16. }
  17. else {
  18. this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
  19. this.pointcut = declaredPointcut;
  20. this.lazy = false;
  21. }
  22. }

封装过程只是简单地将信息封装在类的实例中,所有的信息单纯地复制。在实例初始化的过程中还完成了对于增强器的初始化。因为不同的增强所体现的逻辑是不同的,比如@Before(“test()”)与After(“test()”)标签的不同就是增强器增强的位置不同,所以就需要不同的增强器来完成不同的逻辑,而根据注解中的信息初始化对应的增强器就是在instantiateAdvice函数中实现的。

  1. private Advice instantiateAdvice(AspectJExpressionPointcut pcut)
  2. {
  3. return atAspectJAdvisorFactory.getAdvice(method, pcut, aspectInstanceFactory, declarationOrder, aspectName);
  4. }
  5. public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut ajexp,
              MetadataAwareAspectInstanceFactory aif, int declarationOrderInAspect, String aspectName)
  6. {
  7. Class candidateAspectClass = aif.getAspectMetadata().getAspectClass();
  8. validate(candidateAspectClass);
  9. AbstractAspectJAdvisorFactory.AspectJAnnotation aspectJAnnotation =
                  AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
  10. if(aspectJAnnotation == null)
  11. return null;
  12. if(!isAspect(candidateAspectClass))
  13. throw new AopConfigException(
                (new StringBuilder())
                      .append("Advice must be declared inside an aspect type: Offending method '")
                      .append(candidateAdviceMethod)
                      .append("' in class [")
                      .append(candidateAspectClass.getName())
                      .append("]").toString());
  14. if(logger.isDebugEnabled())
  15. logger.debug((new StringBuilder()).append("Found AspectJ method: ").append(candidateAdviceMethod).toString());
  16. AbstractAspectJAdvice springAdvice;
  17. switch(SwitchMap.org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactory.AspectJAnnotationType
                [aspectJAnnotation.getAnnotationType().ordinal()])
  18. {
  19. case 1: // '\001'
  20. springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, ajexp, aif);
  21. break;
  22.  
  23. case 2: // '\002'
  24. springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, ajexp, aif);
  25. break;
  26.  
  27. case 3: // '\003'
  28. springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, ajexp, aif);
  29. AfterReturning afterReturningAnnotation = (AfterReturning)aspectJAnnotation.getAnnotation();
  30. if(StringUtils.hasText(afterReturningAnnotation.returning()))
  31. springAdvice.setReturningName(afterReturningAnnotation.returning());
  32. break;
  33.  
  34. case 4: // '\004'
  35. springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, ajexp, aif);
  36. AfterThrowing afterThrowingAnnotation = (AfterThrowing)aspectJAnnotation.getAnnotation();
  37. if(StringUtils.hasText(afterThrowingAnnotation.throwing()))
  38. springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
  39. break;
  40.  
  41. case 5: // '\005'
  42. springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, ajexp, aif);
  43. break;
  44.  
  45. case 6: // '\006'
  46. if(logger.isDebugEnabled())
  47. logger.debug(
                  (new StringBuilder()).append("Processing pointcut '")
                               .append(candidateAdviceMethod.getName())
                               .append("'").toString());
  48. return null;
  49.  
  50. default:
  51. throw new UnsupportedOperationException(
                (new StringBuilder()).append("Unsupported advice type on method ")
                             .append(candidateAdviceMethod).toString());
  52. }
  53. springAdvice.setAspectName(aspectName);
  54. springAdvice.setDeclarationOrder(declarationOrderInAspect);
  55. String argNames[] = parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
  56. if(argNames != null)
  57. springAdvice.setArgumentNamesFromStringArray(argNames);
  58. springAdvice.calculateArgumentBindings();
  59. return springAdvice;
  60. }
  61. }

从函数中可以看到,Spring会根据不同的注解生成不同的增强器,例如AtBefore会对应AspectJMethodBeforeAdvice。

增加同步实例化增强器

如果寻找的增强器不为空而且又配置了增强延迟初始化,那么就需要在首位加入同步实例化增强器。

  1. protected static class SyntheticInstantiationAdvisor extends DefaultPointcutAdvisor {
  2. public SyntheticInstantiationAdvisor(final MetadataAwareAspectInstanceFactory aif) {
  3. super(aif.getAspectMetadata().getPerClausePointcut(), new MethodBeforeAdvice() {
  4. @Override
  5. public void before(Method method, Object[] args, Object target) {
  6. // Simply instantiate the aspect
  7. aif.getAspectInstance();
  8. }
  9. });
  10. }
  11. }

获取DeclareParents注解

DeclareParents主要用于引介增强的注解形式的实现,而其实现方式驭普通增强很类似,只不过使用DeclareParentsAdvisor对功能进行封装。

引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过引介功能,可以动态的未该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。

  1. private Advisor getDeclareParentsAdvisor(Field introductionField)
  2. {
  3. DeclareParents declareParents = (DeclareParents)introductionField.getAnnotation(org/aspectj/lang/annotation/DeclareParents);
  4. if(declareParents == null)
  5. return null;
  6. if("org/aspectj/lang/annotation/DeclareParents".equals(declareParents.defaultImpl()))
  7. throw new IllegalStateException("defaultImpl must be set on DeclareParents");
  8. else
  9. return new DeclareParentsAdvisor(introductionField.getType(), declareParents.value(), declareParents.defaultImpl());
  10. }

至此已经完成了所有增强器的解析,但是对于所有增强器来讲,并不一定都适用于当前的Bean,还要挑取除适合的增强器,也就是满足我们配置的通配符的增强器。具体的实现在findAdvisorsThatCanApply中。

获取合适的增强器

findAdvisorsThatCanApply函数的主要功能是寻找所有增强器中适用于当前class的增强器。对于真是的匹配在canApply中实现。

  1. protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class beanClass, String beanName) {
  2. ProxyCreationContext.setCurrentProxiedBeanName(beanName);
  3. try {
  4. return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
  5. }
  6. finally {
  7. ProxyCreationContext.setCurrentProxiedBeanName(null);
  8. }
  9. }
  10. public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
  11. if (candidateAdvisors.isEmpty()) {
  12. return candidateAdvisors;
  13. }
  14. List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
  15. //首先处理引介增强
  16. for (Advisor candidate : candidateAdvisors) {
  17. if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
  18. eligibleAdvisors.add(candidate);
  19. }
  20. }
  21. boolean hasIntroductions = !eligibleAdvisors.isEmpty();
  22. for (Advisor candidate : candidateAdvisors) {
  23. if (candidate instanceof IntroductionAdvisor) {
  24. continue;
  25. }
  26. //对于普通bean的处理
  27. if (canApply(candidate, clazz, hasIntroductions)) {
  28. eligibleAdvisors.add(candidate);
  29. }
  30. }
  31. return eligibleAdvisors;
  32. }
  33. public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
  34. Assert.notNull(pc, "Pointcut must not be null");
  35. if (!pc.getClassFilter().matches(targetClass)) {
  36. return false;
  37. }
  38. MethodMatcher methodMatcher = pc.getMethodMatcher();
  39. IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
  40. if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
  41. introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
  42. }
  43. Set<Class> classes = new LinkedHashSet<Class>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
  44. classes.add(targetClass);
  45. for (Class<?> clazz : classes) {
  46. Method[] methods = clazz.getMethods();
  47. for (Method method : methods) {
  48. if ((introductionAwareMethodMatcher != null &&
  49. introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
  50. methodMatcher.matches(method, targetClass)) {
  51. return true;
  52. }
  53. }
  54. }
  55. return false;
  56. }
 

AOP动态代理解析3-增强方法的获取的更多相关文章

  1. AOP动态代理解析4-代理的创建

    做完了增强器的获取后就可以进行代理的创建了 AnnotationAwareAspectJAutoProxyCreator->postProcessAfterInitialization-> ...

  2. AOP动态代理解析1-标签的解析

    spring.handlers http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespa ...

  3. AOP动态代理解析4-jdk代理的实现

    JDKProxy的使用关键是创建自定义的InvocationHandler,而InvocationHandler中包含了需要覆盖的函数getProxy,而当前的方法正是完成了这个操作.在此确认一下JD ...

  4. AOP动态代理解析2-代码织入入口

    通过自定义配置完成了对AnnotationAwareAspectJAutoProxyCreator类型的自动注册,那么这个类到底做了什么工作来完成AOP的操作呢?首先我们看看AnnotationAwa ...

  5. AOP动态代理解析5-cglib代理的实现

    CGLIB是一个强大的高性能的代码生成包.它广泛地被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的Interception(拦截).EasyMock和jMock是通过使 ...

  6. Java 动态代理 两种实现方法

    AOP的拦截功能是由java中的动态代理来实现的.说白了,就是在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标类函数抛出异常时候执 ...

  7. 技术的正宗与野路子 c#, AOP动态代理实现动态权限控制(一) 探索基于.NET下实现一句话木马之asmx篇 asp.net core 系列 9 环境(Development、Staging 、Production)

    黄衫女子的武功似乎与周芷若乃是一路,飘忽灵动,变幻无方,但举手抬足之间却是正而不邪,如说周芷若形似鬼魅,那黄衫女子便是态拟神仙. 这段描写出自<倚天屠龙记>第三十八回. “九阴神抓”本是& ...

  8. Spring ( 四 )Spring的AOP动态代理、切面编程

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.AOP切面编程 1.什么是AOP AOP是面向切面编程.全称:Aspect Oriented Pro ...

  9. Spring学习笔记之aop动态代理(3)

    Spring学习笔记之aop动态代理(3) 1.0 静态代理模式的缺点: 1.在该系统中有多少的dao就的写多少的proxy,麻烦 2.如果目标接口有方法的改动,则proxy也需要改动. Person ...

随机推荐

  1. WCF重写ServiceHost,实现独立配置文件

    有时我们需要将WCF的配置文件放在单独的配置文件中,而默认情况下WCF又是在web.config或app.config中去寻找服务配置.如果我们把配置文件放在另一个config文件中,如何让WCF知道 ...

  2. (2016弱校联盟十一专场10.2) A.Nearest Neighbor Search

    题目链接 水题,算一下就行. #include <bits/stdc++.h> using namespace std; typedef long long ll; ll x[],y[], ...

  3. EF性能调优

    首先说明下: 第一次运行真是太慢了,处理9600多个员工数据,用了81分钟!! 代码也挺简单,主要是得到数据-->对比分析-->插入分析结果到数据库.用的是EF的操作模式. public ...

  4. August 30th 2016 Week 36th Tuesday

    If you keep on believing, the dreams that you wish will come true. 如果你坚定信念,就能梦想成真. I always believe ...

  5. 查询Oracle中字段名带"."的数据

    SDE中的TT_L线层会有SHAPE.LEN这样的字段,使用: SQL>select shape.len from tt_l; 或 SQL>select t.shape.len from ...

  6. NYOJ题目34韩信点兵

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAskAAAHiCAIAAACV1MbSAAAgAElEQVR4nO3dPXLjONeG4W8TyrUQx1 ...

  7. IOS之笑脸app

    ios笑脸app实现 import UIKit @IBDesignable class FaceView: UIView { @IBInspectable var lineWidth:CGFloat= ...

  8. JAVA数据库基本操作 (转)

    JAVA数据库基本操作指南   Java数据库操作基本流程:取得数据库连接 - 执行sql语句 - 处理执行结果 - 释放数据库连接. 一.取得数据库连接 1.用DriverManager取数据库连接 ...

  9. jquery学习笔记---this关键字

    1.    在JavaScript的变量作用域里有一条规则“全局变量都是window对象的属性”.当执行 checkThis() 时相当于 window.checkThis(),因此,此时checkT ...

  10. (编辑器)Jquery-EasyUI集合Kindeditor编辑器

    1.在html里面添加 list.html list.html (function ($, K) { if (!K) throw "KindEditor未定义!"; functio ...