学习资料:http://how2j.cn/k/spring/spring-aop/89.html
又出现了新名词AOP, Aspect Oriented Program 面向切面编程,其实也是Spring的两大核心之一。另一个就是之前提到的控制反转IOC
PS:其实我一直不懂这些翻译为什么要弄得这么高大上,好好说人话不好吗。。。比如控制反转改叫自动生成对象之类的。。。
面向切面AOP:把一个程序中的一些周边功能(如日志、性能统计)独立开,这些功能不会干涉别的功能运行,专心完成自己的业务逻辑,独立开发完成,再在需要的时候将它们与核心业务编制在一起,这样的编程方式便称为AOP
PS:我自己瞎总结的定义
新建一个业务类ProdudcService.java,假装它有在忙什么事情
package com.how2java.service;
public class ProductService {
public void doSomeService(){
System.out.println("doSomeService");
}
}
修改一下测试用例TestSpring
package com.how2java.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.how2java.service.ProductService;
public class TestSpring {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] { "applicationContext.xml" });
ProductService s = (ProductService) context.getBean("s");
s.doSomeService();
}
}
让Spring生成一个ProductService的对象,并且调用一下其中的方法
但这里how2java上漏掉了一个步骤,它并没有在xml中配置关键字为"s"的bean,也没有在ProductService.java中作注解,因此直接运行会报找不到该bean的错误
因为这里讲的是普通方式(非注解),因此先回到在xml中写bean的方法
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean name="c" class="com.how2java.pojo.Category">
<property name="name" value="yyy" />
</bean>
<bean name="p" class="com.how2java.pojo.Product">
<property name="name" value="product1" />
<property name="category" ref="c" />
</bean>
<bean name="s" class="com.how2java.service.ProductService">
</bean>
</beans>
现在可以顺利运行TestSpring了
以上都不重要,就当复习了一下IOC的内容
====================================================================================================
假设ProductService就作为这次的核心功能,我们需要在核心功能的执行前后分别输出日志,那日志就作为周边功能,在AOP的思想中,称其为切面(aspect)
那么创建一个日志切面类LoggerAspect.java
package com.how2java.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
public class LoggerAspect {
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("start log:" + joinPoint.getSignature().getName());
Object object = joinPoint.proceed();
System.out.println("end log:" + joinPoint.getSignature().getName());
return object;
}
}
导入支持AOP的lib中名为ProceedingJoinPoint的类
这个后面经常出现的joinPoint,好记一点就称为joinPoint连接点
比如这其中的方法log(ProceedingJoinPoint joinPoint)传入了一个连接点作为参数,方法体内
Object object = joinPoint.proceed();
这一行代表核心功能的执行过程,于是在这一行的前后,分别输出日志
System.out.println("start/end log:" + joinPoint.getSignature().getName());
大概意思是在这个连接点根据标记输出其名称
日志功能写完之后,我们再来修改xml
首先在之前的<bean>下面,添加LoggerAspect.java的<bean>,关键词id设定为loggerAspect
<bean id="loggerAspect" class="com.how2java.aspect.LoggerAspect"/>
在此之后添加
<aop:config>
<aop:pointcut id="loggerCutpoint"
expression=
"execution(* com.how2java.service.ProductService.*(..)) "/>
<aop:aspect id="logAspect" ref="loggerAspect">
<aop:around pointcut-ref="loggerCutpoint" method="log"/>
</aop:aspect>
</aop:config>
这里出现一个词pointcut切入点(那个Cutpoint是自己起的id无所谓),把这一段代码由中间的空行分为上下两部分看:
①上面部分为切入点的声明
指定了这个切入点的id关键词loggerAspect和产生这个切入点的触发条件,即expression=后面的内容
execution(* com.how2java.service.ProductService.*(..))
其中 * 作为常见的通配符,这段中第一次出现表示方法返回的类型可以是任意的,第二次出现表示这个方法可以是ProductService中的任意方法
而 (..) 在这里就表示方法的参数的数量和类型也是任意的
总结起来就是,当ProductService中的任意方法被执行时,就设置一个名为loggerCutPoint的切入点
②下面的部分为切面的声明
指定了这个切面的id关键词logAspect和引用的<bean> loggerAspect
<aop:around pointcut-ref="loggerCutpoint" method="log"/>
这一句声明了这个切面的通知类型为around(在下面AOP术语中作解释),触发切面的切入点关键词为loggerCutPoint,method后面不重要,似乎可以加上参数列表
这样,在不修改TestSpring的情况下,再次执行,就会发现核心功能执行的前后分别输出了日志。
结果为:
start log:doSomeService
doSomeService
end log:doSomeService
how2java上对这一部分的讲解到此结束
====================================================================================================
再具体说一说AOP
AOP的过程分为两步
1.在作为切面的类中设置好连接点
2.在配置中声明切入点和切面
实现核心功能的类完全不需要修改,达到了当核心功能的某个方法触发了切入点时,切面也就被执行了。就像切面功能在监视核心功能的执行一样。
AOP术语
(1)通知
AOP的通知类型有五种:
- Before——在方法调用之前调用通知
- After——在方法完成之后调用通知,无论方法执行成功与否
- After-returning——在方法执行成功之后调用通知
- After-throwing——在方法抛出异常后进行通知
- Around——通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
前四个好理解
最后一个around 表示切面在被监视的函数运行前后都会执行
下面是切面要执行的方法 log,方法有一个形参 joinPoint ,中间一句代表的就是被监视的程序运行,在被监视的程序运行时,可以替换其形参,这个是 around特殊的地方。如果被监视的程序,运行的时候输入的是一个"haha"作为实参,但是经过log方法之后,这个参数就被替换为"abc"了
(2)连接点
在执行正常的功能时,能够插入切面的点、可以作为切入点的点、备选点。
连接点可以是调用方法时、抛出异常时、甚至修改字段时,可以在这些点去执行切面。
(3)切面
切面是通知和切入点的集合,通知和切入点共同定义了切面的全部功能——它是什么,在何时何处完成其功能。
声明切面:
在Spring中,切面就是一个包含通知和切入点的对象,是一个Bean,Bean的字段和方法就是该切面的状态和行为,还要通过配置,来指定切入点和通知实现
在xml中,切面使用<aop:aspect>标签指定,ref属性用来引用切面支持Bean。这个bean里面就是用来执行要做的辅助功能的。
(4)切入点
如果通知定义了“什么”和“何时”。那么切入点就定义了“何处”。切入点会匹配通知所要织入的一个或者多个连接点。通常使用明确的类或者方法来指定这些切入点。
作用:定义通知被应用的位置(在哪些连接点)
切入点的声明:
切入点在Spring也是一个Bean。
切入点的声明有三种定义方式:
1)在<aop:config>标签下使用<aop:pointcut>声明一个切入点Bean,该切入点可以被多个切面使用,对于需要共享使用的切入点最好使用该方式,该切入点使用id属性指定Bean名字,在通知定义时使用pointcut-ref属性通过该id引用切入点,expression属性指定切入点表达式
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* cn.javass..*.*(..))"/>
<aop:aspect ref="aspectSupportBean">
<aop:before pointcut-ref="pointcut" method="before"/>
</aop:aspect>
</aop:config>
2)在<aop:aspect>标签下使用<aop:pointcut>声明一个切入点Bean,该切入点可以被多个切面使用,但一般该切入点只被该切面使用,当然也可以被其他切面使用,但最好不要那样使用,该切入点使用id属性指定Bean名字,在通知定义时使用pointcut-ref属性通过该id引用切入点,expression属性指定切入点表达式
<aop:config>
<aop:aspect ref="aspectSupportBean">
<aop:pointcut id=" pointcut" expression="execution(* cn.javass..*.*(..))"/>
<aop:before pointcut-ref="pointcut" method="before"/>
</aop:aspect>
</aop:config>
3)匿名切入点Bean,可以在声明通知时通过pointcut属性指定切入点表达式,该切入点是匿名切入点,只被该通知使用
<aop:config>
<aop:aspect ref="aspectSupportBean">
<aop:after pointcut="execution(* cn.javass..*.*(..))" method="afterFinallyAdvice"/>
</aop:aspect>
</aop:config>
(5)引入
引入允许我们向现有的类中添加方法或属性
(6)织入
织入是将切面应用到目标对象来创建的代理对象过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期中有多个点可以织入
1)编译期——切面在目标类编译时期被织入,这种方式需要特殊编译器。AspectJ的织入编译器就是以这种方式织入切面。
2)类加载期——切面在类加载到
3)JVM ,这种方式需要特殊的类加载器,他可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5 的 LTW 就支持这种织入方式
4)运行期——切面在应用运行期间的某个时刻被织入。一般情况下,在织入切面时候,AOP 容器会为目标对象动态的创建代理对象。Spring AOP 就是以这种方式织入切面。
至此,对AOP的初步认识告一段落,边学习边总结成博客的方式帮助我比较清晰地梳理了一遍AOP的配置过程,最后的总结部分参考另一篇博文:《Spring之AOP在XML中的配置方法》,感谢其作者以及how2java的站长。