一、了解注解
JDK5.0注解可以看成是Javadoc标签和Xdoclet标签的延伸和发展。在JDK5.0中,我们可以自定义这些标签,并通过Java语言反射机制获取类中标注的注解,完成特定的功能。
注解是代码的附属信息,它遵循一个基本的原值:注解不能直接干扰程序代码的运行,无论增加或删除注解,代码都能够正常运行。Java语言注解器会忽略这些注解,而由第三方工具负责对注解进行处理。第三方工具可以利用代码中的注解间接控制程序代码的运行,它们通过Java反射机制读取注解的信息,并根据这些信息更改目标程序的逻辑,而这正是Spring AOP对@AspectJ提供支持所采取的方法。
(一)、一个简单的注解类
(1)、注解类的编写:
package com.baobao.springtest7.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)//声明注解的保留期限
@Target(ElementType.METHOD)//声明可以使用该注解的目标类型
public @interface NeedTest {//定义注解
boolean value() default true;//声明注解成员
}
@Retention(RetentionPolicy.RUNTIME)表示NeedTest这个注解可以在运行期被JVM读取,注解的保留期限类型在java.lang.annotation.Retention类中定义。
@Target(ElementType.METHOD)表示NeedTest这个注解只能应用到目标类的方法上,注解的应用目标在java.lang.annotation.ElementType类中定义。
(2)、使用注解public class ForumService {
@NeedTest(value=true)//标注注解
public void deleteForum(int forumId){
System.out.println("删除模块:"+forumId);
}
@NeedTest(value=false)//标注注解
public void deleteTopic(int postId){
System.out.println("删除主题:"+postId);
}
}
(3)、访问注解
@Test
public void test() {
//得到ForumService的class对象
Class clazz = ForumService.class;
//得到ForumService对应的Method数组,Method是类或接口中的方法的信息
Method[] methods = clazz.getDeclaredMethods();
System.out.println("一共有方法:"+methods.length);
for(Method method:methods){
//获取方法上所标注的注解对象
NeedTest nt = method.getAnnotation(NeedTest.class);
if(nt!=null){
if(nt.value()){
System.out.println(method.getName()+"()需要测试");
}else{
System.out.println(method.getName()+"()不需要测试");
}
}
}
通过方法的反射对象获取了方法上标注的NeedTest注解对象,接着就可以访问注解对象的成员,从而得到ForumService类方法的测试需求。
二、使用@AspectJ
@AspectJ是采用注解来描述切点、增强。
(一)、一个简单的例子 目标类
public class NaiveWaiter implements Waiter {
public void greetTo(String clientName) {
System.out.println("NaiveWaiter:greetTo"+clientName);
}
public void serveTo(String clientName) {
System.out.println("NaiveWaiter:serveTo"+clientName);
}
}
切面类
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
//通过该注解将PreGreetingAspect标识为一个切面
@Aspect
public class PreGreetingAspect {
//定义切点和增强类型
@Before("execution(* greetTo(..))")
public void beforeGreeting(){//增强的横切逻辑
System.out.println("How are you");
}
}
测试
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
import com.baobao.springtest7.aspectj.PreGreetingAspect;
import com.baobao.springtest7.service.Waiter;
import com.baobao.springtest7.service.impl.NaiveWaiter;
public class AspectJProxyTest {
@Test
public void test() {
Waiter target = new NaiveWaiter();
AspectJProxyFactory factory = new AspectJProxyFactory();
//设置目标对象
factory.setTarget(target);
//添加切面类
factory.addAspect(PreGreetingAspect.class);
//生成织入切面的代理对象
Waiter proxy = factory.getProxy();
proxy.greetTo("John");
proxy.serveTo("John");
}
}
(二)、
通过配置使用@AspectJ切面
配置信息:
<?xml version="1.0" encoding="UTF-8"?>
<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.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<!-- 基于@AspectJ切面的驱动器 -->
<aop:aspectj-autoproxy/>
<bean id="waiter" class="com.baobao.springtest7.service.impl.NaiveWaiter"/>
<bean class="com.baobao.springtest7.aspectj.PreGreetingAspect"/>
</beans>
测试代码:
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.baobao.springtest7.service.Waiter;
public class AspectTest {
@Test
public void test() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext1.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter");
waiter.greetTo("John");
waiter.serveTo("Tom");
}
}
在配置文件中引入aop命名空间,通过aop命名空间的<aop:aspectj-autoproxy>自动为Spring容器中那些匹配@AspectJ切面的Bean创建代理,完成切面织入。Spring在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建,但具体实现细节被<aop:aspectj-autoproxy>隐藏起来了。
三、@AspectJ语法基础
(一)、切点表达式函数
Spring支持9个@AspectJ切点表达式函数,它们用不同的方式描述目标类的连接点,根据描述对象的不同,可以大致分为4中类型:
(1)、方法切点函数:通过描述目标类方法信息定义连接点;
(2)、方法入参切点函数:通过描述目标类方法入参的信息定义连接点;
(3)、目标类切点函数:通过描述目标类类型信息定义连接点;
(4)、代理类切点函数:通过描述目标类的代理类的信息定义连接点。
这4中类型的切点函数,如表说明:
(四)、引介增强的用法
假设NaiveWaiter能够同时充当售货员的角色,即通过切面技术为NaiveWaiter增强Seller接口的实现。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import com.baobao.springtest7.service.Seller;
import com.baobao.springtest7.service.impl.SmartSeller;
@Aspect
public class EnableSellerAspect {
//引介切面
@DeclareParents(value="com.baobao.springtest7.service.impl.NaiveWaiter",
defaultImpl=SmartSeller.class)
public Seller seller;
}
在EnableSellerAspect切面中,我们通过@DeclareParents为NaiveWaiter添加了一个需要实现的Seller接口,并制定其默认实现类为SmartSeller,然后通过切面节数将SmartSeller融合到NaiveWaiter中,这样NaiveWaiter就实现Seller接口了。
在Spring配置文件中配置好切面和NaiveWaiter的Bean
<aop:aspectj-autoproxy/>
<bean id="waiter" class="com.baobao.springtest7.service.impl.NaiveWaiter"/>
<bean class="com.baobao.springtest7.aspectj.EnableSellerAspect"/>
测试代码
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext1.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter");
waiter.greetTo("John");
Seller seller = (Seller) waiter;
seller.sell("beer");
代码成功执行,可见NaiveWaiter已经成功地新增了Seller接口的实现。
四、切点函数详解
(一)、@annotation()
@annotation表示标注了某个注解的所有方法。我们通过一个实例说明@annotation()的用法,TestAspect定义了一个后置增强切面,该增强将应有到标注有NeedTest的目标方法中:
@Aspect
public class TestAspect {
//后置增强切面@annotation()括号中为标签类的类名,所有使用了该标签的类都会被织入该增强逻辑
@AfterReturning("@annotation(com.baobao.springtest7.anno.NeedTest)")
public void needTestFun(){
System.out.println("needTestFun()executed!");
}
}
(二)、execution()
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
(1)、通过方法签名定义切点
1)、execution(public**(..)):匹配所有目标类Public方法。第一个*代表返回类型;第二个*代表方法名;而..代表任意入参的方法;
2)、execution(**To(..)):匹配目标类所有以To为后缀的方法。第一个*代表返回类型;而*To代表任意以To为后缀的方法。
(2)、通过类定义切点
1)、execution(*com.baobao.Waiter.*(..)):匹配Waiter接口的所有方法,它匹配NaireWaiter和NaughtyWaiter类的greetTo()和serveTo()方法。第一个*代表返回任意类型;com.baobao.Waiter.*代表Waiter接口中的所有方法;
2)、execution(*com.baobao.Waiter+.*(..)):匹配Waiter接口及其所有实现类的方法,它不但匹配NaiveWaiter和NaughtyWaiter类的greetTo()和serveTo()这两个Waiter接口定义的方法,同时还匹配NaiveWaiter#smile()和NaugtyWaiter#joke()这两个接口中定义的方法。
(3)、通过类包定义切点
在类名模式串中,“.*”表示包下的所有类,而“..*”表示包、子孙包下的所有类。
1)、execution(*com.baobao.*(..)):匹配com.baobao包下所有类的所有方法;
2)execution(*com.baobao..*(..)):匹配com.baobao包、子孙包下所有类的所有方法
3)execution(*com..*.*Dao.find*(..)):匹配包名前缀为com的任何包下类名后缀到Dao的方法,方法名必须以find为前缀。
(4)、通过方法入参定义切点
切点表达式中方法入参部分比较复杂,可以使用“*”和“..”通配符,其中“*”表示任意类型的参数;而“..”表示任意类型参数且参数个数不限。
1)、execution(*joke(String,in)):匹配joke(String,int)方法,且第一个入参是String,第二个入参是int。