Spring框架
Spring AOP(面向切面编程)是什么?
面向切面编程(AOP)和面向对象编程(OOP)类似,也是一种编程模式。Spring AOP 是基于 AOP 编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间的松耦合目的。
AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。
Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。
AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。
为了更好地理解 AOP,就需要对 AOP 的相关术语有一些了解,这些专业术语主要包含 Joinpoint、Pointcut、Advice、Target、Weaving、Proxy 和 Aspect,它们的含义如下表所示。
名称 | 说明 |
Joinpoint(连接点) | 指那些被拦截到的点,在 Spring 中,可以被动态代理拦截目标类的方法。 |
Pointcut(切入点) | 指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 |
Advice(通知) | 指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。 |
Target(目标) | 指代理的目标对象。 |
Weaving(植入) | 指把增强代码应用到目标上,生成代理对象的过程。 |
Proxy(代理) | 指生成的代理对象。 |
Aspect(切面) | 切入点和通知的结合。 |
Spring JDK动态代理
JDK 动态代理是通过 JDK 中的 java.lang.reflect.Proxy 类实现的。下面通过具体的案例演示 JDK 动态代理的使用。
1. 创建项目
2. 创建接口 StudentDao
在项目的 src 目录下创建一个名为 com.wangxing.jdkproxydemo1.dao的包,在该包下创建一个 StudentDao接口,编辑后如下所示。
package com.wangxing.jdkproxydemo1.dao;
public interface StudentDao {
public void add(); // 添加
public void update(); // 修改
public void delete(); // 删除
public void find(); // 查询
}
3. 创建实现类 StudentDaoImpl
在com.wangxing.jdkproxydemo1.dao.impl包下创建 CustomerDao 接口的实现类 StudentDaoImpl,并实现该接口中的所有方法,如下所示。
package com.wangxing.jdkproxydemo1.dao.impl;
import com.wangxing.jdkproxydemo1.dao.StudentDao;
public class StudentDaoImpl implements StudentDao {
@Override
public void add() {
System.out.println("添加学生...");
}
@Override
public void update() {
System.out.println("修改学生...");
}
@Override
public void delete() {
System.out.println("删除学生...");
}
@Override
public void find() {
System.out.println("查询学生...");
}
}
4. 创建切面类 MyAspect
在 src 目录下,创建一个名为com.wangxing.jdkproxydemo1.jdkaspect的包,在该包下创建一个切面类 MyAspect,编辑后如下所示。
package com.wangxing.jdkproxydemo1.jdkaspect;
public class MyAspect {
public void myBefore() {
System.out.println("方法执行之前");
}
public void myAfter() {
System.out.println("方法执行之后");
}
}
上述代码中,在切面中定义了两个增强的方法,分别为 myBefore() 方法和 myAfter() 方法,用于对目标类(CustomerDaoImpl)进行增强。
5. 创建代理类 MyBeanFactory
在 com.wangxing.jdkproxydemo1.jdkaspect包下创建一个名为 MyBeanFactory 的类,在该类中使用 java.lang.reflect.Proxy 实现 JDK 动态代理,如下所示。
package com.wangxing.jdkproxydemo1.jdkaspect;
import com.wangxing.jdkproxydemo1.dao.StudentDao;
import com.wangxing.jdkproxydemo1.dao.impl.StudentDaoImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyBeanFactory {
public static StudentDao getBean() {
// 准备目标类
final StudentDao studentDao = new StudentDaoImpl();
// 创建切面类实例
final MyAspect myAspect = new MyAspect();
// 使用代理类,进行增强
return (StudentDao) Proxy.newProxyInstance(
MyBeanFactory.class.getClassLoader(),
new Class[] { StudentDao.class }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {
myAspect.myBefore(); // 前增强
Object obj = method.invoke(studentDao, args);
myAspect.myAfter(); // 后增强
return obj;
}
});
}
}
上述代码中,定义了一个静态的 getBean() 方法,这里模拟 Spring 框架的 IoC 思想,通过调用 getBean() 方法创建实例,第 14 行代码创建了 customerDao 实例。
第 16 行代码创建的切面类实例用于调用切面类中相应的方法;第 18~26 行就是使用代理类对创建的实例 customerDao 中的方法进行增强的代码,其中 Proxy 的 newProxyInstance() 方法的第一个参数是当前类的类加载器,第二参数是所创建实例的实现类的接口,第三个参数就是需要增强的方法。
在目标类方法执行的前后,分别执行切面类中的 myBefore() 方法和 myAfter() 方法。
6. 创建测试
@Test
public void test1(){
//得到代理对象
StudentDao studentDao=MyBeanFactory.getBean();
//得到普通对象
//StudentDao studentDao=new StudentDaoImpl();
studentDao.add();
}
上述代码中,在调用 getBean() 方法时,获取的是 StudentDao 类的代理对象,然后调用了该对象中的方法。
7. 运行项目并查看结果
Spring CGLlB动态代理
通过《Spring JDK动态代理》教程的学习可以知道,JDK 动态代理使用起来非常简单,但是它也有一定的局限性,这是因为 JDK 动态代理必须要实现一个或多个接口,如果不希望实现接口,则可以使用 CGLIB 代理。
CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它被许多 AOP 框架所使用,其底层是通过使用一个小而快的字节码处理框架 ASM(Java 字节码操控框架)转换字节码并生成新的类。因此 CGLIB 要依赖于 ASM 的包,解压 Spring 的核心包 spring-core-5.1.5.RELEASE.jar,文件目录如图 1 所示。
在图 1 中可以看出,解压的核心包中包含 cglib 和 asm,也就是说 spring-core-5.1.5.RELEASE.jar 版本的核心包已经集成了 CGLIB 所需要的包,所以在开发中不需要另外导入 ASM 的 JAR 包了。
下面通过案例演示实现 CGLIB 的代理过程。
1. 创建项目,导入Spring依赖
2.在com.wangxing.cglibdemo1.dao包下创建接口 UserDao,在类中定义增、删、改、查方法,并在每个方法编写输出语句,如下所示。
package com.wangxing.cglibdemo1.dao;
public interface UserDao {
public void add(); // 添加
public void update(); // 修改
public void delete(); // 删除
public void find(); // 查询
}
3.在com.wangxing.cglibdemo1.dao.impl包下创建接口 UserDao的实现类
package com.wangxing.cglibdemo1.dao.impl;
import com.wangxing.cglibdemo1.dao.UserDao;
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("添加User...");
}
@Override
public void update() {
System.out.println("修改User...");
}
@Override
public void delete() {
System.out.println("删除User...");
}
@Override
public void find() {
System.out.println("查询User...");
}
}
4.在com.wangxing.cglibdemo1.dao.cglibdemo创建切面类MyAspect
package com.wangxing.cglibdemo1.dao.cglibdemo;
public class MyAspect {
public void myBefore() {
System.out.println("方法执行之前");
}
public void myAfter() {
System.out.println("方法执行之后");
}
}
5.创建代理类 MyBeanFactory
package com.wangxing.cglibdemo1.dao.cglibdemo;
import com.wangxing.cglibdemo1.dao.UserDao;
import com.wangxing.cglibdemo1.dao.impl.UserDaoImpl;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyBeanFactory {
public static UserDao getBean() {
// 准备目标类
final UserDao userDao = new UserDaoImpl();
// 创建切面类实例
final MyAspect myAspect = new MyAspect();
// 生成代理类,CGLIB在运行时,生成指定对象的子类,增强
Enhancer enhancer = new Enhancer();
// 确定需要增强的类
enhancer.setSuperclass(userDao.getClass());
// 添加回调函数
enhancer.setCallback(new MethodInterceptor() {
// intercept 相当于 jdk invoke,前三个参数与 jdk invoke—致
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
myAspect.myBefore(); // 前增强
Object obj = method.invoke(userDao, args); // 目标方法执行
myAspect.myAfter(); // 后增强
return obj;
}
});
// 创建代理类
UserDao userDaoProxy = (UserDao) enhancer.create();
return userDaoProxy;
}
}
上述代码中,应用了 CGLIB 的核心类 Enhancer。在第 19 行代码调用了 Enhancer 类的 setSuperclass() 方法,确定目标对象。
第 21 行代码调用 setCallback() 方法添加回调函数;第 24 行代码的 intercept() 方法相当于 JDK 动态代理方式中的 invoke() 方法,该方法会在目标方法执行的前后,对切面类中的方法进行增强;第 33~34 行代码调用 Enhancer 类的 create() 方法创建代理类,最后将代理类返回。
6创建测试
@Test
public void test1() {
UserDao userDao=MyBeanFactory.getBean();
userDao.add();
}
上述代码中,调用 getBean() 方法时,依然获取的是 UserDao的代理对象,然后调用该对象的方法。
从图 2 的输出结果中可以看出,在调用目标类的方法前后,也成功调用了增强的代码,由此说明,使用 CGLIB 代理的方式同样实现了手动代理。
Spring通知类型及使用ProxyFactoryBean创建AOP代理
在《Spring JDK动态代理》和《Spring CGLlB动态代理》中,讲解了 AOP 手动代理的两种方式,下面通过讲解 Spring 的通知介绍 Spring 是如何创建 AOP 代理的。
Spring 通知类型
通过前面的学习可以知道,通知(Advice)其实就是对目标切入点进行增强的内容,Spring AOP 为通知(Advice)提供了 org.aopalliance.aop.Advice 接口。
Spring 通知按照在目标类方法的连接点位置,可以分为以下五种类型,如表 1 所示。
表 1 Spring 通知的 5 种类型 | |
名称 | 说明 |
org.springframework.aop.MethodBeforeAdvice(前置通知) | 在方法之前自动执行的通知称为前置通知,可以应用于权限管理等功能。 |
org.springframework.aop.AfterReturningAdvice(后置通知) | 在方法之后自动执行的通知称为后置通知,可以应用于关闭流、上传文件、删除临时文件等功能。 |
org.aopalliance.intercept.MethodInterceptor(环绕通知) | 在方法前后自动执行的通知称为环绕通知,可以应用于日志、事务管理等功能。 |
org.springframework.aop.ThrowsAdvice(异常通知) | 在方法抛出异常时自动执行的通知称为异常通知,可以应用于处理异常记录日志等功能。 |
org.springframework.aop.IntroductionInterceptor(引介通知) | 在目标类中添加一些新的方法和属性,可以应用于修改旧版本程序(增强类)。 |
Spring AOP的ProxyFactoryBean类实现代理
Spring 创建一个 AOP 代理的基本方法是使用
org.springframework.aop.framework.ProxyFactoryBean,这个类对应的切入点和通知提供了完整的控制能力,并可以生成指定的内容。
ProxyFactoryBean 类中的常用可配置属性如表 2 所示。
表 2 ProxyFactoryBean 的常用属性 | |
属性名称 | 描 述 |
target | 代理的目标对象 |
proxyInterfaces | 代理要实现的接口,如果有多个接口,则可以使用以下格式赋值: |
proxyTargetClass | 是否对类代理而不是接口,设置为 true 时,使用 CGLIB 代理 |
interceptorNames | 需要植入目标的 Advice |
singleton | 返回的代理是否为单例,默认为 true(返回单实例) |
optimize | 当设置为 true 时,强制使用 CGLIB |
在 Spring 通知中,环绕通知是一个非常典型的应用。
下面通过环绕通知的案例演示 Spring 创建 AOP 代理的过程。
1. 创建项目,导入依赖,完善项目结构
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
2. 创建业务接口以及实现类
package com.wangxing.proxyfactorybeandemo.service;
public interface PersonService {
public void insertPerson();
public void updatePerson();
public void deletePerson();
public void selectPerson();
}
package com.wangxing.proxyfactorybeandemo.service.impl;
import com.wangxing.proxyfactorybeandemo.service.PersonService;
public class PersonServiceImpl implements PersonService {
@Override
public void insertPerson() {
System.out.println("添加Person。。。。。。");
}
@Override
public void updatePerson() {
System.out.println("修改Person。。。。。。");
}
@Override
public void deletePerson() {
System.out.println("删除Person。。。。。。");
}
@Override
public void selectPerson() {
System.out.println("查询Person。。。。。。");
}
}
3. 创建切面类 MyAspect
package com.wangxing.proxyfactorybeandemo.myaspect;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("方法执行之前");
// 执行目标方法
Object obj = invocation.proceed();
System.out.println("方法执行之后");
return obj;
}
}
上述代码中,MyAspect 类实现了org.aopalliance.intercept.MethodInterceptor接口,并实现了接口的 invoke() 方法。MethodInterceptor 接口是 Spring AOP 的 JAR 包提供的,而 invoke() 方法用于确定目标方法 MethodInvocation,并告诉 Spring 要在目标方法前后执行哪些方法,这里为了演示效果在目标方法前后分别向控制台输出了相应语句。
3. 创建 Spring 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--创建目标对象-->
<bean id="personService" class="com.wangxing.proxyfactorybeandemo.service.impl.PersonServiceImpl"></bean>
<!-- 通知 advice -->
<bean id="myAspect" class="com.wangxing.proxyfactorybeandemo.myaspect.MyAspect" />
<!-- org.springframework.aop.framework.ProxyFactoryBean -->
<bean id="personServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--代理实现的接口 -->
<property name="proxyInterfaces" value="com.wangxing.proxyfactorybeandemo.service.PersonService"></property>
<!--代理的目标对象 -->
<property name="target" ref="personService"></property>
<!--用通知增强目标 -->
<property name="interceptorNames" value="myAspect"></property>
<!-- 如何生成代理,true:使用cglib; false :使用jdk动态代理 -->
<property name="proxyTargetClass" value="true"></property>
</bean>
</beans>
上述代码中,首先配置目标类和通知,然后使用 ProxyFactoryBean 类生成代理对象;第 14 行代码配置了代理实现的接口;第 16 行代码配置了代理的目标对象;第 18 行代码配置了需要植入目标的通知;当第 20 行代码中的 value 属性值为 true 时,表示使用 CGLIB 代理,属性值为 false 时,表示使用 JDK 动态代理。
4.创建测试
@Test
public void test1() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
PersonService personService = (PersonService) applicationContext.getBean("personServiceProxy");
personService.insertPerson();
}
5. 运行项目并查看结果
Spring使用AspectJ开发AOP:基于XML和基于Annotation
AspectJ 是一个基于 Java 语言的 AOP 框架,它扩展了 Java 语言。Spring 2.0 以后,新增了对 AspectJ 方式的支持,新版本的 Spring 框架,建议使用 AspectJ 方式开发 AOP。
使用 AspectJ 开发 AOP 通常有两种方式:
1. 基于 XML 的声明式。
2. 基于 Annotation 的声明式。
接下来将对这两种 AOP 的开发方式进行讲解。
基于XML的声明式
基于 XML 的声明式是指通过 Spring 配置文件的方式定义切面、切入点及声明通知,而所有的切面和通知都必须定义在 <aop:config> 元素中。
下面通过案例演示 Spring 中如何使用基于 XML 的声明式实现 AOP 的开发。
1. 创建项目,导入依赖包,完善结构
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
2. 创建业务接口以及实现类
package com.wangxing.proxyfactorybeandemo.service;
public interface PersonService {
public void insertPerson();
public void updatePerson();
public void deletePerson();
public void selectPerson();
}
package com.wangxing.proxyfactorybeandemo.service.impl;
import com.wangxing.proxyfactorybeandemo.service.PersonService;
public class PersonServiceImpl implements PersonService {
@Override
public void insertPerson() {
System.out.println("添加Person。。。。。。");
}
@Override
public void updatePerson() {
System.out.println("修改Person。。。。。。");
}
@Override
public void deletePerson() {
System.out.println("删除Person。。。。。。");
}
@Override
public void selectPerson() {
System.out.println("查询Person。。。。。。");
}
}
3. 创建切面类 MyAspect
package com.mengma.aspectj.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
//切面类
public class MyAspect {
// 前置通知
public void myBefore(JoinPoint joinPoint) {
System.out.print("前置通知,目标:");
System.out.print(joinPoint.getTarget() + "方法名称:");
System.out.println(joinPoint.getSignature().getName());
}
// 后置通知
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知,方法名称:" + joinPoint.getSignature().getName());
}
// 环绕通知
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("最终通知");
}
}
上述代码中,分别定义了几种不同的通知类型方法,在这些方法中,通过 JoinPoint 参数可以获得目标对象的类名、目标方法名和目标方法参数等。需要注意的是,环绕通知必须接收一个类型为 ProceedingJoinPoint 的参数,返回值必须是 Object 类型,且必须抛出异常。异常通知中可以传入 Throwable 类型的参数,用于输出异常信息。
3. 创建 Spring 配置文件
<?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.xsd">
<!--目标类 -->
<bean id="personService" class="com.wangxing.aopdemo1.service.impl.PersonServiceImpl" />
<!--切面类 -->
<bean id="myAspect" class="com.wangxing.aopdemo1.aspect.MyAspect"></bean>
<!--AOP 编程 -->
<aop:config>
<!--配置切面-->
<aop:aspect ref="myAspect">
<!--创建切入点-->
<aop:pointcut id="mypointcut1" expression="execution ( * com.wangxing.aopdemo1.service.impl.PersonServiceImpl.insertPerson(..))"/>
<aop:before method="myBefore" pointcut-ref="mypointcut1"></aop:before>
</aop:aspect>
<!--配置切面-->
<aop:aspect ref="myAspect">
<!--创建切入点-->
<aop:pointcut id="mypointcut2" expression="execution ( * com.wangxing.aopdemo1.service.impl.PersonServiceImpl.updatePerson(..))"/>
<aop:after-returning method="myAfterReturning"pointcut-ref="mypointcut2" returning="joinPoint" />
</aop:aspect>
<!--配置切面-->
<aop:aspect ref="myAspect">
<!--创建切入点-->
<aop:pointcut id="mypointcut3" expression="execution ( * com.wangxing.aopdemo1.service.impl.PersonServiceImpl.deletePerson(..))"/>
<aop:around method="myAround" pointcut-ref="mypointcut3" />
</aop:aspect>
<!--配置切面-->
<aop:aspect ref="myAspect">
<!--创建切入点-->
<aop:pointcut id="mypointcut4" expression="execution ( * com.wangxing.aopdemo1.service.impl.PersonServiceImpl.selectPerson(..))"/>
<!--作用通知-->
<!---->
<!--抛出通知:用于处理程序发生异常,可以接收当前方法产生的异常 -->
<!-- *注意:如果程序没有异常,则不会执行增强 -->
<!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
<aop:after-throwing method="myAfterThrowing"pointcut-ref="mypointcut4" throwing="e" />
<!--最终通知:无论程序发生任何事情,都将执行 -->
<!--<aop:after method="myAfter" pointcut-ref="myPointCut" />-->
</aop:aspect>
</aop:config>
</beans>
4. 创建测试
@Test
public void test1() {
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
PersonService personService=(PersonService)context.getBean("personService");
personService.insertPerson();
personService.updatePerson();
personService.deletePerson();
//personService.selectPerson();
}
5. 运行项目并查看结果
为了更好地演示异常通知,接下来在 PersonServiceImpl 类的 selectPerson() 方法中添加一行会抛出异常的代码,如“int i=1/0;”,重新运行测试方法,可以看到异常通知执行了,此时控制台的输出结果如图 2 所示。
图 2 运行结果
从图 1 和图 2 的输出结果中可以看出,基于 XML 声明式的 AOP 开发已经成功实现。
基于 Annotation 的声明式
在 Spring 中,尽管使用 XML 配置文件可以实现 AOP 开发,但是如果所有的相关的配置都集中在配置文件中,势必会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。
为此,AspectJ 框架为 AOP 开发提供了另一种开发方式——基于 Annotation 的声明式。AspectJ 允许使用注解定义切面、切入点和增强处理,而 Spring 框架则可以识别并根据这些注解生成 AOP 代理。
关于 Annotation 注解的介绍如表 1 所示。
表 1 Annotation 注解介绍 | |
名称 | 说明 |
@Aspect | 用于定义一个切面。 |
@Before | 用于定义前置通知,相当于 BeforeAdvice。 |
@AfterReturning | 用于定义后置通知,相当于 AfterReturningAdvice。 |
@Around | 用于定义环绕通知,相当于MethodInterceptor。 |
@AfterThrowing | 用于定义抛出通知,相当于ThrowAdvice。 |
@After | 用于定义最终final通知,不管是否异常,该通知都会执行。 |
@DeclareParents | 用于定义引介通知,相当于IntroductionInterceptor(不要求掌握)。 |
下面使用注解的方式重新实现《基于XML的声明式》部分的功能。
1. 创建切面类 MyAspect
在 src 目录下创建一个名为 com.mengma.aspectj.annotation 的包,在该包下创建一个切面类 MyAspect,如下所示。
package com.mengma.aspectj.annotation;
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 {
// 用于取代:<aop:pointcut
// expression="execution(*com.mengma.dao..*.*(..))" id="myPointCut"/>
// 要求:方法必须是private,没有值,名称自定义,没有参数
@Pointcut("execution(*com.mengma.dao..*.*(..))")
private void myPointCut() {
}
// 前置通知
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint) {
System.out.print("前置通知,目标:");
System.out.print(joinPoint.getTarget() + "方法名称:");
System.out.println(joinPoint.getSignature().getName());
}
// 后置通知
@AfterReturning(value = "myPointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知,方法名称:" + 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("最终通知");
}
}
上述代码中,第 13 行 @Aspect 注解用于声明这是一个切面类,该类作为组件使用,所以要添加 @Component 注解才能生效。第 19 行中 @Poincut 注解用于配置切入点,取代 XML 文件中配置切入点的代码。
在每个通知相应的方法上都添加了注解声明,并且将切入点方法名“myPointCut”作为参数传递给要执行的方法,如需其他参数(如异常通知的异常参数),可以根据代码提示传递相应的属性值。
2. 为目标类添加注解
在 com.mengma.dao.CustomerDaoImpl 目标类中添加注解 @Repository("customerDao")。
3. 创建Spring配置文件
在 com.mengma.aspectj.annotation 包下创建 applicationContext.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-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描含com.mengma包下的所有注解-->
<context:component-scan base-package="com.mengma"/>
<!-- 使切面开启自动代理 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
上述代码中,首先导入了 AOP 命名空间及其配套的约束,使切面类中的 @AspectJ 注解能够正常工作;第 13 行代码添加了扫描包,使注解生效。需要注意的是,这里还包括目标类 com.mengma.dao.CustomerDaoImpl 的注解,所以 base-package 的值为 com.mengma;第 15 行代码的作用是切面开启自动代理。
4. 创建测试类
在 com.mengma.aspectj.annotation 包下创建一个名为 AnnotationTest 的测试类,如下所示。
package com.mengma.aspectj.annotation;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mengma.dao.CustomerDao;
public class AnnotationTest {
@Test
public void test() {
String xmlPath = "com/mengma/aspectj/xml/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
xmlPath);
// 从spring容器获取实例
CustomerDao customerDao = (CustomerDao) applicationContext
.getBean("customerDao");
// 执行方法
customerDao.add();
}
}
5. 运行项目并查看结果
使用 JUnit 测试运行 test() 方法,运行成功后,控制台的输出结果如图 3 所示。
图 3 运行结果
删除 add() 方法中的“int i=1/0;”,重新运行 test() 方法,此时控制台的输出结果如图 4 所示。
图 4 运行结果
从图 3 和图 4 的输出结果中可以看出,已成功使用 Annotation 的方式实现了 AOP 开发。与其他方式相比,基于 Annotation 方式实现 AOP 的效果是最方便的方式,所以实际开发中推荐使用注解的方式。