上次我们学习了基于代理类的AOP实现。而新版本的Spring框架,建议使用AspectJ来开发AOP。所以接下来我们学习AspectJ开发AOP。
AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。Spring 2.0以后,Spring AOP引入了对AspectJ的支持,并允许直接使用AspectJ进行编程,而Spring自身的AOP API也尽量与AspectJ保持一致。
使用AspectJ实现AOP有两种方式:一种是基于XML的声明式AspectJ,另一种是基于注解的声明式AspectJ。
1.基于XML的声明式AspectJ
基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在<aop:config>元素内。
<aop:config>元素及其子元素如下:(红色的为常用的配置元素)
Spring配置文件中的<beans>元素可以包含多个<aop:config>,一个<aop:config>元素又可以包含属性和子元素,子元素包括<aop:pointcut>、<aop:advisor>、<aop:aspect>,配置时,这3个子元素必须按照此顺序定义。
1.1配置切面
在Spring的配置文件中,配置切面使用的是<aop:aspect>元素,该元素可以将定义好的Spring Bean转换成切面Bean,所以先定义一个普通的Spring Bean。配置<aop:aspect>元素时,通常会指定id和ref两个属性。
属性名称 | 解释 |
---|---|
id | 用来定义该切面的唯一标识名称。 |
ref | 用于引用普通的Spring Bean。 |
1.2配置切入点
当<aop:pointcut>元素作为<aop:config>元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;当<aop:pointcut>元素作为<aop:aspect>元素的子元素时,表示该切入点只对当前切面有效。
定义<aop:pointcut>元素时,通常会指定id和expression两个属性。
属性名称 | 解释 |
---|---|
id | 用于指定切入点的唯一标识名称。 |
expression | 用于指定切入点关联的切入点表达式。 |
切入点表达式(举例说明)
切入点表达式的基本格式:
1.3配置通知
使用<aop:aspect>的子元素可以配置5种常用通知,这5个子元素不支持使用子元素,但在使用时可以指定一些属性,其常用属性及其描述如下:
属性名称 | 解释 |
---|---|
pointcut | 该属性用于指定一个切入点表达式,Spring将在匹配该表达式的连接点时织入该通知。 |
pointcut-ref | 该属性指定一个已经存在的切入点名称,通常pointcut和pointcut-ref两个属性只需要使用其中之一。 |
method | 该属性指定一个方法名,指定将切面Bean中的该方法转换为增强处理。 |
throwing | 该属性只对<after-throwing>元素有效,用于指定一个形参名,异常通知方法可以通过该形参访问目标方法所抛出的异常。 |
returning | 该属性只对<after-returning>元素有效,用于指定一个形参名,后置通知方法可以通过该形参访问目标方法的返回值。 |
我们通过案例来演示基于XML的声明式AspectJ的实现过程。
(1).打开Eclipse,在SpringTest项目的src目录下,创建一个com.example.aspectjxml包,包中创建用户管理接口UserDao接口以及实现类UserDaoImpl类。
package com.example.aspectjxml;
public interface UserDao {
public void addUser();
public void delUser();
}
package com.example.aspectjxml;
public class UserDaoImpl implements UserDao{
@Override
public void addUser() {
// TODO Auto-generated method stub
System.out.println("添加用户");
}
@Override
public void delUser() {
// TODO Auto-generated method stub
System.out.println("删除用户");
}
}
(2).在com.example.aspectjxml包,创建切面类MyAspect类。
package com.example.aspectjxml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
//前置通知
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知:"+joinPoint);
System.out.println("被植入增强处理的目标方法:"+joinPoint.getSignature().getName());
}
//后置通知
public void myAfterReturrning(JoinPoint joinPoint){
System.out.println("后置通知:"+joinPoint);
System.out.println("被植入增强处理的目标方法:"+joinPoint.getSignature().getName());
}
/*
* 环绕通知
* proceedingJoinPoint是JoinPoint子接口,表示可以执行目标方法
* 1.必须是Object类型的返回值
* 2.必须接收一个参数,类型为ProceedingJoinPoint
* 3.必须throws Throwable
*/
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕前通知");
//执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
System.out.println("环绕后通知");
return obj;
}
//异常通知
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:"+e.getMessage());
}
//最终通知
public void myAfter(){
System.out.println("最终通知");
}
}
(3).在com.example.aspectjxml包,创建配置文件beans.xml。
<?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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 目标类 -->
<bean id="userDao" class="com.example.aspectjxml.UserDaoImpl"></bean>
<!-- 切面 -->
<bean id="myAspect" class="com.example.aspectjxml.MyAspect"></bean>
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 配置切入点 -->
<aop:pointcut expression="execution(* com.example.aspectjxml.*.*(..))" id="myPointCut"/>
<!-- 前置通知 -->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!-- 后置通知 returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
<aop:after-returning method="myAfterReturrning" pointcut-ref="myPointCut" returning="returnVal"/>
<!-- 环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<!-- 异常通知:如果没有异常,将不会执行增强
throwing属性:用于设置通知第二个参数的的名称、类型-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<!-- 最终通知 -->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
(4).在com.example.aspectjxml包,创建测试类JdkTest类。
package com.example.aspectjxml;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AspectjxmlTest {
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("com/example/aspectjxml/beans.xml");
UserDao userDao = (UserDao) ac.getBean("userDao");
userDao.addUser();
System.out.println("--------分割线--------");
userDao.delUser();
}
}
运行结果:
2.基于注解的声明式AspectJ
基于XML的声明式AspectJ存在一些不足,需要在Spring配置文件配置大量的代码信息,为了解决这个问题,AspectJ框架为AOP的实现提供了一套注解。
注解名称 | 解释 |
---|---|
@Aspect | 用来定义一个切面。 |
@pointcut | 用于定义切入点表达式。在使用时还需要定义一个包含名字和任意参数的方法签名来表示切入点名称,这个方法签名就是一个返回值为void,且方法体为空的普通方法。 |
@Before | 用于定义前置通知,相当于BeforeAdvice。在使用时,通常需要指定一个value属性值,该属性值用于指定一个切入点表达式(可以是已有的切入点,也可以直接定义切入点表达式)。 |
@AfterReturning | 用于定义后置通知,相当于AfterReturningAdvice。在使用时可以指定pointcut / value和returning属性,其中pointcut / value这两个属性的作用一样,都用于指定切入点表达式。returning属性值用于表示Advice方法中定义与此同名的形参,该形参可用于访问目标方法的返回值。 |
@Around | 用于定义环绕通知,相当于MethodInterceptor。在使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。 |
@After-Throwing | 用于定义异常通知来处理程序中未处理的异常,相当于ThrowAdvice。在使用时可指定pointcut / value和throwing属性。其中pointcut/value用于指定切入点表达式,而throwing属性值用于指定-一个形参名来表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法抛出的异常。 |
@After | 用于定义最终final 通知,不管是否异常,该通知都会执行。使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。 |
@DeclareParents | 用于定义引介通知,相当于IntroductionInterceptor (不要求掌握)。 |
我们通过案例来演示基于Annotation的声明式AspectJ的实现过程。
(1).打开Eclipse,在SpringTest项目的src目录下,创建一个com.example.aspectjannotation包,包中创建用户管理接口UserDao接口以及实现类UserDaoImpl类。
package com.example.aspectjannotation;
public interface UserDao {
public void addUser();
public void delUser();
}
package com.example.aspectjannotation;
import org.springframework.stereotype.Repository;
@Repository("userDao")
public class UserDaoImpl implements UserDao{
@Override
public void addUser() {
// TODO Auto-generated method stub
System.out.println("添加用户");
}
@Override
public void delUser() {
// TODO Auto-generated method stub
System.out.println("删除用户");
}
}
(2).在com.example.aspectjannotation包,创建切面类MyAspect类。
package com.example.aspectjannotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//切面类
@Aspect
@Component
public class MyAspect {
//定义切入点表达式
@Pointcut("execution(* com.example.aspectjannotation.*.*(..))")
//定义一个返回值为void、方法体为空的方法来命名切入点
private void myPointCut(){
}
@Before("myPointCut()")
public void myBefore(JoinPoint joinpoint){
System.out.println("前置通知:"+joinpoint);
System.out.println("被植入增强处理的目标方法为:"+joinpoint.getSignature().getName());
}
@AfterReturning("myPointCut()")
public void myAfterReturning(JoinPoint joinpoint){
System.out.println("后置通知:"+joinpoint);
System.out.println(",被植入增强处理的目标方法为:"+joinpoint.getSignature().getName());
}
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕开始。。。");
Object obj = proceedingJoinPoint.proceed();
System.out.println("环绕结束。。。");
return obj;
}
@AfterThrowing(value="myPointCut()",throwing="e")
public void myAfterThrowing(JoinPoint joinpoint,Throwable e){
System.out.println("异常通知:"+e.getMessage());
}
@After("myPointCut()")
public void myAfter(){
System.out.println("最终通知");
}
}
(3).在com.example.aspectjannotation包,创建配置文件beans.xml。
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 指定需要扫描的包,使注解生效 -->
<context:component-scan base-package="com.example.aspectjannotation"></context:component-scan>
<!-- 启动基于注解的声明式AspectJ支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
(4).在com.example.aspectjannotation包,创建测试类JdkTest类。
package com.example.aspectjannotation;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AspectjannotationTest {
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("com/example/aspectjannotation/beans.xml");
UserDao userDao = (UserDao) ac.getBean("userDao");
userDao.addUser();
System.out.println("--------分割线--------");
userDao.delUser();
}
}
运行结果:
使用注解的方式更加简单、方便,所以,在实际开发中建议使用注解的方式进行AOP开发。
注意:如果在同一个连接点有多个通知需要执行,那么在同一切面中,目标方法之前的前置通知和环绕通知的执行顺序是未知的,目标方法之后的后置通知和环绕通知的执行顺序是未知的。
这就是Spring中AspectJ开发,若有错漏,欢迎指正,希望大家一起学习进步!!!!
如果转载以及CV操作,请务必注明出处,谢谢!