Spring AOP Introductions

前言

最近想起来在博客【详解什么是Spring AOP】 里面挖了一个坑没有填。那就是Introduction部分的内容,当时说要写一个小例子讲一下,这个东西是怎么用的。直接就给忘了,这次就是把这个坑填上的。更多Spring内容进入【Spring解读系列目录】

Introductions 初识

首先我们还是看官网怎么说的【Introductions】,以下英文部分来自官方文档:

Introductions (known as inter-type declarations in AspectJ) enable an aspect to declare that advised objects implement a given interface, and to provide an implementation of that interface on behalf of those objects.
You can make an introduction by using the @DeclareParents annotation. This annotation is used to declare that matching types have a new parent (hence the name). For example, given an interface named UsageTracked and an implementation of that interface named DefaultUsageTracked, the following aspect declares that all implementors of service interfaces also implement the UsageTracked interface (to expose statistics via JMX for example):

前一段是概念,Introductions能够是一个切面去声明一个已经通知过的对象去实现给定的接口,并且提代表这些对象供一个接口的实现。后面一段主要是说怎么用的,主要就是用@DeclareParents 这个注解。概念说的很绕,其实就是在使用中就是给某一个类A指定一个父类B。而且类A本来没有继承B,经过这个概念以后,就完成了继承。@DeclareParents看这个注解的名字也是非常的耿直,声明父类。

参考下官方文档的例子。就是在切面类中给一个接口加上@DeclareParents注解,然后指定接口的路径,再给一个默认的实现类,下面代码来自官网。

@Aspect
public class UsageTracking {
	//给com.xzy.myapp.service包下的所有接口指定一个默认的实现类
    @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class) 
    public static UsageTracked mixin;
    @Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")
    public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();
    }
}

怎么访问这个bean呢?官网也说了,和一般的bean没有区别,直接用就好了。那么随后我们就自己写一个小例子来验证官网这个说法。

The interface to be implemented is determined by the type of the annotated field. The value attribute of the @DeclareParents annotation is an AspectJ type pattern. Any bean of a matching type implements the UsageTracked interface. Note that, in the before advice of the preceding example, service beans can be directly used as implementations of the UsageTracked interface. If accessing a bean programmatically, you would write the following:
UsageTracked usageTracked = (UsageTracked) context.getBean(“myService”);

Introductions Sample

既然要用例子讲,那就还是得写几个例子。假设我们有一个接口TargetDao,一个实现类DemoDao,一个切面类DemoAspect,还有一个毫不相关的类IntroduDemoDao,以及一个测试类DemoTest。这个类将会给我们展示什么是Introductions。

public interface TargetDao {
    public void print();
    public void print(String a);
    public void print(Integer a);
}
@Repository("demodao")
public class DemoDao implements TargetDao{
    public void print(){
        System.out.println("print empty");  //打印测试
    }
}
@Component
@Aspect 
public class DemoAspect {
    @DeclareParents(value = "com.demo.dao.*", defaultImpl = DemoDao.class)
    public static TargetDao targetDao;
}
@Repository("introDao")
public class IntroduDemoDao { //这个类是空的
}
public class DemoTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext anno=new AnnotationConfigApplicationContext(Appconfig.class);
        TargetDao targetDao= (TargetDao) anno.getBean("introDao");
        targetDao.print();
    }
}

写完了,直接运行。如果按照常理,一定会报错的。因为我们要拿出来的IntroduDemoDao根本就没有任何的实现(其实连内容都没有),也没有对任何的对象进行初始化,不报错不可能的。但是这里运行以后却把print empty这行打印出来了,是不是见鬼了。我们现在这个例子,就是把introDao拿出来,但是introDao这里没有任何方法,没有实现任何接口,但是却能调用方法打印东西, 而且是最终执行的是DemoDao.print()方法。这是为什么呢?

先不解释这个问题,如果我们把切面类里的下面这两句删掉,一定会报错。

@DeclareParents(value = "com.demo.dao.*", defaultImpl = DemoDao.class)
public static TargetDao targetDao;
    
删掉上面两行,运行结果:报错------------------------------
Exception in thread "main" java.lang.ClassCastException: com.demo.dao.IntroduDemoDao cannot be cast to 
	com.demo.dao.TargetDao
	at com.demo.main.DemoTest.main(DemoTest.java:13)

Introductions解析

要解释这个原因,首先得分析报错。我们看这里报的是强转错误,因为IntroduDemoDaoTargetDao完全是两个东西,不可能强转成功,更不可能去调用方法。但是(注意这个但是)加上就没有问题了,所以回到@DeclareParents这个注解,直接翻译就是声明父类。解释到这里有没有小伙伴恍然大悟呢?

按照Spring官网他叫Introduction,但是实际表现可以认为是指定父类。那么这个怎么理解呢,就是首先找到com.demo.dao下的所有类,然后让他去引入TargetDao接口的DemoDao实现。就是说我现在com.demo.dao.*这个写法,就是让dao包下所有的类都去实现TargetDao。既然是要实现TargetDao就得把这里面的方法实现了对不对。这个实现肯定不是Spring做的,Spring在这里做的就是去找TargetDao的实现类,于是就找到了DemoDao。然后就把DemoDao里面实现的方法给贴过来作为IntroduDemoDao的实现方法,所以当我们调用print()的时候,执行的是DemoDao.print()

如果我们把IntroduDemoDao里面也实现了TargetDao,那么执行的就是IntroduDemoDao里面的方法了。因为我们这里只是给没有实现的那些类指定一个默认的实现类,如果有真正的实现还是会用真正的实现类去做的,比如我们给IntroduDemoDao实现TargetDao接口,再运行看看结果:

@Repository("introDao")
public class IntroduDemoDao implements TargetDao{ //实现TargetDao
    public void print() {
        System.out.println("IntroduDemoDao empty");  //测试打印
    }
}

运行结果:
IntroduDemoDao empty  ------> 这次打印就是IntroduDemoDao里面的内容了。

总结

所以Introduction做了什么事情呢?就是给我们要实现的类指定一个父类,并且指定一个默认实现的类。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值