一、介绍
在传统的业务处理代码中,通常都会进行事务处理、日志记录等操作。虽然使用OOP可以通过组合或者继承的方式来达到代码的重用,但如果要实现某个功能(如日志记录),同样的代码仍然会分散到各个方法中。这样,如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。这不但增加了开发人员的工作量,而且提高了代码的出错率
AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。这种采用横向抽取机制的方式,采用传统的OOP思想显然是无法办到的,因为OOP只能实现父子关系的纵向的重用。虽然AOP是一种新的编程思想,但却不是OOP的替代品,它只是OOP的延伸和补充
二、 AOP术语
- Advice(通知/增强处理):AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法
- Target Object(目标对象):指所有被通知的对象,也被称为被增强对象。如果AOP框架采用的是动态的AOP实现,那么该对象就是一个被代理对象
- Proxy(代理):将通知应用到目标对象之后,被动态创建的对象
- Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程
三、AOP介绍
3.1 什么是AOP ?
- AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来
- 在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方
3.2 Spring通知类型
前置通知、后置通知、环绕通知、异常抛出通知、引介通知
-
org.springframework.aop.MethodBeforeAdvice(前置通知)
在目标方法执行前实施增强,可以应用于权限管理等功能 -
org.springframework.aop.AfterReturningAdvice(后置通知)
在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能 -
org.aopalliance.intercept.MethodInterceptor(环绕通知)
在目标方法执行前后实施增强,可以应用于日志、事务管理等功能 -
org.springframework.aop.ThrowsAdvice(异常抛出通知)
在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能 -
org.springframework.aop.IntroductionInterceptor(引介通知)
在目标类中添加一些新的方法和属性,可以应用于修改老版本程序
四、面向切面编程
4.1 面向切面AspectJ
- 自定义的切面
public class MyAspect {
// 前置通知
public void beforeProxy(JoinPoint joinPoint) {
System.out.print("前置通知: 模拟执行权限检查, ");
System.out.print("目标类是: " + joinPoint.getTarget());
System.out.println(", 被织入的方法: " + joinPoint.getSignature().getName());
}
// 后置通知
public void afterReturningProxy(JoinPoint joinPoint) {
System.out.print("后置通知:模拟记录日志...");
System.out.print("目标类是:" + joinPoint.getTarget());
System.out.println(", 被织入的目标方法: " + joinPoint.getSignature().getName());
}
// ProceedingJoinPoint是 JoinPoint的子接口, 表示可以执行的目标方法
// 使用proceedingJoinPoint为参数,必须返回一个对象 Object
// 必须接收一个参数,类型为 ProceedingJoinPoint
// 必须抛出异常 throws Throwable
public Object aroundProxy(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知开始执行, 模拟开启事务: ");
Object obj = proceedingJoinPoint.proceed();
System.out.println("环绕通知结束, 模拟关闭事务...");
return obj;
}
public void afterThrowingProxy(JoinPoint joinPoint, Throwable exception) {
System.out.println("抛出异常: " + exception.getMessage());
}
// 最终通知
public void afterProxy() {
System.out.println("最终通知,结束后释放资源... ");
}
}
- 编写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-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 1 目标类 -->
<bean id="userDAO" class="com.hlq.AspectJ.UserDAOImpl" />
<!-- 2 切面 -->
<bean id="myAspect" class="com.hlq.AspectJ.MyAspect" />
<!-- 3 aop编程 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 3.1 配置切入点,通知最后增强哪些方法 -->
<aop:pointcut
expression="execution(* com.hlq.AspectJ.*.add*(..))"
id="addPointCut" />
<!-- 配置另一个切入点 -->
<aop:pointcut expression="execution(* com.hlq.AspectJ.*.delete*(..))" id="deletePointCut"/>
<!-- 3.2 关联通知Advice和切入点pointCut -->
<!-- 3.2.1 前置通知 -->
<aop:before method="beforeProxy" pointcut-ref="addPointCut" />
<!-- 3.2.2 后置通知,在方法返回之后执行,就可以获得返回值 returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
<aop:after-returning method="afterReturningProxy"
pointcut-ref="deletePointCut" returning="returnVal" />
<!-- 3.2.3 环绕通知 -->
<aop:around method="aroundProxy" pointcut-ref="addPointCut" />
<!-- 3.2.4 抛出通知:用于处理程序发生异常-->
<!-- * 注意:如果程序没有异常,将不会执行增强 -->
<!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
<aop:after-throwing method="afterThrowingProxy"
pointcut-ref="addPointCut" throwing="exception" />
<!-- 3.2.5 最终通知:无论程序发生任何事情,都将执行 -->
<aop:after method="afterProxy" pointcut-ref="addPointCut" />
</aop:aspect>
</aop:config>
</beans>
- 测试
public class UserDAOImpl implements UserDAO {
public void addUser() {
System.out.println("UserDAO addUser is called... ");
}
public void deleteUser() {
System.out.println("UserDAO deleteUser is called...");
}
public UserDAOImpl() {
super();
}
}
public class AspectTest {
public static void main(String[] args) {
String xmlPath = "com/hlq/AspectJ/aspectConf.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserDAO userDAO = (UserDAO)applicationContext.getBean("userDAO");
userDAO.addUser();
userDAO.deleteUser();
}
}
前置通知: 模拟执行权限检查, 目标类是: com.hlq.AspectJ.UserDAOImpl@4686afc2, 被织入的方法: addUser
环绕通知开始执行, 模拟开启事务:
UserDAO addUser is called...
最终通知,结束后释放资源...
环绕通知结束, 模拟关闭事务...
UserDAO deleteUser is called...
后置通知:模拟记录日志...目标类是:com.hlq.AspectJ.UserDAOImpl@4686afc2, 被织入的目标方法: deleteUser
4.2 注解式的Aspect
- 定义注解切面
@Aspect
@Component
public class AspectJ {
// 定义切入点表达式
@Pointcut("execution(* com.hlq.annotationAspect.*.add*(..))")
// 使用一个返回值为void、方法体为空的方法来命名切入点
private void addPointCut() {
}
@Pointcut("execution(* com.hlq.annotationAspect.*.delete*(..))")
public void deletePointCut() {
}
// 前置通知
@Before("addPointCut()")
public void beforeProxy(JoinPoint joinPoint) {
System.out.print("前置通知: 模拟执行权限检查, ");
System.out.print("目标类是: " + joinPoint.getTarget());
System.out.println(", 被织入的方法: " + joinPoint.getSignature().getName());
}
// 后置通知
@AfterReturning("deletePointCut()")
public void afterReturningProxy(JoinPoint joinPoint) {
System.out.print("后置通知:模拟记录日志...");
System.out.print("目标类是:" + joinPoint.getTarget());
System.out.println(", 被织入的目标方法: " + joinPoint.getSignature().getName());
}
// // ProceedingJoinPoint是 JoinPoint的子接口, 表示可以执行的目标方法
// // 使用proceedingJoinPoint为参数,必须返回一个对象 Object
// // 必须接收一个参数,类型为 ProceedingJoinPoint
// // 必须抛出异常 throws Throwable
// @Around("myPointCut()")
// public Object aroundProxy(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//
// System.out.println("环绕通知开始执行, 模拟开启事务: ");
//
// Object obj = proceedingJoinPoint.proceed();
//
// System.out.println("环绕通知结束, 模拟关闭事务...");
// return obj;
// }
//
// @AfterThrowing(value="myPointCut()", throwing="exception")
// public void afterThrowingProxy(JoinPoint joinPoint, Throwable exception) {
// System.out.println("抛出异常: " + exception.getMessage());
// }
//
// // 最终通知
// @After("myPointCut()")
// public void afterProxy() {
// System.out.println("最终通知,结束后释放资源... ");
// }
}
- 配置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.hlq.annotationAspect" />
<!-- 启动基于注解的声明式AspectJ支持 -->
<aop:aspectj-autoproxy />
</beans>
- 编写任意测试类
@Repository("userDAO")
public class UserDAOImpl implements UserDAO {
public void addUser() {
System.out.println("UserDAO addUser is called... ");
}
public void deleteUser() {
System.out.println("UserDAO deleteUser is called...");
}
}
public class AspectTest {
public static void main(String[] args) {
String xmlPath = "com/hlq/annotationAspect/aspectConf.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserDAO userDAO = (UserDAO)applicationContext.getBean("userDAO");
userDAO.addUser();
userDAO.deleteUser();
}
}
前置通知: 模拟执行权限检查, 目标类是: com.hlq.annotationAspect.UserDAOImpl@56dc1551, 被织入的方法: addUser
UserDAO addUser is called...
UserDAO deleteUser is called...
后置通知:模拟记录日志...目标类是:com.hlq.annotationAspect.UserDAOImpl@56dc1551, 被织入的目标方法: deleteUser