在之前的文章我们提到AOP面向切面编程是一种编程思想,是对OOP面向对象编程的一种补充。对于AOP这种编程思想,很多框架都进行了实现比如Spring.
AspectJ是eclipse基金会的一个项目,是最早、功能比较强大的 AOP 实现之一。对整套 AOP 机制都有较好的实现,很多其他语言的 AOP 实现,也借鉴或采纳了 AspectJ 中很多设计。在 Java 领域,AspectJ 中的很多语法结构基本上已成为 AOP 领域的标准。而且还支持注解式开发。
所以,Spring又将AspectJ的对于AOP的实现也引入到了自己的框架中。所以本文讨论的就是如何在Spring项目中使用AspectJ来实现切面编程。本质上是使用了AspectJ的注解类库,和解析类库,并通过JDK动态代理或者CGLib代理生成代理类,最终实现切面编程。
需要说明的是,上篇文章我们通过Pointcut和Advise接口描述切点和增强,并用Advisor整合两者描述切面,@AspectJ则采用注解描述,本质是一样的。
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjrtartifactId>
<version>${aspectjweaver.version}version>
dependency>
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>2.2version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjtoolsartifactId>
<version>1.6.9version>
dependency>
/**
* 通过AspectJ注解,声明切面,描述切点。
*/
@Aspect
public class PregreetingAspect {
@Before("execution(* greetTo(..))")
public void beforeGreeting(){
System.out.println("How are you !");
}
}
通过@Aspect注解就可以声明一个切面,通过@Before关键字声明前置增强逻辑。
execution是相应切点的匹配逻辑。我们可以发现,这个切面没有实现任何接口,是一个普通的POJO。仅仅通过简单的注解就可以将增强逻辑、切点和增强类型整合到一个类中。而在上篇文章中,我们需要做很多工作才能达到相同的目的。
public class AspectJBeforeTest {
AspectJProxyFactory aspectJProxyFactory =null;
@BeforeClass
public void init() {
Waiter target = new NaiveWaiter();
aspectJProxyFactory =new AspectJProxyFactory();
aspectJProxyFactory.setTarget(target);
aspectJProxyFactory.addAspect(PregreetingAspect.class);
}
@Test
public void aspectTest(){
Waiter proxy = aspectJProxyFactory.getProxy();
proxy.greetTo("John");
proxy.serveTo("John");
}
}
打印结果:增强已经生效
How are you !
NaiveWaiter:greet to John...
NaiveWaiter:serving John...
使用配置方式实现
上面是通过编程的方式生成代理类,如果使用配置完成,需要编写一个配置文件:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
<aop:aspectj-autoproxy/>
<bean id="waiter" class="com.smart.NaiveWaiter"/>
<bean class="com.smart.aspectj.example.PregreetingAspect"/>
beans>
这里引入了aop的命名空间,只需要配置aspectj-autoproxy标签就可以完成自动代理生成。当然,Spring在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被
最后使用:
public class AspectJConfiguerBeforeTest {
String configurePath= "";
ApplicationContext ctx = null;
@BeforeClass
public void init() {
configurePath = "com/smart/aspectj/beans.xml";
ctx = new ClassPathXmlApplicationContext(configurePath);
}
@Test
public void aspectTest(){
Waiter proxy = (Waiter) ctx.getBean("waiter");
proxy.greetTo("John");
proxy.serveTo("John");
}
}
效果和上面一致。
上面我们通过一个例子演示了如何通过@Aspect注解实现一个切面。那这些注解到底代表什么意思?有哪些需要掌握的常用注解?
@Aspect 注解 作用是告诉Spring容器,标注该注解的类是一个切面。
@Before(“execution(* greetTo(…))”) 表示这个方法是一个前置增强,其中execution用于声明切点表达式,* greetTo(…) 表示匹配目标了类的greetTo()方法,greetTo()方法可以带任意的入参和任意的返回值。
由于Spring只支持方法的连接点,所以Spring仅支持部分AspectJ的语言。上面提到execution关键字用来定义切点表达式。Spring中支持哪些表达式?
Spring中支持9个@ApectJ切点表达式函数。
2019.8.19补充:Spring中新增了一种切点表达式
10)bean:通过受管Bean的名字来限定连接点所在的Bean。该关键词是Spring2.5新增的。
@Pointcut(“bean(person)”)
public void before(){}
如果使用@Bean注解,在没有指定name的情况下,那么这个bean的name是方法的名称
下面分别介绍各个表达式的用法:
execution 常用示例:
举几个例子:
execution(public * *(..)) 指定切入点为:任意公共方法
execution(* *.service.*.*(..)) 指定只有一级包下的service子包下所有类(接口)中所有方法为切入点
execution(* *..service.*.*(..)) 指定所有包下的service子包下所有类(接口)中所有方法为切入点
execution(* *.SomeService.*(..)) 指定只有一级包下的SomeService类(接口)中所有方法为切入点
execution(* *..SomeService.*(..)) 指定所有包下的SomeService类(接口)中所有方法为切入点
@annotation示例:
@Aspect
public class AnnotationAspect {
@Before("@annotation(com.smart.anno.NeedTest)")
public void beforeGreeting(){
System.out.println("How are you !");
}
}
一般可以自定义一个注解,然后通过该注解标识哪些方法需要织入增强。注意注解要加载实现类上。
自定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NeedTest {
boolean value() default false;
}
用注解标注目标类:
public class NaiveSeller implements Seller {
@Override
public int sell(String goods, String clientName) {
return 12;
}
@Override
public void greetTo(String clientName) {
System.out.println("seller greetTo"+clientName);
}
@NeedTest
@Override
public void annotationTo() {
System.out.println("seller annotation greetTo!");
}
}
详细信息可以下载我的示例代码Github
args和@args()
args: 例如 ,如下注解表明,目标类参数为String类型的方法,在调用时,会织入增强。
@Before("args(String)")
public void beforeGreeting(){
System.out.println("How are you String!");
}
@args: 当切面的方法注解为下面这样时,表明目标对象的方法如果参数所对应的对象被Monitorable注解标注,则增强会生效。
切面的关键配置:
@Before("@args(com.smart.anno.Monitorable)")
public void afterGreeting(){
System.out.println("参数的类有注解Monitorable!");
}
自定义注解:Monitorable
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Monitorable {
}
目标对象关键方法:
@Override
public void args(Waiter waiter) {
System.out.println("args hello");
}
参数对象:
@Monitorable
public interface Waiter {
public void greetTo(String clientName);
public void serveTo(String clientName);
public void smile(String clientName, int times);
}
within() 和@within: 详细示例见文末码云。
@Aspect
public class WithInAspect {
@Before("within(com.smart.aspectj.within.*)")
public void beforeGreeting(){
System.out.println("WithIn say how are you!");
}
@After("@within(com.smart.anno.Monitorable)")
public void afterGreeting(){
System.out.println("参数的类有注解Monitorable!");
}
}
target 和 @target 这两个和上面的within和@within原理类似,不同之处在于,within描述的是某个包路径下,而target描述的是具体的类。
关于@AspectJ在Spring中的使用,非常简单,只需要用@Aspect标注一个POJO就可以声明一个切面。重点是关于切点的描述,这里涉及到切点表达式函数,在Spring中支持9种,不同的表达式有不同的匹配策略,大体可以分为四类:
方法切点函数(execution,@annotation)
方法入参切点函数(args,@args)
目标类切点函数(within,@within, target,@target)
代理类切点函数:(this)
不同的切点函数,提供了丰富的匹配规则,具体要用到哪种,需要根据业务来确定。
关于各种切点函数的示例,详见文末的码云链接。
码云