继续前一篇
这次来认识什么是AOP。。
AOP:Aspect-Oriented Programming 即面向切面编程,它是面向对象编程(OOP)的一种补充。
传统的业务处理代码中,通常都会进行事务处理,日志记录等操作。虽然使用OOP可以通过组合或者继承的方法来达到代码的重用,但如果要实现某个功能,同样的代码仍然会分散到各个方法中,如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。为了解决这一问题,AOP思想随之产生。
1. AOP术语
术语 | 解释 |
---|---|
Aspect(切面) | 指封装的用于横向插入系统功能的类 |
Joinpoint(连接点) | 在程序执行过程中的某个连接点 |
Pointcut(切入点) | 指切面与程序流程的交叉点 |
Advice(通知/增强处理) | AOP框架在特定的切入点执行的增强处理 |
Target Object(目标对象) | 指所有被通知的对象,也称为增强对象 |
Proxy(代理) | 将通知应用到目标对象之后,被动态创建的对象 |
Weaving(织入) | 将切面代码插入到目标对象上,从而产生代理对象的过程 |
2. AspectJ开发
AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。使用AspectJ实现AOP有两种方式:
- 基于XML的声明式AspectJ
- 基于注解的声明式AspectJ
2.1 基于XML的声明式AspectJ
指通过XML文件来定义切面,切入点及通知,所有的切面,切入点和通知都必须定义在<aop:config>
元素内。
<aop:config>
的子元素
子元素 | 描述 |
---|---|
<aop:pointcut> | 配置全局切入点 |
<aop:advisor> | 配置通知器 |
<aop:aspect> | 配置切面 |
<aop:aspect>
的子元素
子元素 | 描述 |
---|---|
<aop:pointcut> | 配置全局切入点 |
<aop:before> | 配置前置通知 |
<aop:after-returning> | 配置后置通知 |
<aop:around> | 配置环绕通知 |
<aop:after-throwing> | 配置异常通知 |
<aop:after> | 配置最终通知 |
<aop:declare-parents> | 配置引介通知 |
Spring配置文件中的<beans>
元素下可以包含对个<aop:config>
元素,一个<aop:config>
元素中也可以包含属性和子元素,其子元素如上所示<aop:pointcut>
,<aop:advisor>
,<aop:aspect>
,配置时,这3个元素必须按照此顺序来定义
接下来通过例子来演示(基于XML的声明式AspectJ):
- 创建Web项目,导入基本包和AspectJ框架相关的jar包
- spring-aop-4.3.6.RELEASE.jar:是Spring为AOP提供的实现包,在Spring中的ibs中已经提供。
- aopalliance-1.0.jar:是AOP提供的规范包,该jar可通过https://mvnrepository.com/artifact/aopalliance/下载
- spring-aspects-4.3.6.RELEASE.jar:Spring为AspectJ提供的实现,在Spring中的ibs中已经提供。
- aspectjweaver-1.8.10.jar:是AspectJ框架所提供的规范,可通过https://mvnrepository.com/artifact/org.aspectj/aspectjweaver下载
访问会有点慢,其实网站就是maven项目管理工具
- 创建包(我创建的包com.xhh.dao),包中的类为目标类
UserDao.java
package com.xhh.dao;
public interface UserDao {
public void addUser();
public void deleteUser();
}
UserDaoImpl
package com.xhh.dao;
import org.springframework.stereotype.Repository;
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
- 创建包(我创建的包com.xhh.aspectJ.xml),在包中创建切面类MyAspect,在类中定义不同类型的通知。
- Spring中的通知类型
- 环绕通知:应用于日志,事务管理等功能
- 前置通知:应用于权限管理等功能
- 后置通知:应用于关闭流,上传文件,删除临时文件等功能
- 异常通知:应用于处理异常记录日志等功能
- 引介通知:应用于修改老版本程序
MyAspect.java
package com.xhh.aspectJ.xml;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
// 前置通知
public void myBefore() {
System.out.println("前置通知:模拟执行权限检查...");
}
// 后置通知
public void myAfterReturning() {
System.out.println("后置通知:模拟记录日志...");
}
// 环绕通知
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 开始
System.out.println("环绕开始,模拟开启事务");
// 执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
// 结束
System.out.println("环绕结束,模拟关闭事务");
return obj;
}
// 异常通知
public void myAfterThrowing(Throwable e) {
System.out.println("异常通知:" + e.getMessage());
}
// 最终通知
public void myAfter() {
System.out.println("最终通知:模拟方法结束释放资源");
}
}
需要注意:环绕通知必须接受一个类型为ProceedingJoinPoint的参数,返回值也必须是Object类型,且必须抛出异常。
- 创建配置文件applicationContext.xml
需要添加aop标签规范声明地址
<?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">
<!-- 目标类 -->
<bean id="userDao" class="com.xhh.dao.UserDaoImpl"></bean>
<!-- 切面类 -->
<bean id="myAspect" class="com.xhh.aspectJ.xml.MyAspect"></bean>
<!-- aop编程 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 配置切入点 -->
<aop:pointcut
expression="execution(* com.xhh.dao.*.*(..))"
id="myPointCut" />
<!-- 前置通知 -->
<aop:before method="myBefore" pointcut-ref="myPointCut" />
<!-- 后置通知 -->
<aop:after-returning method="myAfterReturning"
pointcut-ref="myPointCut" returning="returnVal" />
<!-- 环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointCut" />
<!-- 抛出异常通知 -->
<aop:after-throwing method="myAfterThrowing"
pointcut-ref="myPointCut" throwing="e" />
<!-- 最终通知 -->
<aop:after method="myAfter" pointcut-ref="myPointCut" />
</aop:aspect>
</aop:config>
</beans>
对配置文件讲解:
- 配置切面:使用
<aop:aspect>
元素,该属性会将一个已定义好的Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的Spring Bean,如上代码中id为myAspect的Bean。定义完成后使用ref属性引用该Bean。
<aop:aspect>
属性 | 描述 |
---|---|
id | 用于定义该切面的唯一标识名称 |
ref | 用于引用普通的Spring Bean |
- 配置切入点:通过
<aop:pointcut>
元素来定义的。当<aop:pointcut>
作为<aop:config>
的子元素定义时,表示该切入点是全局切入点,当<aop:pointcut>
作为<aop:aspect>
的子元素定义时,表示该切入点自对当前切面有效。
<aop:pointcut>
属性 | 描述 |
---|---|
id | 用于指定切入点的唯一标识名称 |
expression | 用于指定切入点关联的切入点表达式 |
上述代码中,execution(* com.xhh.dao.*.*(..))
就是定义的切入点表达式,意思是匹配com.xhh.dao包中任意类的任意方法的执行。
-
execution()为表达式的主体,第一个
*
表示的是返回类型,使用*
代表所有类型
第二个*
表示的是类名,使用*
代表所有类
第三个*
表示的是方法名,使用*
代表所有方法
后面(…)表示方法的参数,(…)表示任意参数 -
配置通知:通过
<aop:pointcut>
的子元素配置了5中常用通知,使用时可以指定一些属性。
<aop:pointcut>
的子元素
属性 | 描述 |
---|---|
pointcut | 指定一个切入点表达式 |
pointcut-ref | 指定一个已经存在的切入点名称 |
method | 指定一个方法名 |
throwing | 指定一个形参名,只对after-throwing 有效 |
returning | 指定一个形参名,只对after-returning 有效 |
- 创建测试类AspectXmlTest.java
AspectXmlTest.java
package com.xhh.aspectJ.xml;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.xhh.dao.UserDao;
public class AspectXmlTest {
public static void main(String[] args) {
ApplicationContext context = new
ClassPathXmlApplicationContext("com/xhh/aspectJ/xml/applicationContext.xml");
UserDao userDao = (UserDao) context.getBean("userDao");
userDao.addUser();
}
}
结果:
2.2 基于注解的声明式AspectJ
在Spring配置文件中配置了大量的代码信息,过于臃肿,还是注解好用。
AspectJ注解介绍
注解名称 | 描述 |
---|---|
@Aspect | 用于定义一个切面 |
@Pointcut | 用于定义切入点表达式 |
@Before | 用于定义前置通知 |
@AfterReturning | 用于定义后置通知 |
@Around | 用于定义环绕通知 |
@AfterThrowing | 用于定义异常通知来处理程序中未处理的异常 |
@After | 用于定义最终通知 |
- 创建包(我创建的包com.xhh.aspectJ.annotation),复制MyAspect到该包下,并添加相应注解。
MyAspect.java
package com.xhh.aspectJ.annotation;
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 {
// 定义切入点表达式,使用一个返回值为void,方法体为空的方法来命名切入点
@Pointcut("execution(* com.xhh.dao.*.*(..))")
private void myPointCut() {}
// 前置通知
@Before("myPointCut()")
public void myBefore() {
System.out.println("前置通知:模拟执行权限检查...");
}
// 后置通知
@AfterReturning("myPointCut()")
public void myAfterReturning() {
System.out.println("后置通知:模拟记录日志...");
}
// 环绕通知
@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(Throwable e) {
System.out.println("异常通知:" + e.getMessage());
}
// 最终通知
@After("myPointCut()")
public void myAfter() {
System.out.println("最终通知:模拟方法结束释放资源");
}
}
注意:@Aspect定义了一个切面类,由于该类在Spring中是作为Bean使用的,所以还需要添加@Component。
- 在目标类com.xhh.dao.UserDaoImpl添加注解:
@Repository("userDao")
- 创建配置文件,记得添加相应标签规范context
<?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.xhh"></context:component-scan>
<!-- 启动基于注解的声明式AspectJ支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
- 创建测试类
AnnotationTest.java
package com.xhh.aspectJ.annotation;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.xhh.dao.UserDao;
public class AnnotationTest {
public static void main(String[] args) {
ApplicationContext context = new
ClassPathXmlApplicationContext("com/xhh/aspectJ/annotation/applicationContext.xml");
UserDao userDao = (UserDao) context.getBean("userDao");
userDao.addUser();
}
}
完成。