最近学习了spring中的AOP(面向切面编程),总结了一下所学的知识,算是学习笔记,来分享一下。
什么是AOP?
在软件也中,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架的一个重要内容,是函数式编写的一中衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
Struts2中的拦截器、Hibernate中的一级缓存和快照 都是利用了AOP的思想
-
AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
-
Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码
**************************************************************************************************************************************
AOP相关术语:
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点. (对哪些方法进行代码增强,也就是要增强的方法)
Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义.(对哪个方法进行增强)
Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)(增强==通知)
Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
Target(目标对象):代理的目标对象(目标对象)
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程.
spring采用动态代理织入,而AspectJ采用编译期织入和类装在期织入
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
Aspect(切面): 是切入点和通知(引介)的结合(把增强单独提取出来进行维护)
**************************************************************************************************************************************
使用AspectJ实现AOP:
AspectJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入
需要导入的jar包:
@AspectJ提供的不同的通知类型(增强代码):
-
@Before 前置通知,相当于BeforeAdvice org.springframework.aop.MethodBeforeAdvice 在目标方法执行前实施增强
-
@AfterReturning 后置通知,相当于AfterReturningAdvice org.springframework.aop.AfterReturningAdvice 在目标方法执行后实施增强
-
@Around 环绕通知,相当于MethodInterceptor org.aopalliance.intercept.MethodInterceptor 在目标方法执行前后实施增强
-
@AfterThrowing抛出通知,相当于ThrowAdvice org.springframework.aop.ThrowsAdvice 在方法抛出异常后实施增强
-
@After 最终final通知,不管是否异常,该通知都会执行
-
@DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握) org.springframework.aop.IntroductionInterceptor 在目标类中添加一些新的方法和属性
在通知中通过value属性定义切点:
execution(* com.baidu.dao.GenericDAO+.*(..))
-
通过execution函数,可以定义切点的方法切入
-
语法:
-
execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
-
-
例如
-
匹配所有类public方法 execution(public * *(..))
-
匹配指定包下所有类方法 execution(* com.baidu.dao.*(..)) 不包含子包
-
execution(* com.baidu.dao..*(..)) ..*表示包、子孙包下所有类
-
匹配指定类所有方法 execution(* com.baidu.service.UserService.*(..))
-
匹配实现特定接口所有类方法
-
匹配所有save开头的方法 execution(* save*(..))
-
**************************************************************************************************************************************
代码测试@Before前置通知:
目标对象接口:
public interface AopServiceI {
int add();
void list();
String delete();
void update();
}
目标对象:
public class AopServiceImpl implements AopServiceI{
@Override
public int add() {
// TODO Auto-generated method stub
System.out.println("AOPadd..........");
return 360;
}
@Override
public void list() {
// TODO Auto-generated method stub
System.out.println("AOPlist..........");
}
@Override
public String delete() {
// TODO Auto-generated method stub
System.out.println("AOPdelete.....");
return "delete ex";
}
@Override
public void update() {
// TODO Auto-generated method stub
System.out.println("update.........");
int i=1/0;
}
}
切面类:
//切面类
@Aspect
public class MyAspect {
@Before(value="execution(* com.baidu.aop.AopServiceImpl.list(..))")
public void myBefore(JoinPoint joinPoint){
//joinPoint:切入点
System.out.println("前置通知"+joinPoint);
}
}
beans.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">
<!-- 开启切面的自动代理 -->
<aop:aspectj-autoproxy/>
<bean id="aopService" class="com.baidu.aop.AopServiceImpl"></bean>
<!-- 配置切面 -->
<bean class="com.baidu.aop.MyAspect"></bean>
</beans>
运行结果:
**************************************************************************************************************************************
代码测试@AfterReturning后置通知:
通过returning属性 可以定义方法返回值,作为参数
目标对象接口、目标对象和配置文件同上
切面类:
@AfterReturning(value="execution(* com.baidu.aop.AopServiceImpl.add(..))",returning="reVal")
public void myAfterReturning(JoinPoint joinPoint,Object reVal){
System.out.println("后置通知,返回值是:"+reVal);
}
测试方法:
public class TestAop {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
AopServiceI aopServiceI =(AopServiceI) applicationContext.getBean("aopService");
// aopServiceI.list();
aopServiceI.add();
}
}
运行结果:
**************************************************************************************************************************************
代码测试@Around 环绕通知:
-
around方法的返回值就是目标代理方法执行返回值
-
参数为ProceedingJoinPoint 可以调用拦截目标方法执行
目标对象接口、目标对象和配置文件同上
切面类:
@Around("execution(* com.baidu.aop.AopServiceImpl.delete(..))")
public Object myAround(ProceedingJoinPoint point) throws Throwable{
System.out.println("环绕通知前");
Object returnVal = point.proceed();
System.out.println("环绕通知后,返回值是:"+returnVal);
return returnVal;
}
测试方法:
public class TestAop {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
AopServiceI aopServiceI =(AopServiceI) applicationContext.getBean("aopService");
// aopServiceI.list();
// aopServiceI.add();
aopServiceI.delete();
}
}
运行结果:
重点:如果不调用 ProceedingJoinPoint的 proceed方法,那么目标方法就被拦截了
**************************************************************************************************************************************
代码测试@AfterThrowing抛出通知:
通过设置throwing属性,可以设置发生异常对象参数
目标对象接口、目标对象和配置文件同上
切面类:
@AfterThrowing(value="execution(* com.baidu.aop.AopServiceImpl.update(..))",throwing="ox")
public void myAfterthrowing(JoinPoint joinPoint,Throwable ox){
System.out.println("出异常了,异常信息是:"+ox.getMessage());
}
测试方法:
public class TestAop {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
AopServiceI aopServiceI =(AopServiceI) applicationContext.getBean("aopService");
// aopServiceI.list();
// aopServiceI.add();
// aopServiceI.delete();
aopServiceI.update();
}
}
运行结果:
**************************************************************************************************************************************
代码测试@After 最终final通知通知:
不管是否异常,该通知都会执行
切面类:
@After(value="execution(* com.baidu.aop.AopServiceImpl.*(..))")
public void myAfter(){
System.out.println("最终通知");
}
测试方法(有异常):
public class TestAop {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
AopServiceI aopServiceI =(AopServiceI) applicationContext.getBean("aopService");
// aopServiceI.list();
// aopServiceI.add();
// aopServiceI.delete();
aopServiceI.update();
}
}
运行结果:
测试方法(无异常):
public class TestAop {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
AopServiceI aopServiceI =(AopServiceI) applicationContext.getBean("aopService");
// aopServiceI.list();
// aopServiceI.add();
aopServiceI.delete();
// aopServiceI.update();
}
}
运行结果:
**************************************************************************************************************************************
单独提取切入点
在每个通知内定义切点,会造成工作量大,不易维护,对于重复的切点,可以使用@Poingcut进行定义
-
切点方法:private void 无参数方法,方法名为切点名
-
当通知多个切点时,可以使用 || 进行连接
例如:
@After(value="MyAspect.aa()") //多个切点时可以用 || 连接
public void myAfter(){
System.out.println("最终通知");
}
@Pointcut("execution(* com.baidu.aop.AopServiceImpl.*(..))")
private void aa(){
}
**************************************************************************************************************************************
使用XML配置切面(推荐使用)
就不一一举例了
举例:前置通知:
目标对象和目标对象接口同上
切面类:
//切面类
@Aspect
public class MyAspectXML {
public void myBefore(JoinPoint joinPoint){
//joinPoint:切入点
System.out.println("前置通知"+joinPoint);
}
}
测试方法:
public class TestAop {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
AopServiceI aopServiceI =(AopServiceI) applicationContext.getBean("aopService");
aopServiceI.list();
// aopServiceI.add();
// aopServiceI.delete();
// aopServiceI.update();
}
}
配置文件:
beans.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">
<!-- 开启切面的自动代理 -->
<aop:aspectj-autoproxy/>
<bean id="aopService" class="com.baidu.aop.AopServiceImpl"></bean>
<!-- 配置切面 -->
<!-- <bean class="com.baidu.aop.MyAspect"></bean> -->
<bean id="myAspectXML" class="com.baidu.aop.MyAspectXML"></bean>
<aop:config>
<aop:aspect ref="myAspectXML">
<!-- 配置切入点和增强的集合 -->
<aop:pointcut expression="execution(* com.baidu.aop.AopServiceImpl.*(..))" id="pot1"/>
<aop:before method="myBefore" pointcut-ref="pot1"/>
</aop:aspect>
</aop:config>
</beans>
运行结果: