前言:
前面我们简单介绍了Spring的IOC容器,其实,本篇要讲述的是Spring中另外一个常用的模块AOP。在看本篇之前,要确保已经有代理模式的基本知识,这样看起来才不会费劲!
1.AOP简介
AOP(面向切面编程)是对OOP中遇到的一些问题的补充,是OOP的扩展,在不改变原有代码的情况下增加新的功能,比如我们常见的日志功能,权限,异常处理,缓存等公共业务(即与软件的开发业务没有关联)。
举一个简单的例子:
public class UserService{
//添加用户的方法
public void add() {
//加一个输出日志
log();
//代码逻辑
}
public void delete() {
//加一个输出日志
log();
//代码逻辑
}
}
我们可以看到如果按照我们以前的编写代码的方式的话,每个方法都有写重复的代码,而且log()这个方法还不是我们主要的业务逻辑,这样编码的话充满耦合性,这时候我们就要利用Spring的AOP技术将log()这一块抽取出来
Spring的AOP底层实现
Spring的AOP底层用到了我们两种动态代理模式JDK的动态代理(针对实现了接口的类产生代理),Cglib的动态代理(针对没有实现接口的类产生代理,应用的是底层的字节码增强的技术,生成当前的子类对象)
2.AOP开发中的相关术语
-
关注点:重复代码就叫做关注点即公共业务。
-
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。
-
Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint(方法)进行拦截的定义。
-
Advice(通知/增强):所谓通知是指拦截到Joinpoint(方法)之后所要做的事情就是通知。通知分为前置通知、后置通知、异常通知、最终通知和环绕通知(切面要完成的功能)。
-
Aspect(切面):是切入点和通知的结合。
-
Target(目标对象):代理的目标对象(要增强的类)
-
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
-
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field。
我们还是用上面的例子解释一下这些术语: -
连接点:就是里面的三个方法
-
切入点:假设里面的add()方法加入了日志的功能,这个就add()方法就是切入点
-
通知/增强:比如增强UserService类里面的add方法,在add方法中添加了日志功能,这个日志功能就称为增强。
通知类型: -
前置通知:在增强的方法执行之前进行操作。
-
后置通知:在增强的方法执行之后进行操作。
-
环绕通知:在增强的方法执行之前和执行之后进行操作。
-
最终通知:增强了两个方法,执行第一个方法,执行第二个方法,在第二个方法执行之后进行操作。
也可理解为后置通知后面执行的通知或者无论目标方法是否出现异常,最终通知都会执行。
异常通知:程序出现异常之后执行的通知。 -
切面:假设有个日志功能,把日志功能加到add()方法上面的过程
-
目标对象:UserService
-
织入:把日志功能增加到目标对象(UserService)的过程
3.使用SpringAOP开发步骤(基于AspectJXML方式)
注意对切面的配置有两种方式:一种是传统的aop联盟制定的aop通知的配置,一种是spring支持的AspectJ框架的切面
image.png
1.引入相关的jar文件
spring-aop-3.2.5.RELEASE.jar 【spring3.2源码】
aopalliance.jar 【spring2.5源码/lib/aopalliance】
aspectjweaver.jar 【spring2.5源码/lib/aspectj】或【aspectj-1.8.2\lib】
aspectjrt.jar 【spring2.5源码/lib/aspectj】或【aspectj-1.8.2\lib】
2.bean.xml中引入aop名称空间
<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">
3.创建业务功能代码和公共代码
public class TestAop {
public void sayHello(String name) {
System.out.println("sayHello方法被执行");
}
}
public class Log {
//方法执行前
public void before() {
System.out.println("方法执行前" );
}
//方法执行后
public void after() {
System.out.println("方法执行后" );
}
}
4.配置bean.xml
<!--配置业务Bean-->
<bean id="testAop" class="test.TestAop"></bean>
<!--配置切面Bean-->
<bean id="log" class="test.Log"></bean>
<!--配置切面-->
<aop:config>
<!--定义切入表达式,要拦截什么方法-->
<aop:pointcut id="pointCut" expression="execution(* test.TestAop.*(..))"></aop:pointcut>
<!--指定切面是那个类-->
<aop:aspect ref="log">
<!--指定来拦截的时候执行切面类的哪些方法-->
<!-- 配置前置通知 -->
<aop:before method="before" pointcut-ref="pointCut" ></aop:before>
<!-- 配置后通知 -->
<aop:after method="after" pointcut-ref="pointCut" />
</aop:aspect>
</aop:config>
结果
image.png
切入点表达式
通过execution函数,可以定义切点的方法切入。
语法为:execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)。
例如:
- 匹配所有类的public方法:execution(public .(..))
- 匹配指定包下所有类的方法:execution(* cn.itcast.dao.*(..)),但不包含子包
- execution(* cn.itcast.dao..(..)),..表示包、子孙包下所有类。
- 匹配指定类所有方法:execution(* cn.itcast.service.UserService.*(..))
- 匹配实现特定接口的所有类的方法:execution(* cn.itcast.dao.GenericDAO+.*(..))
- 匹配所有save开头的方法:execution(* save*(..))
- 匹配所有类里面的所有的方法:execution(* .(..))
表达式有如下形式:
<!-- 【拦截所有public方法】 -->
<!--<aop:pointcut expression="execution(public * *(..))" id="pt"/>-->
<!-- 【拦截所有save开头的方法 】 -->
<!--<aop:pointcut expression="execution(* save*(..))" id="pt"/>-->
<!-- 【拦截指定类的指定方法, 拦截时候一定要定位到方法】 -->
<!--<aop:pointcut expression="execution(public * cn.itcast.g_pointcut.OrderDao.save(..))" id="pt"/>-->
<!-- 【拦截指定类的所有方法】 -->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.*(..))" id="pt"/>-->
<!-- 【拦截指定包,以及其自包下所有类的所有方法】 -->
<!--<aop:pointcut expression="execution(* cn..*.*(..))" id="pt"/>-->
<!-- 【多个表达式】 -->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) || execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) or execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<!-- 下面2个且关系的,没有意义 -->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) && execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) and execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<!-- 【取非值】 -->
<!--<aop:pointcut expression="!execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
4.使用注解的形式
在实际开发中,如果我们可以用注解形式的话,还是推荐使用注解的形式
1.在bean.xml中开启AOP注解方式
<!-- 开启aop注解方式 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
切面类
@Aspect//指定为切面类
public class Log {
//方法执行前
@Before("execution(* test.TestAop.*(..))")
public void before() {
System.out.println("方法执行前");
}
//方法执行后
@After("execution(* test.TestAop.*(..))")
public void after() {
System.out.println("方法执行后" );
}
}
image.png
优化:
@Aspect//指定为切面类
public class Log {
// 指定切入点表达式,拦截哪个类的哪些方法
// 指定切入点表达式,拦截哪个类的哪些方法
@Pointcut("execution(* test.TestAop.*(..))")
public void pt() {
}
//方法执行前
@Before ("pt()")
public void before() {
System.out.println("方法执行前");
}
//方法执行后
@After( "pt()")
public void after() {
System.out.println("方法执行后" );
}
}
网友评论