# 精通Spring+4.x++企业开发与实践之基于@AspectJ和Schema的AOP
使用@AspectJ的条件
1.保证是java5以上的版本(需要使用注解,而java5及以上才使用注解)
2.需要将Spring的asm(轻量级的字节码处理框架)的模块添加到类路径中,因为java的反射无法获取入参的名字,所以Spring就是要asm处理@AspectJ中描述的方法入参名。
3.Spring使用AspectJ提供的@AspectJ注解类库及相应的解析类库,需要在pom.xml文件添加aspectjweaver和aspectj的工具类aspectjrt
例子:
PreGreetingAspect.java
@Aspect//使用该注解定义一个切面
public class PreGreetingAspect {
/**
* 这段代码包含了横切的逻辑
* @Before 增强的类型
* "execution(* greetTo(..))"目标切点的表达式
*/
@Before("execution(* greetTo(..))")
public void beforeGreeting(){
System.out.println("How are you");
}
}
测试代码:
//创建被代理的对象
Waiter waiter = new PoliteWaiter();
//AspectJ代理工厂
AspectJProxyFactory factory = new AspectJProxyFactory();
//提娜佳目标类
factory.setTarget(waiter);
//添加切面类
factory.addAspect(PreGreetingAspect.class);
//生成切入的代理对象
Waiter proxy = factory.getProxy();
proxy.greetTo("张三");
执行结果: How are you
greet to 张三....
使用schema的配置方式进行配置
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--基于asjectj的切面驱动器-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="waiter" class="com.flexible.aspectj.PoliteWaiter"></bean>
<bean class="com.flexible.aspectj.PreGreetingAspect"></bean>
</beans>
测试代码:
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml");
Waiter waiter = (Waiter) context.getBean("waiter");
waiter.greetTo("zhangsan");
@AspectJ语法基础
@AspectJ使用Java5.0注解和正规的AspectJ的切点表达式语言描述切面,由于Spring只支持方法的链接,所以Spring仅支持部分AspectJ的切点语言。
切点表达式函数
组成:1.关键字和操作参数execution(* greetTo(..)),execution是关键字,"* greetTo(..)"位操作参数。execution代表目标类执行某一方法,"* greetTo(..)"描述目标方法的匹配模式串,二者联合起来表示目标类的greetTo()方法的连接点。execution()称为函数,"* greet(..)"称为函数入参。
Spring支持9个@AdpectJ切点表达式函数,它用不同的方式藐视目标类的连接点。根据描述的不同可以分为4种: 1.方法切点函数:通过描述目标类方法的信息定义连接点。 2.方法入参切点函数:通过描述目标类的方法入参的信息定义连接点。 3.目标类切点函数:通过描述目标类类型的信心定义连接点。 4.代理切点函数:通过描述目标类的代理类的信息定义连接点。
入参函数的通配符
-
*:匹配任意字符,但它只能匹配上下文中的一个元素。
-
..:匹配任意字符,可以匹配上下文中的多个元素,但在表示类时,必须和*联合使用,而在表示入参时则单独使用。
-
+:表示按类型匹配指定的类的所有类,必须跟在类名后面,如com.flexible.Car+表示继承或者拓展了制定类的所有类,同时还包括指定类本身。
@AaspectJ函数按其是否支持通配符及支持的程度,可以分为三类:
1.支持通配符:execution()和within(),如 within(com.flexible.),within(com.flexible.service...*.Service).
2.仅支持"+"通配符:args(),this()和target(),如args(com.flexible.Waiter+),target(java.util.List+),但是其实这几个函数是不是有这个通配符都时一样的。
3.不支持通配符的:@args,@within(),@target()和@annotation().
不同类型的增强
1.@Before 前置增强,相当于BeforeAdvice。Before注解类用于两个成员
- value:该成员用于定义切点。
- argsNames:由于无法通过Java反射机制获取方法入参名,所以如果在Java编译时未启用调式信息,或者需要在运行期间解析切点,就必须通过这个成员指定直接所标注增强的方法的参数名(注意二者名字必须完全相同),多个参数名用逗号分开。
2.@AfterReturning 后置增强,相当于AfterReturningAdvice。AfterReturning注解类拥有4个成员。
- value:该成员用于定义切点
- pointcut:表示切点的信息。如果显式地指定pointcut值,那么它将覆盖value的设置值,可以将pointcut成员看作value同义词。
- returning:将目标对象方法的返回值绑定给增强的方法。
- argNames:如上所述
3.@Around 环绕增强,相当于MethodInterceptor。Around注释类用于两个成员。
- value:该成员用于定义切点
- argNames:如上所述
4.@AfterThrowing 抛出增强,相当于ThrowsAdvice。AfterThrowing注解有4个成员。
-
value:指定切点
-
pointcut:表示切点的信息,如果显示指定pointcut值,那么它将覆盖value值得设置值,可以将pointcut成员看作value得同义词。
-
throwing:将抛出得异常绑定到增强方法中。
-
argNames:如上所述。
5.@After Final增强,不管时抛出异常还是正常退出,该增强都会执行,该增强没有对应得增强接口,可以把它看成ThrowsAdvice和AfterReturningAdvice得混合物,议案用于释放资源,相当于try{}finally{}的控制流程。它有两个成员;
-
value:该成员用于定义切点。
-
argNames:如上所述。
6.@DeclareParents 引介增强,相当于IntroductionInterceptor.DeclareParents注解类拥有两个成员
-
value:定义切点,表示在那个木堡垒上添加引介增强。
-
defaultImpl:默认接口实现类。
引介增强的使用
通过引介增强将waiter也具有seller的功能
EnableSellerAspect.java
@Aspect
public class EnableSellerAspect {
//1.为PoliteWaiter太你家接口实现 2.默认接口实现类 3.要是西安的目标接口。
@DeclareParents(value = "com.flexible.inroductionofdeclareparent.PoliteWaiter",defaultImpl = SmartSeller.class)
public Seller seller;
}
beans.xml
<!--基于asjectj的切面驱动器-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="waiter_2" class="com.flexible.inroductionofdeclareparent.PoliteWaiter"></bean>
<bean class="com.flexible.inroductionofdeclareparent.EnableSellerAspect"></bean>
测试代码:
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml");
Waiter waiter = (Waiter) context.getBean("waiter_2");
waiter.greetTo("zhangsan");
Seller seller = (Seller) waiter;
seller.sell("apple");
执行结果:
切点函数详解
命名切点
直接将切点声明在增强的方式是匿名增强,而如果希望在其他地方重用一个切点,可以通过@Pointcut注解及切面类方法对切点进行命名。
例子:
import org.aspectj.lang.annotation.Pointcut;
public class NamedPointcut {
//通过注解方法inpackage()对该切点进行命名,方法可视域修饰符为private
//表明该命名切点只能在本切面类中使用
@Pointcut("within(com.flexible.*)")
private void inpackage(){}
//通过注解方法greetTo()对该切点进行命名,方法可以视域修饰符为protected
//表明该命名切点可以在当前包中的切面类,自切脉你类只要。
@Pointcut("execution(* greetTo(..))")
protected void greetTo(){}
//引用命名切点定义的切点,本切点也是命名切点,它对应的可视域为public
@Pointcut("inpackage() && greetTo()")
public void inPkgGreetTo(){}
}
命名切点的使用类方法作为切点的名称,此方法的访问修饰符还控制了切点的可引用性。命名切点定义好之后我们就可以在定义切面的时通过名称引用切点。
例子:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class NamedAspect {
@Before("NamedPointcut.inPkgGreetTo()")
public void pkgGreetTo(){
System.out.println("----------pkgGreetTo() executed!---");
}
@Before("!target(com.flexible.pointcutbreakdown.annotation.PoliteWaiter) && NamedPointcut.inPkgGreetTo()")
public void pkgGreetToNotNaivewaiter(){
System.out.println("----------pkgGreetToNotNaivewaiter() executed!---");
}
}
增强植入的顺序
一个连接点可u哦同时匹配多个其欸但,切点对应的增强在连接点上的植入顺序有三种情况需要讨论
1.如果增强在同一个切面类中声明,则依照增强在切面类中定义的顺序织入
2.如果增强位于不同的切面类中,且这些切面类都实现了org.springframework.core.Ordered接口,则由接口方法的顺序好决定(顺序号小的先织入)
3.如果增强位于不同的切面类中,且这些切面类没有实现org.springframework.core.Ordered接口,则织入顺序是不确定的。
例子: 如果有切面A和切面B,而且这两个切面都实现了org.springframework.core.Ordered接口,A的顺序是1,而得顺序是2,而且A定义了三个切点,B定义两个切点。那么访问顺序如下图所示:
访问连接点信息
AspectJ使用org.aspectj.lang.JoinPoint接口表示目标类连接点对象。如果是环绕增强,则使用org.aspectj.lang.ProceedingJoinPoint表示连系欸但对象,该类是JoinPoint得子接口。任何增强方法都可以通过将第一个入参声明为JoinPoint访问连系欸但上下文信息。 1.JoinPoint
- java.lang.Object[] getArgs():获取连系欸但方法运行时得入参列表。
- Signature getSignature():获取连接点得方法签名对象。
- java.lang.Object getTarget():获取连接点所在目标对象
- java.lang.Object getThis():获取代理对象本身。
2.ProceedingJoinPoint
ProceedingJoinPoint继承于JoinPoint子接口,它新增了两个用于执行连接点得方法。
- java.lang.Object.proceed() throws java.lang.Throwable:通过反射执行目标对象得连接点处得方法。
- java.lang.Object proceed(java.lang.Object[] args)throws java.lang.Throwable:通过反射执行目标对象连接点处得方法,不过使用新得入仓替换原来得入参。
例子:
TestAspect.java
@Aspect
public class TestAspect {
@Around("execution(* com.flexible.obtainproceedingpointinfo..*(..))")
public void joinPointAccess(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("------joinPointAccess----");
System.out.println("args[0]:" + proceedingJoinPoint.getArgs()[0]);
System.out.println("signature:" + proceedingJoinPoint.getTarget().getClass());
proceedingJoinPoint.proceed();
System.out.println("------joinPointAccess----");
}
}
beans.xml
<!--基于asjectj的切面驱动器-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--获取连接点的信息-->
<bean id="waiter_4" class="com.flexible.obtainproceedingpointinfo.PoliteWaiter"></bean>
<bean class="com.flexible.obtainproceedingpointinfo.TestAspect"></bean>
测试代码:
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml");
Waiter waiter = (Waiter) context.getBean("waiter_4");
waiter.greetTo("zhangsan");
执行结果: