这篇文章讲述的是Spring框架中的AOP原理以及相关的一些例子,如有错误或不当之处还望各位大神批评指正
什么是AOP?
基本概念
- AOP是一种编程思想,即面向切面编程,是对传统的面向对象编程思想(OOP)的补充
AOP术语
- 切面Aspect:一个模块具有一组提供横切需求的 APIs
- 通知Advice:这是实际行动之前或之后执行的方法
- 目标对象Target object:被通知的对象
- 代理proxy:向目标对象应用通知后生成的对象
- 连接点Join point:程序执行的某个特定位置
- 切点Pointcut: 这是一组一个或多个连接点,通知应该被执行
- Introduction:引用允许你添加新方法或属性到现有的类中
- Weaving: Weaving 把方面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时,类加载时和运行时完成
AOP编程的好处
- AOP的作用在与分离系统中各个关注点,将核心关注点和横切关注点分离开来
AOP的简单例子
业务要求
假如有一个Student接口及其实现类如下
/*Student接口*/
package com.cn.cmc.aop;
/** * @author 叶清逸 * @date 2018年7月17日下午9:25:59 * @version 1.0 * @project com.cn.cmc.aop */
public interface Student {
//添加学生
public void addStudent() ;
//删除学生
public void delStudent();
}
/*Student实现类*/
package com.cn.cmc.aop;
/** * @author 叶清逸 * @date 2018年7月17日下午9:28:21 * @version 1.0 * @project com.cn.cmc.aop */
public class StudentImpl implements Student{
@Override
public void addStudent() {
System.out.println("添加学生");
}
@Override
public void delStudent() {
System.out.println("删除学生");
}
}
现有业务要求:在执行Student的方法前后打印通知
传统的实现方法
传统的解决办法是直接在实现的业务代码中添加通知
package com.cn.cmc.aop;
/** * @author 叶清逸 * @date 2018年7月17日下午9:28:21 * @version 1.0 * @project com.cn.cmc.aop */
public class StudentImpl implements Student{
@Override
public void addStudent() {
//执行前通知
System.out.println("方法addStudent开始执行");
//业务代码
System.out.println("添加学生");
//执行后通知
System.out.println("方法addStudent执行完毕");
}
@Override
public void delStudent() {
//执行前通知
System.out.println("方法delStudent开始执行");
//业务代码
System.out.println("删除学生");
//执行后通知
System.out.println("方法delStudent执行完毕");
}
}
问题:添加删除和改动时需要改动大量代码,且不便于维护
动态代理的实现方法
可以使用动态代理来实现相关的业务逻辑
- 编写代理类
package com.cn.cmc.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/** * @author 叶清逸 * @date 2018年7月17日下午9:31:16 * @version 1.0 * @project com.cn.cmc.aop */
public class StudentProxy {
private Student student ;
public StudentProxy(Student student) {
this.student = student;
}
@SuppressWarnings("unchecked")
public Proxy getProxy(){
//类加载器用于加载Class
ClassLoader loader = ClassLoader.getSystemClassLoader() ;
//代理类中实现的方法
Class<Student> [] interfaces = new Class[]{Student.class} ;
//当调用代理类时,实际执行的方法
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法"+method.getName()+"开始执行");
if(method.getName().equals("addStudent")){
student.addStudent();
}else if(method.getName().equals("delStudent")){
student.delStudent();
}
System.out.println("方法"+method.getName()+"执行结束");
return proxy;
}
};
//初始化代理类
Proxy proxy = (Proxy) Proxy.newProxyInstance(loader, interfaces, handler) ;
return proxy ;
}
}
- 使用代理
//要代理类的接口
Student student = new StudentImpl() ;
//获取代理类
Student proxy = (Student) new StudentProxy(student).getProxy();
//执行方法
proxy.addStudent();
proxy.delStudent();
SpringAOP的实现方法
- 通知的切面类
package com.cn.cmc.aop;
/** * * @author 叶清逸 * @date 2018年7月18日下午1:59:04 * @version 1.0 * @project com.cn.cmc.aop */
public class LoggingAspect {
/** * 前置通知Advice */
public void beforeMethod(){
System.out.println("前置通知");
}
/** * 前置通知Advice */
public void afterMethod(){
System.out.println("后置通知");
}
}
- 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" xmlns:context="http://www.springframework.org/schema/context" 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.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<!-- 配置切面的bean -->
<bean id="logging" class="com.cn.cmc.aop.LoggingAspect"></bean>
<!-- 配置bean -->
<bean id="student" class="com.cn.cmc.aop.StudentImpl"></bean>
<!-- 配置aop aop:config标签:表示aop的配置 aop:pointcut标签:配置切点表达式 expression属性:切点的aspectJ表达式 id属性:切点的唯一标识 aop:aspect标签:配置切面及通知 id属性:该切面的唯一标识 ref属性:该切面关联的通知类 order属性:切面的优先级,值越小优先级越大 aop:before标签:表示前置通知 method属性:表示前置通知对应的方法 pointcut-ref:切点表达式 aop:after标签:表示后置通知 method属性:表示前置通知对应的方法 pointcut-ref:切点表达式 -->
<aop:config>
<aop:pointcut expression="execution(* com.cn.cmc.aop.Student.*() )" id="pointcut"/>
<aop:aspect ref="logging" order="1">
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
- 运行
public class Test {
public static void main(String[] args) {
ApplicationContext context = new FileSystemXmlApplicationContext("config/beans-aop.xml") ;
Student student = (Student) context.getBean("student") ;
student.addStudent();
student.delStudent();
}
}
切面的优先级
通过aop:aspect的order属性可以配置切面的优先级,值越小优先级越大
AOP的通知类型
Spring中AOP的通知类型包括:
- 前置通知:业务接口方法代码执行前执行
- 后置通知:业务接口方法代码执行后执行
- 返回通知:业务接口方法代码返回时执行(可以取得返回值)
- 异常通知:业务接口方法代码异常时执行
- 环绕通知:类似于执行动态代理的全过程
通知位置的理解代码
/*动态代理*/
//当调用代理类时,实际执行的方法
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行业务代码
try{
/*前置通知执行位置*/
/*执行业务代码*/
student.addStudent();
/*返回通知执行位置*/
}catch(Exception e){
/*异常通知执行位置*/
}
/*后置通知执行位置*/
return proxy;
}
};
前置通知
业务接口方法代码执行前执行
- xml中配置
<!--aop:before标签:表示前置通知 method属性:表示前置通知对应的方法 pointcut-ref:切点表达式 -->
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
- 通知代码
/** * 前置通知Advice * JoinPoint:获取方法执行细节 */
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName() ;
System.out.println("方法"+methodName+"开始执行");
}
后置通知
业务接口方法代码执行后执行,发生异常时同样执行
- xml中配置
<!--aop:after标签:表示前置通知 method属性:表示前置通知对应的方法 pointcut-ref:切点表达式 -->
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
- 通知代码
/** * 后置通知Advice * JoinPoint:获取方法执行细节 */
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName() ;
System.out.println("方法"+methodName+"执行结束");
}
返回通知
业务接口方法代码返回后执行
– xml
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
- 通知代码
/** * 返回通知afterReturning * 可以获取返回值 */
public void afterReturning(JoinPoint joinPoint ,Object result){
String methodName = joinPoint.getSignature().getName() ;
System.out.println("方法"+methodName+"执行结束"+"返回值:"+result);
}
异常通知
业务接口方法代码抛出异常时执行
- xml
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
- 通知代码
/** * 异常通知afterThrowing */
public void afterThrowing(JoinPoint joinPoint ,Exception e){
String methodName = joinPoint.getSignature().getName() ;
System.out.println("方法"+methodName+"执行结束"+"异常:"+e);
}
环绕通知
类似于执行动态代理的全过程
- xml
<aop:around method="aroundMethod" pointcut-ref="pointcut"/>
- 通知代码
/** * 环绕通知aroundMethod * 环绕通知需要携带ProceedingJoinPoint参数 */
public Object aroundMethod(ProceedingJoinPoint pjp){
System.out.println("环绕通知");
return 1000 ;
}