Spring(11) - Introductions进行类扩展方法

  Introductions(引用),在 Aspect 中称为类型间的声明,使切面能够声明被通知的对象(拦截的对象)实现给定的接口,并提供该接口的实现。

  简单点说可以将一个类的实现方法复制到未实现的类中,动态的扩展类没有的方法。

  通过 @DeclareParents 注解进行声明,声明在一个父类型的属性上(比如接口),其中注解的属性 value 指定对哪些路径(包)下的类进行类方法扩展,defaultImpl 指定以哪个类为模板。

  如下案例:

  1)切面类:

@Aspect
@Component
public class LogAspects {
    @DeclareParents(value = "com.hrh.aop.*", defaultImpl = IndexDao.class)
    public static Dao dao;

    @Pointcut("execution(public * com.hrh.aop.*.*(..))")
    public void pointcut() {

    }

    @Before("pointcut()")
    public void LogStart() {
        System.out.println("LogStart...");
    }
}

  2)接口和实现类:

public interface Dao{
    void query();
}

@Repository
public class IndexDao implements Dao{
    public void query(){
        System.out.println("query:"+this.getClass().getSimpleName());
    }
}

  3)配置类:

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.hrh.aop")
public class Config {
}

  4)空类:

@Repository("orderDao")
public class OrderDao{}

  5)测试和结果:

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        Dao bean = (Dao) context.getBean("indexDao");
        bean.query();
        System.out.println("----------");
        Dao dao = (Dao) context.getBean("orderDao");
        dao.query();
    }
    =========结果==========
    LogStart...
    query:IndexDao
    ----------
    query:IndexDao

   根据前文Spring笔记(3) - SpringAOP基础详解和源码探究可以得知如下:

  • @EnableAspectJAutoProxy(proxyTargetClass=false),表示使用JDK动态代理(默认);
  • @EnableAspectJAutoProxy(proxyTargetClass=true),表示使用CGLIB代理;

   debug运行后查看beanFactory的OrderDao和IndexDao代理后的对象,如下图:

 

   从上面可以看到IndexDao的对象是使用JDK代理的,而OrderDao的对象是CGLIB代理的。

  那么当设置@EnableAspectJAutoProxy(proxyTargetClass=true)时,是如下图:

 

   由此可以判定@DeclareParents注解给拦截到的类进行代理使用的是CGLIB代理(其实OrderDao没有实现任何接口,所以最终实现的是CGLIB),那么可以查看下代理类生成的内容是什么,可以在context前面加一行代码将代理类class文件存入本地磁盘:

        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "xxx");//xxx是存放代理类class的本地路径
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);

 

   下面是代理类class反编译后的完整代码(感兴趣可以完全打开,完全的很多内容,包含了各种父类的方法,比如toString、clone、equals、hashCode):

View Code【完整代码查看https://www.cnblogs.com/huangrenhui/p/14655795.html

  从上面可以看到代理类里面有query方法,代理类继承了OrderDao和实现了Dao接口:

public class OrderDao$$EnhancerBySpringCGLIB$$ee33136 extends OrderDao implements Dao, SpringProxy, Advised, Factory {    
    ............................
    
    final void CGLIB$query$4() {
        super.query();
    }

    public final void query() {
        try {
            MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
            if (this.CGLIB$CALLBACK_0 == null) {
                CGLIB$BIND_CALLBACKS(this);
                var10000 = this.CGLIB$CALLBACK_0;
            }

            if (var10000 != null) {
                var10000.intercept(this, CGLIB$query$4$Method, CGLIB$emptyArgs, CGLIB$query$4$Proxy);
            } else {
                super.query();
            }
        } catch (Error | RuntimeException var1) {
            throw var1;
        } catch (Throwable var2) {
            throw new UndeclaredThrowableException(var2);
        }
    }
    
    ............................
}    

   从前文Spring笔记(3) - SpringAOP基础详解和源码探究可以得知在 AbstractAutoProxyCreator#wrapIfNecessary()中会对bean进行包装增强(代理),下面打下断点debug跟踪下流程:

 

 

   当执行到wrapIfNecessary方法下面的代码时,bean还是原对象(未被代理):

Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);

 

   当上面的代码执行完后,可以看到specificInterceptors数组包含了advice(通知)、introducedInterface(引用接口)、typePatternClassFilter(class过滤规则,重点),其中advice包含了defaultImplType(默认实现类型,上面代码中定义的),可以看到是IndexDao,interfaceType(接口类型)可以看到是Dao,以上信息是通过解析@DeclareParents注解而来的:

   下面typePatternClassFilter里面的详情,可以看到,上面的信息又包含在typePatternClassFilter里面,同时里面包含了过滤规则:

   最后通过DefaultAopProxyFactory#createAopProxy方法创建的对象如下图:

   当执行query方法时,CglibAopProxy.DynamicAdvisedInterceptor#intercept方法进行拦截时,会执行父类的方法ReflectiveMethodInvocation#ReflectiveMethodInvocation:

 

   执行完会跳转到ReflectiveMethodInvocation#proceed执行方法的执行:

    public Object proceed() throws Throwable {
        // We start with an index of -1 and increment early.
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();
        }
        //获取通知
        Object interceptorOrInterceptionAdvice =
                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            // Evaluate dynamic method matcher here: static part will already have
            // been evaluated and found to match.
            InterceptorAndDynamicMethodMatcher dm =
                    (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
            if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
                return dm.interceptor.invoke(this);
            }
            else {
                // Dynamic matching failed.
                // Skip this interceptor and invoke the next in the chain.
                return proceed();
            }
        }
        else {
            // It's an interceptor, so we just invoke it: The pointcut will have
            // been evaluated statically before this object was constructed.
            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);//执行方法
        }
    }    

 

  下面执行delegate对象获取到IndexDao,然后AopUtils.invokeJoinpointUsingReflection执行IndexDao的query实现方法:

  其中getIntroductionDelegateFor方法中的delegate通过反射对默认实现类型进行构造方法实例化后调用实例化的方法,由此可以看出上面的第二次打印结果没有打印前置通知的内容,因为此时IndexoDao的对象是原对象,不是代理对象

 

 

   总结:Introductions(引用)通过 @DeclareParents 注解实现,可以给一个空白类进行方法复制,它通过解析@DeclareParents 注解信息,代理时将定义的静态接口变量作为它的父类,并实现它的方法,方法执行时调用的是defaultImpl 指定的类模板的实现方法;

  参考:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-introductions

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值