- 切面:使用切点表达式表示,指定了当前切面逻辑所要包裹的业务模块的范围大小;
- Advice:也即切面逻辑,指定了当前用于包裹切面指定的业务模块的逻辑。
- @Before:该注解标注的方法在业务模块代码执行之前执行,其不能阻止业务模块的执行,除非抛出异常;
- @AfterReturning:该注解标注的方法在业务模块代码执行之后执行;
- @AfterThrowing:该注解标注的方法在业务模块抛出指定异常后执行;
- @After:该注解标注的方法在所有的Advice执行完成后执行,无论业务模块是否抛出异常,类似于finally的作用;
- @Around:该注解功能最为强大,其所标注的方法用于编写包裹业务模块执行的代码,其可以传入一个ProceedingJoinPoint用于调用业务模块的代码,无论是调用前逻辑还是调用后逻辑,都可以在该方法中编写,甚至其可以根据一定的条件而阻断业务模块的调用;
- @DeclareParents:其是一种Introduction类型的模型,在属性声明上使用,主要用于为指定的业务模块添加新的接口和相应的实现。
- @Aspect:严格来说,其不属于一种Advice,该注解主要用在类声明上,指明当前类是一个组织了切面逻辑的类,并且该注解中可以指定当前类是何种实例化方式,主要有三种:singleton、perthis和pertarget,具体的使用方式后面会进行讲解。
与其对应的是@AfterReturning,而不是@After,@After是所有的切面逻辑执行完之后才会执行,无论是否抛出异常。
3.1 execution
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
- modifiers-pattern:方法的可见性,如public,protected;
- ret-type-pattern:方法的返回值类型,如int,void等;
- declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
- name-pattern:方法名类型,如buisinessService();
- param-pattern:方法的参数类型,如java.lang.String;
- throws-pattern:方法抛出的异常类型,如java.lang.Exception;
execution(public * com.spring.service.BusinessObject.businessService(java.lang.String,..))
- *通配符,该通配符主要用于匹配单个单词,或者是以某个词为前缀或后缀的单词。
execution(* com.spring.service.BusinessObject.*())
execution(* com.spring.service.Business*.*())
- ..通配符,该通配符表示0个或多个项,主要用于declaring-type-pattern和param-pattern中,如果用于declaring-type-pattern中,则表示匹配当前包及其子包,如果用于param-pattern中,则表示匹配0个或多个参数。
execution(* com.spring.service..*.businessService())
execution(* com.spring.service.BusinessObject.businessService(java.lang.String,..))
3.2 within
within(declaring-type-pattern)
within(com.spring.service.BusinessObject)
within(com.spring.service.*)
within(com.spring.service..*)
3.3 args
args(param-pattern)
args(java.lang.String)
args(java.lang.String,..,java.lang.Integer)
this(com.spring.service.BusinessObject)
target(com.spring.service.BusinessObject)
代理模式实现方式及优缺点对比)。针对这两种代理类型,关于目标对象与代理对象,理解如下两点是非常重要的:
- 如果目标对象被代理的方法是其实现的某个接口的方法,那么将会使用Jdk代理生成代理对象,此时代理对象和目标对象是两个对象,并且都实现了该接口;
- 如果目标对象是一个类,并且其没有实现任意接口,那么将会使用Cglib代理生成代理对象,并且只会生成一个对象,即Cglib生成的代理类的对象。
- this(SomeInterface)或target(SomeInterface):这种情况下,无论是对于Jdk代理还是Cglib代理,其目标对象和代理对象都是实现SomeInterface接口的(Cglib生成的目标对象的子类也是实现了SomeInterface接口的),因而this和target语义都是符合的,此时这两个表达式的效果一样;
- this(SomeObject)或target(SomeObject),这里SomeObject没实现任何接口:这种情况下,Spring会使用Cglib代理生成SomeObject的代理类对象,由于代理类是SomeObject的子类,子类的对象也是符合SomeObject类型的,因而this将会被匹配,而对于target,由于目标对象本身就是SomeObject类型,因而这两个表达式的效果一样;
- this(SomeObject)或target(SomeObject),这里SomeObject实现了某个接口:对于这种情况,虽然表达式中指定的是一种具体的对象类型,但由于其实现了某个接口,因而Spring默认会使用Jdk代理为其生成代理对象,Jdk代理生成的代理对象与目标对象实现的是同一个接口,但代理对象与目标对象还是不同的对象,由于代理对象不是SomeObject类型的,因而此时是不符合this语义的,而由于目标对象就是SomeObject类型,因而target语义是符合的,此时this和target的效果就产生了区别;这里如果强制Spring使用Cglib代理,因而生成的代理对象都是SomeObject子类的对象,其是SomeObject类型的,因而this和target的语义都符合,其效果就是一致的。
// 目标类 public class Apple { public void eat() { System.out.println("Apple.eat method invoked."); } }
// 切面类 @Aspect public class MyAspect { @Around("this(com.business.Apple)") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("this is before around advice"); Object result = pjp.proceed(); System.out.println("this is after around advice"); return result; } }
<!-- bean声明文件 --> <bean id="apple" class="chapter7.eg1.Apple"/> <bean id="aspect" class="chapter7.eg6.MyAspect"/> <aop:aspectj-autoproxy/>
// 驱动类 public class AspectApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Apple fruit = (Apple) context.getBean("apple"); fruit.eat(); } }
this is before around advice Apple.eat method invoked. this is after around advice
target(com.business.Apple)
public class Apple implements IApple { public void eat() { System.out.println("Apple.eat method invoked."); } }
public class AspectApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Fruit fruit = (Fruit) context.getBean("apple"); fruit.eat(); } }
Apple.eat method invoked.
this is before around advice Apple.eat method invoked. this is after around advice
3.5 @within
,其使用语法如下所示:
@within(annotation-type)
@within(com.spring.annotation.BusinessAspect)
(这里驱动类和xml文件配置与3.4节使用的一致,这里省略):
// 注解类 @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface FruitAspect { }
// 目标类 @FruitAspect public class Apple { public void eat() { System.out.println("Apple.eat method invoked."); } }
// 切面类 @Aspect public class MyAspect { @Around("@within(com.business.annotation.FruitAspect)") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("this is before around advice"); Object result = pjp.proceed(); System.out.println("this is after around advice"); return result; } }
this is before around advice Apple.eat method invoked. this is after around advice
3.6 @annotation
@annotation(annotation-type)
@annotation(com.spring.annotation.BusinessAspect)
,只是这里需要对Apple和MyAspect使用和指定注解的方式进行修改,FruitAspect不用修改的原因是声明该注解时已经指定了其可以使用在类,方法和参数上:
// 目标类,将FruitAspect移到了方法上 public class Apple { @FruitAspect public void eat() { System.out.println("Apple.eat method invoked."); } }
@Aspect public class MyAspect { @Around("@annotation(com.business.annotation.FruitAspect)") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("this is before around advice"); Object result = pjp.proceed(); System.out.println("this is after around advice"); return result; } }
this is before around advice Apple.eat method invoked. this is after around advice
3.7 @args
,@args则表示使用指定注解标注的类作为某个方法的参数时该方法将会被匹配。:
@args(annotation-type)
@args(com.spring.annotation.FruitAspect)
<!-- xml配置文件 --> <bean id="bucket" class="chapter7.eg1.FruitBucket"/> <bean id="aspect" class="chapter7.eg6.MyAspect"/> <aop:aspectj-autoproxy/>
// 使用注解标注的参数类 @FruitAspect public class Apple {}
// 使用Apple参数的目标类 public class FruitBucket { public void putIntoBucket(Apple apple) { System.out.println("put apple into bucket."); } }
@Aspect public class MyAspect { @Around("@args(chapter7.eg6.FruitAspect)") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("this is before around advice"); Object result = pjp.proceed(); System.out.println("this is after around advice"); return result; } }
// 驱动类 public class AspectApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); FruitBucket bucket = (FruitBucket) context.getBean("bucket"); bucket.putIntoBucket(new Apple()); } }
,因而该方法的调用将会被环绕。执行驱动类,结果如下:
this is before around advice put apple into bucket. this is after around advice
3.8 @DeclareParents
(引入),表示为指定的目标类引入新的属性和方法。,因为无论是Jdk代理还是Cglib代理,想要引入新的方法,只需要通过一定的方式将新声明的方法织入到代理类中即可,因为代理类都是新生成的类,因而织入过程也比较方便。:
@DeclareParents(value = "TargetType", defaultImpl = WeaverType.class) private WeaverInterface attribute;
@DeclareParents(value = "com.spring.service.Apple", defaultImpl = DescriberImpl.class) private IDescriber describer;
// 织入方法的目标类 public class Apple { public void eat() { System.out.println("Apple.eat method invoked."); } }
// 要织入的接口 public interface IDescriber { void desc(); }
// 要织入接口的默认实现 public class DescriberImpl implements IDescriber { @Override public void desc() { System.out.println("this is an introduction describer."); } }
// 切面实例 @Aspect public class MyAspect { @DeclareParents(value = "com.spring.service.Apple", defaultImpl = DescriberImpl.class) private IDescriber describer; }
// 驱动类 public class AspectApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); IDescriber describer = (IDescriber) context.getBean("apple"); describer.desc(); } }
。这里perthis和pertarget表达式中都是指定一个切面表达式,其语义与前面讲解的this和target非常的相似,perthis表示如果某个类的代理类符合其指定的切面表达式,那么就会为每个符合条件的目标类都声明一个切面实例;pertarget表示如果某个目标类符合其指定的切面表达式,那么就会为每个符合条件的类声明一个切面实例。从上面的语义可以看出,perthis和pertarget的含义是非常相似的。如下是perthis和pertarget的使用语法:
perthis(pointcut-expression)
pertarget(pointcut-expression)
<!-- xml配置文件 --> <bean id="apple" class="chapter7.eg1.Apple"/> <bean id="aspect" class="chapter7.eg6.MyAspect" scope="prototype"/> <aop:aspectj-autoproxy/>
// 目标类实现的接口 public interface Fruit { void eat(); }
// 业务类 public class Apple implements Fruit { public void eat() { System.out.println("Apple.eat method invoked."); } }
// 切面类 @Aspect("perthis(this(com.spring.service.Apple))") public class MyAspect { public MyAspect() { System.out.println("create MyAspect instance, address: " + toString()); } @Around("this(com.spring.service.Apple)") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("this is before around advice"); Object result = pjp.proceed(); System.out.println("this is after around advice"); return result; } }
// 驱动类 public class AspectApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Fruit fruit = context.getBean(Fruit.class); fruit.eat(); } }
Apple.eat method invoked.
@Aspect("pertarget(target(com.spring.service.Apple))") public class MyAspect { public MyAspect() { System.out.println("create MyAspect instance, address: " + toString()); } @Around("target(com.spring.service.Apple)") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("this is before around advice"); Object result = pjp.proceed(); System.out.println("this is after around advice"); return result; } }
create MyAspect instance, address: chapter7.eg6.MyAspect@48fa0f47 this is before around advice Apple.eat method invoked. this is after around advice
<!-- xml配置文件 --> <bean id="apple" class="chapter7.eg1.Apple" scope="prototype"/> <bean id="aspect" class="chapter7.eg6.MyAspect" scope="prototype"/> <aop:aspectj-autoproxy/>
public class AspectApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Fruit fruit = context.getBean(Fruit.class); fruit.eat(); fruit = context.getBean(Fruit.class); fruit.eat(); } }
create MyAspect instance, address: chapter7.eg6.MyAspect@48fa0f47 this is before around advice Apple.eat method invoked. this is after around advice create MyAspect instance, address: chapter7.eg6.MyAspect@56528192 this is before around advice Apple.eat method invoked. this is after around advice