AOP: 面向切面编程
通常,系统由很多组件组成,每个组件负责一部分功能,然而,这些组件也经常带有一些除了核心功能之外的附带功能 。系统服务如日志、事务管理和安全经常融入到一些其他功能模块中。这些系统服务通常叫做交叉业务
AOP能帮助我们将服务模块化,并把它们声明式地应用在需要它们的地方,使得这些组件更加专注于自身业务,完全不知道其它涉及到的系统服务。
这里的概念切面,就是我们要实现的交叉功能,是应用系统模块化的一个方面或领域。切面的最常见例子就是日志记录。日志记录在系统中到处需要用到,利用继承来重用日志模块是不合适的,这样,就可以创建一个日志记录切面,并且使用AOP在系统中应用。
有些代码与业务无关,却被业务模块所调用并封装起,Spring提供了面向切面的编程方式,即SpingAOP,来解决这个问题,能有效的减少了系统间的重复代码,达到了模块间的松耦合目的。
AOP采用横向抽取机制,取代了传统纵向继承体系重复性代码,主要体现在事务处理,日志管理,权限控制,异常处理等方面,使开发人员在编写业务逻辑时可以专心于核心业务,提高了代码的可维护性。
AOP框架
目前最流行的AOP框架有两个,分别为Spring AOP 和AspectJ.
Spring AOP使用纯Java实现,不需要专门的编译器。
AspectJ 是一个基于java语言的AOP框架,并对AspectJ进行了扩展,提供了一个专门的编译器
新版本Spring框架,建议使用AspectJ方式来开发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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
增加了AOP的命名空间与AOP的xsd规范,如不需要注解,可以省略context.xsd
就是其黄色的部分。用于注解。
通知类型
前置通知:在目标方法执行前执行
后置通知:在目标方法执行后执行
异常通知:在目标方法抛出异常后执行
环绕通知:在目标方法执行前和后执行
切面程序:后置通知的切面程序中可以获取到目标方法返回参数,但需要在配置文件中声明参数名,依赖spring容器注入参数值。
后置通知
切入点程序:
package model;
public class Chengxu {
public String talk(String str){
System.out.println("str="+str);
return "保存成功";
}
}
切面程序:
package model;
import org.aspectj.lang.JoinPoint;
public class Charu {
public void log(JoinPoint jp,Object ret){
System.out.println("后置");
System.out.println("obj为目标组件方法返回值:" +ret);
}
}
如果没有返回值,这里的Object要去掉,否则报错
public void log(JoinPoint jp,Object obj){
System.out.println("log......");
System.out.println("获取目标函数参数方式:" +jp.getArgs()[0]);
System.out.println("获取目标对象:" +jp.getTarget().getClass().getName());
System.out.println("获取目标函数的java反射对象:" +jp.getSignature());
System.out.println("返回值===="+obj);
}
测试类:
package model;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestChengxu {
@Test
public void test(){
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("model/applicationContext.xml");
Chengxu chengxu = (Chengxu)app.getBean("chengxu");
chengxu.talk("聊天");
}
异常通知
异常通知:在目标组件的方法抛出异常信息后执行的程序。
当前置通知和后置通知发生异常时,这个异常通知是不执行的,只有当目标组件发生异常时才执行。
切面程序:异常通知的切面程序中可以获取到目标组件抛出的异常信息,需要在配置文件上声明异常参数名,依赖spring容器注入参数值。
最终通知
最终通知:在目标组件的方法正常执行后执行,或在异常通知之前执行。
也就是当目标组件发生没发生异常,都会被执行。
如果目标组件异常时,那后置通知不会执行,异常通知会执行。最终通知会在异常通知之前执行。
如果目标组件正常时,那最终通知会在后置通知之前执行。
切面程序:异常通知的切面程序中可以获取到目标组件抛出的异常信息,需要在配置文件上声明异常参数名,依赖spring容器注入参数值。
Application.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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--切入点程序 -->
<bean id="chengxu" class="model.Chengxu"></bean>
<!--切面程序 前置-->
<bean id="charu" class="model.Charu"></bean>
<!--切面程序2 后置-->
<bean id="charu2" class="model.Charu2"></bean>
<!--切面程序3 异常-->
<bean id="charu3" class="model.Charu3"></bean>
<!--切面程序4 最终-->
<bean id="charu4" class="model.Charu4"></bean>
<!-- AOP配置跟标签 -->
<aop:config>
<!-- 切入点配置:expression切入点表达式 * model....不限返回类型 (*)方法里面是什么参数类型都可以,方法没有参数也可以那就不加这个*
-->
<aop:pointcut expression="execution(* model.Chengxu.talk(*))" id="chengxuPointCut"/>
<!-- 切面配置 -->
<!-- aspect切面程序依赖于谁 -->
<aop:aspect ref="charu">
<!-- 前置通知 -->
<!--切面程序的方法 切入点依赖于谁 (简单的说,把这个方法前置在哪个方法前面) -->
<aop:before method="log" pointcut-ref="chengxuPointCut"/>
</aop:aspect>
<aop:aspect ref="charu2">
<!-- 后置通知 -->
<!--切面程序的方法 切入点依赖于谁 (简单的说,把这个方法前置在哪个方法后面) -->
<aop:after-returning method="log2" pointcut-ref="chengxuPointCut"
returning="ret"/>
</aop:aspect>
<aop:aspect ref="charu3">
<!-- 异常通知 -->
<!--切面程序的方法 切入点依赖于谁 (简单的说,当目标组件发生异常时,执行log3个这方法) -->
<aop:after-throwing method="log3" pointcut-ref="chengxuPointCut"
throwing="t"/>
</aop:aspect>
<aop:aspect ref="charu4">
<!-- 最终通知 -->
<!--切面程序的方法 切入点依赖于谁 (简单的说,目标组件无论发生不发生异常都会走这个log4方法) -->
<aop:after method="log4" pointcut-ref="chengxuPointCut"/>
</aop:aspect>
</aop:config>
</beans>
环绕通知
环绕通知: 切面程序负责调用目标组件的运行,与struts中的拦截器功能类似,可以完全取代之前的几个通知。
切面程序:在类型为环绕通知的切面程序函数中,参数为org.aspectj.lang.ProceedingJoinPoint是JoinPoint的子类,扩展了JoinPoint类,提供了proceed()函数,该函数的作用是调用目标组件,并返回目标组件返回的值。
<!-- AOP配置跟标签 -->
<aop:config>
<!-- 切入点配置:expression切入点表达式 * model....不限返回类型 (*)方法里面是什么参数类型都可以,方法没有参数也可以那就不加这个*
-->
<aop:pointcut expression="execution(* model.Chengxu.talk(*))" id="chengxuPointCut"/>
<aop:aspect ref="charu5">
<!-- 环绕通知 -->
<aop:around method="log5" pointcut-ref="chengxuPointCut"/>
</aop:aspect>
</aop:config>
</beans>
切面程序:
package model;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class Charu5 {
public void log5(ProceedingJoinPoint pjp) {
System.out.println("前置通知");
try {
Object ob = pjp.proceed();
System.out.println("目标函数的返回值:"+ob);
System.out.println("后置通知");
} catch (Throwable e) {
System.out.println("异常通知");
}
System.out.println("最终通知");
}
public void log5(ProceedingJoinPoint pjp) {
System.out.println("环绕通知开始");
Object ob = pjp.proceed();
System.out.println("环绕通知结束");
}
切入点表达式
切入点表达式: 切入点表达式用于声明spring容器中哪些组件的函数是目标函数,也就是切面程序要作用到哪些组件的哪些函数上。
常用的切入点表达式分为:
1)按类匹配:匹配的java类中全部函数作为目标函数使用within关键字。
2)按函数匹配:匹配的函数作为目标函数,使用execution关键字。
3)按bean的id匹配:匹配的bean中全部函数作为目标组件。使用bean关键字
按类匹配:
匹配到类
<aop:pointcut id="targetPintcut"
expression="within(com.chinasofti.Target)"/>
匹配到包下的类
<aop:pointcut id="targetPintcut"
expression="within(com.chinasofti.*)"/>
匹配到包下及子包下的类
<aop:pointcut id="targetPintcut"
expression="within(com..*)"/>
按函数匹配:
完整写法
返回类型 类的路径 类名 函数名 参数类型(用,分开)
execution(String com.chinasofti.Target.save(String))
任意返回类型
execution(* com.chinasofti.Target.save(String))
任意返回类型下指定包下任意类
execution(* com.chinasofti..save(String))
任意返回类型下指定包下任意类任意函数
execution( com.chinasofti..(String))
任意返回类型下指定包或子包下任意类任意函数任意参数
execution(* com….(…))
AOP注解
<!-- 开启自动扫描 -->
<context:component-scan base-package="com.china.model"></context:component-scan>
<!-- spring中使用aspectj包中的@Aspect注解标注当前组件为切面,
如果要使用改注解必须开启aspectj的自动代理模式 -->
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
@Component(“before”)
@Aspect --切面程序
@Before(value=“execution(* com.china.model.Chengxu.talk())") --前置通知
@AfterReturning(value=“execution(com.china.model.Chengxu.talk())”,returning=“ret”) --后置通知
@AfterThrowing(value="execution( com.china.model.Chengxu.talk())", throwing=“e”) --异常通知
@After(value="execution( com.china.model.Chengxu.talk())") --最终通知
@Around(value="execution( com.china.model.Chengxu.talk(*))”) --环绕通知