前言
什么是AOP?
软件中,有些行为对于大多数应用都是通用的,但却不是应用重点关注的问题。AOP可以将这些应用与这些通用的行为分离,解除耦合,让应用只关注自身的业务逻辑。而其他方面的问题由其他应用对象来处理。
一、AOP的相关术语
-
joinpoint(连接点)
类中可以被增强的方法,都可以被称为连接点 -
pointcut(切入点)
实际被增强的方法 -
advice(通知/增强)
指拦截到joinpoint之后可以做的事情称之为增强- 前置通知:在目标方法被调用之前调用通知功能
- 后置通知:在目标方法完成之后调用通知、此时不关心方法的输出是什么?
- 异常通知:在目标方法抛出异常后调用通知
- 环绕通知:通知包裹了被通知的方法,在通知方法调用之前和调用之后执行自定义的行为。
- 最终通知:在目标方法成功执行之后调用通知
-
Aspect(切面)
实现的交叉功能 系统级别的功能,比如日志、事务、权限(只是笼统的概念,类似于接口) -
Introduction(引入)
特殊的通知,对原有的对象添加新的方法或者属性、破坏封装。引入允许我们向现有的类添加新的属性和方法。 -
Target(目标对象)
被通知的对象(通知应用给谁) -
Weaving(织入)
织入是将切面应用到目标对象来创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入
编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器,AspectJ的织入编译器就是以这种方式织入切面的
类加载期:切面在目标类被加载到JVM时被织入,这种方式需要特殊的类加载器,它可以子啊目标类被引入应用之前增强该目标类的字节码
运行期:切面在应用运行的某一时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态的创建一个对象,Spring AOP就是以这种方式织入切面的。
- Proxy(代理)
将通知应用到目标对象后 创建对的对象
二、Spring 中AOP的实现
**AspectJ的execution()指示器 **
使用execution()指示器来配置切入点,execution() 指示器属于AspectJ的,所以需要在自己的项目中导入AspectJ的jar包。
execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
匹配所有类public方法 execution(public .(…))
匹配指定包下所有类方法 execution(* com.tulun.bean.(…)) (不包含子包)
execution( com.tulun.bean…(…)) (包含包、子包下所有类)
匹配指定类所有方法 execution( com.tulun.bean.Book.(…))
匹配实现特定接口所有类方法 execution( com.tulun.bean.Book+.(…))
匹配所有com开头的方法 execution( com*(…))
AspectJ的 within()指示器
我们可以使用within()指示器来限制匹配
//within(com.tulun.dao.*) 是指com.tulun.dao包下任意类的方法被调用时
execution(* com.tulun.bean.User.show(..))&& within(com.tulun.dao.*)
这里使用 && 操作符把execution()和within()指示器连接在一起形成and 关系(切点必须匹配所有的指示器),类似的我们也可以用or、not。
在XML中&&有特殊的含义,所以在XML配置时,也可以使用and 代替&&,同样的, | | 和!也可以使用。
Spring 的bean()指示器
bean()指示器允许我们在切点表达式中使用Bean 的ID来标识Bean,使用Bean的ID或者Bean 的名字作为参数来限制切点只匹配特定的Bean
execution(* com.tulun.bean.User.show(..)) && bean(user)
还可以使用非操作作为除了指定ID的Bean 以外的其他Bean 应用通知。
1.通过XML配置实现AOP
创建User 类
public class User {
private int id;
public void show(){
System.out.println("用户");
}
}
创建增强类
public class Log {
public void writeLog() {
System.out.println("写日志");
}
public void after(){
System.out.println("After");
}
public void Around(ProceedingJoinPoint joinPoint){
System.out.println("Around Before");
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
//切入点方法
System.out.println("Around After");
}
public void execption(){
System.out.println("AfterThrowing");
}
public void fin(){
System.out.println("AfterReturning");
}
}
创建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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
创建bean
<!--创建对象-->
<bean id="user" class="com.tulun.bean.User3"/>
<bean id="log" class="com.tulun.bean.Log3"/>
开启AOP操作
<!--开启AOP操作-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
配置AOP的操作
<!--配置aop的操作-->
<aop:config>
<!--配置切入点(实际被增强的方法),使用表达式进行配置-->
<aop:pointcut id="pointcut1" expression="execution(* com.tulun.bean.User.show(..))"></aop:pointcut>
<!--配置切面:将增强用到方法上-->
<aop:aspect ref="log">
<!--前置通知-->
<aop:before method="writeLog" pointcut-ref="pointcut1"></aop:before>
<!--环绕通知-->
<aop:around method="Around" pointcut-ref="pointcut1"></aop:around>
<!--后置通知-->
<aop:after method="after" pointcut-ref="pointcut1"></aop:after>
<!--异常通知-->
<aop:after-throwing method="execption" pointcut-ref="pointcut1"></aop:after-throwing>
<!--最终通知-->
<aop:after-returning method="fin" pointcut-ref="pointcut1"></aop:after-returning>
</aop:aspect>
</aop:config>
aop配置必须在<aop:config> 的标签里。
- <aop:pointcut />:配置切入点
- <aop:aspect />:配置切面
ref 表示我们要配置哪个Bean 为切面 - <aop:before />:前置通知
- <aop:after />:后置通知
- <aop:around />:环绕通知
- <aop:after-throwing />:异常通知
- <aop:after-returning />:最终通知
在每种类型的通知里,method表示要调用切面类的哪个方法作为通知,pointcut-ref表示切点,这是是我们配置好的某个切点的ID。
为通知传递参数
有时候通知的方法也需要参数
给User 增加一个name 的属性,然后增加一个方法
private String name;
public void sname(String name){
this.name = name;
}
//Log 增加方法
public void testargs(String name){
System.out.println("name = "+name);
}
配置一个新的切点
<aop:pointcut id="pointcut2" expression="execution(* com.tulun.bean.User.sname(String)) and args(name)"></aop:pointcut>
//对切点进行增强
<aop:before method="testargs" arg-names="name" pointcut-ref="pointcut2"></aop:before>
切点标识了sname()方法,指定了String 参数,然后再args 参数中标识了将name 作为参数。在<aop:before>元素引用了name 参数,标识将该参数传递给Log的testarg()方法。
2.通过注解实现AOP
在增强类上增加注解@Aspect
@Aspect
public class Log {
//……
}
增加通知的注解,作用在方法上,
- @Before
- @After
- @Around
- @AfterThrowing
- @AfterReturning
以上注解都是作用于方法上,该方法为切入点,即要增加功能的方法。@Before、@After、@Around等的value的值是要使用execution的函数表达式, execution函数的使用和AOP配置中使用的作用及效果是一致的
@Before(value = "execution(* com.tulun.bean.User.show(..))")
public void writeLog() {
System.out.println("写日志");
}
@After(value = "execution(* com.tulun.bean.User.show(..))")
public void after(){
System.out.println("After");
}
@Around(value = "execution(* com.tulun.bean.User.show(..))")
public void Around(ProceedingJoinPoint joinPoint){
System.out.println("Around Before");
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
//切入点方法
System.out.println("Around After");
}
@AfterThrowing(value = "execution(* com.tulun.bean.User.show(..))")
public void execption(){
System.out.println("AfterThrowing");
}
@AfterReturning(value = "execution(* com.tulun.bean.User.show(..))")
public void fin(){
System.out.println("AfterReturning");
}
参数传递
@Before(value = "execution(* com.tulun.bean.User.sname(..)) && args(name)")
public void testargs(String name){
System.out.println("name = "+name);
}
或者这样
@Pointcut("execution(* com.tulun.bean.User.sname(..)) && args(name)")
public void point(String name){
}
@Before("point(name)")
public void getname(String name){
System.out.println("getname");
}
三、总结
通过这两个小例子,我们可以这样解释一下AOP。
这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。AOP是Spring提供的关键特性之一。AOP即面向切面编程,是OOP编程的有效补充。使用AOP技术,可以将一些系统性相关的编程工作,独立提取出来,独立实现,然后通过切面切入进系统。从而避免了在业务逻辑的代码中混入很多的系统相关的逻辑——比如权限管理,事物管理,日志记录等等。这些系统性的编程工作都可以独立编码实现,然后通过AOP技术切入进系统即可。从而达到了 将不同的关注点分离出来的效果。