AOP( Aspect Oriented Programming ):面向切面编程
正常程序执行流程都是纵向执行流程,而AOP面向切面编程,是在原有纵向执行流程中添加横切面,不需要修改原有程序代码,实现高扩展性,原有功能相当于释放了部分逻辑.让职责更加明确.。
面向切面编程是什么?
在程序原有纵向执行流程中,针对某一个或某一些方法添加通知,形成横切面过程就叫做面向切面编程.
常用概念:
原有功能: 切点, pointcut 前置通知: 在切点之前执行的功能. before advice
后置通知: 在切点之后执行的功能,after advice 如果切点执行过程中出现异常,会触发异常通知.throws advice
所有功能总称叫做切面. 织入: 把切面嵌入到原有功能的过程叫做织入
spring 提供了 2 种 AOP 实现方式:
1.Schema-based:每个通知都需要实现接口或类;配置 spring 配置文件时在<aop:config>配置
2 AspectJ:每个通知不需要实现接口或类;配置 spring 配置文件是在<aop:config>的子标签<aop:aspect>中配置
Schema-based 实现步骤:
1. 导入 jar
2.新建前置通知、后置通知:
package com.tao.advice; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; /** * 前置通知 */ public class MyBeforeAdvice implements MethodBeforeAdvice{ @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { System.out.println("切点方法对象"+arg0+",方法名:"+arg0.getName()); System.out.println("切点方法参数"+arg1); System.out.println("对象"+arg2); System.out.println("执行前置通知。。。"); } }
package com.tao.advice; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; /** * 后置通知 */ public class MyAfterAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { System.out.println("执行后置通知。。。"); } }
3.配置Spring的配置文件:
<?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"> <!-- 配置 Demo 类,测试使用 --> <bean id="demo" class="com.tao.testAop.Demo"></bean> <bean id="mybeforeAdvice" class="com.tao.advice.MyBeforeAdvice"></bean> <bean id="myafterAdvice" class="com.tao.advice.MyAfterAdvice"></bean> <!-- 配置切面 --> <aop:config> <!-- 切面配置表达式通配符写法: 1.目前只拦截com.tao.testAop包下Demo类的demo2方法 <aop:pointcut expression="execution(* com.tao.testAop.Demo.demo2())" id="demo2point"/> 2.拦截com.tao.testAop包下Demo类的任意方法形成切面 <aop:pointcut expression="execution(* com.tao.testAop.Demo.*())" id="demo2point"/> 3.拦截com.tao.testAop包下Demo类的任意方法及匹配方法的任意参数(不管几个不管什么类型)形成切面 <aop:pointcut expression="execution(* com.tao.testAop.Demo.*(..))" id="demo2point"/> 4.拦截com.tao.testAop包下任意类的任意方法 形成切面 <aop:pointcut expression="execution(* com.tao.testAop.*.*(..))" id="demo2point"/> 5.在分模块的时候,我们假设需要拦截无论哪个模块下service下任意实现类的任意方法 (下面是com.tao项目下任意模块任意实现类的任意方法) <aop:pointcut expression="execution(* com.tao.*.service.impl.*.*(..))" id="demo2point"/> --> <!-- 配置切点 --> <aop:pointcut expression="execution(* com.tao.testAop.Demo.demo2())" id="demo2point"/> <!-- 切点方法多个参数时,表达式参考写法 <aop:pointcut expression="execution(* com.tap.testAop.Demo.demo2(String) and args(name))" id=""/> <aop:pointcut expression="execution(* com.tap.testAop.Demo.demo2(String,int) and args(name,age))" id=""/> --> <!-- 通知 --> <aop:advisor advice-ref="mybeforeAdvice" pointcut-ref="demo2point"/> <!-- 通知 --> <aop:advisor advice-ref="myafterAdvice" pointcut-ref="demo2point"/> </aop:config> </beans>
package com.tao.testAop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Demo { public void demo1(){ System.out.println("demo1"); } public void demo2(){ System.out.println("demo2"); } public void demo3(){ System.out.println("demo3"); } public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); Demo demo = ac.getBean("demo",Demo.class); demo.demo1(); demo.demo2(); demo.demo3(); } }
异常通知:
基于schema-base方式的实现:
1.新建一个类实现 throwsAdvice 接口: (1) 必须自己写方法,且必须叫 afterThrowing (2)有两种参数方式:必须是 1 个或 4 个
(3)异常类型要与切点报的异常类型一致
异常通知类:
package com.tao.Throw; import org.springframework.aop.ThrowsAdvice; public class MyThrow implements ThrowsAdvice{ public void afterThrowing(Exception ex) throws Throwable{ System.out.println("执行异常通知,通过-schema-base 方式"); } }
在demo2方法中制造一个异常:
package com.tao.testAop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Demo { public void demo1(){ System.out.println("demo1"); } public void demo2(){ int i =5/0; System.out.println("demo2"); } public void demo3(){ System.out.println("demo3"); } public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); Demo demo = ac.getBean("demo",Demo.class); demo.demo1(); demo.demo2(); demo.demo3(); } }
applicationContext.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"> <!-- 配置 Demo 类,测试使用 --> <bean id="demo" class="com.tao.testAop.Demo"></bean> <bean id="mybeforeAdvice" class="com.tao.advice.MyBeforeAdvice"></bean> <bean id="myafterAdvice" class="com.tao.advice.MyAfterAdvice"></bean> <!-- 配置切面 --> <aop:config> <!-- 切面配置表达式通配符写法: 1.目前只拦截com.tao.testAop包下Demo类的demo2方法 <aop:pointcut expression="execution(* com.tao.testAop.Demo.demo2())" id="demo2point"/> 2.拦截com.tao.testAop包下Demo类的任意方法形成切面 <aop:pointcut expression="execution(* com.tao.testAop.Demo.*())" id="demo2point"/> 3.拦截com.tao.testAop包下Demo类的任意方法及匹配方法的任意参数(不管几个不管什么类型)形成切面 <aop:pointcut expression="execution(* com.tao.testAop.Demo.*(..))" id="demo2point"/> 4.拦截com.tao.testAop包下任意类的任意方法 形成切面 <aop:pointcut expression="execution(* com.tao.testAop.*.*(..))" id="demo2point"/> 5.在分模块的时候,我们假设需要拦截无论哪个模块下service下任意实现类的任意方法 (下面是com.tao项目下任意模块任意实现类的任意方法) <aop:pointcut expression="execution(* com.tao.*.service.impl.*.*(..))" id="demo2point"/> --> <!-- 配置切点 --> <aop:pointcut expression="execution(* com.tao.testAop.Demo.demo2())" id="demo2point"/> <!-- 切点方法多个参数时,表达式参考写法 <aop:pointcut expression="execution(* com.tap.testAop.Demo.demo2(String) and args(name))" id=""/> <aop:pointcut expression="execution(* com.tap.testAop.Demo.demo2(String,int) and args(name,age))" id=""/> --> <!-- 前置通知 --> <aop:advisor advice-ref="mybeforeAdvice" pointcut-ref="demo2point"/> <!-- 后置通知 --> <aop:advisor advice-ref="myafterAdvice" pointcut-ref="demo2point"/> <!-- 异常通知 --> <aop:advisor advice-ref="mythrow" pointcut-ref="demo2point"/> </aop:config> <bean id="mythrow" class="com.tao.Throw.MyThrow"></bean> </beans>
配置完成。
基于AspectJ 方式的实现:
package com.tao.Throw; /** * Aspect方式 实现异常通知 */ public class MyAspectJThrow { public void myexception(Exception ex){ System.out.println("执行异常通知,通过-Aspect 方式"+ex.getMessage()); } }
<?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"> <!-- 配置 Demo 类,测试使用 --> <bean id="demo" class="com.tao.testAop.Demo"></bean> <bean id="mybeforeAdvice" class="com.tao.advice.MyBeforeAdvice"></bean> <bean id="myafterAdvice" class="com.tao.advice.MyAfterAdvice"></bean> <!-- 配置切面 --> <aop:config> <!-- 切面配置表达式通配符写法: 1.目前只拦截com.tao.testAop包下Demo类的demo2方法 <aop:pointcut expression="execution(* com.tao.testAop.Demo.demo2())" id="demo2point"/> 2.拦截com.tao.testAop包下Demo类的任意方法形成切面 <aop:pointcut expression="execution(* com.tao.testAop.Demo.*())" id="demo2point"/> 3.拦截com.tao.testAop包下Demo类的任意方法及匹配方法的任意参数(不管几个不管什么类型)形成切面 <aop:pointcut expression="execution(* com.tao.testAop.Demo.*(..))" id="demo2point"/> 4.拦截com.tao.testAop包下任意类的任意方法 形成切面 <aop:pointcut expression="execution(* com.tao.testAop.*.*(..))" id="demo2point"/> 5.在分模块的时候,我们假设需要拦截无论哪个模块下service下任意实现类的任意方法 (下面是com.tao项目下任意模块任意实现类的任意方法) <aop:pointcut expression="execution(* com.tao.*.service.impl.*.*(..))" id="demo2point"/> --> <!-- 配置切点 --> <aop:pointcut expression="execution(* com.tao.testAop.Demo.demo2())" id="demo2point"/> <!-- 切点方法多个参数时,表达式参考写法 <aop:pointcut expression="execution(* com.tap.testAop.Demo.demo2(String) and args(name))" id=""/> <aop:pointcut expression="execution(* com.tap.testAop.Demo.demo2(String,int) and args(name,age))" id=""/> --> <!-- 前置通知 --> <aop:advisor advice-ref="mybeforeAdvice" pointcut-ref="demo2point"/> <!-- 后置通知 --> <aop:advisor advice-ref="myafterAdvice" pointcut-ref="demo2point"/> <!-- 异常通知 AspectJ --> <aop:aspect ref="myAspectJThrow"> <aop:after-throwing method="myexception" pointcut-ref="demo2point" throwing="ex"/> </aop:aspect> </aop:config> <!-- 异常通知 Aspect 方式实体 --> <bean id="myAspectJThrow" class="com.tao.Throw.MyAspectJThrow"></bean> </beans>
AspectJ 方式 配置完成。
环绕通知:把前置通知和后置通知都写到一个通知中,组成了环绕通知
步骤:编写环绕通知实体类,实现MethodInterceptor 接口;配置applicationContext.xml
package com.tao.around; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * 环绕通知 */ public class MyAroundAdvisor implements MethodInterceptor{ @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("环绕-前置"); Object resutl = methodInvocation.proceed(); System.out.println("环绕后置"); return resutl; } }
<?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"> <!-- 配置 Demo 类,测试使用 --> <bean id="demo" class="com.tao.testAop.Demo"></bean> <aop:config> <aop:pointcut expression="execution(* com.tao.testAop.Demo.demo1())" id="demo1point"/> <aop:advisor advice-ref="myAroundAdvisor" pointcut-ref="demo1point"/> </aop:config> <!-- 环绕通知 --> <bean id="myAroundAdvisor" class="com.tao.around.MyAroundAdvisor"></bean> </beans>
AspectJ 最简单实现通知(也能同schema-base一样获得方法参数):
<aop:after/> 后置通知,是否出现异常都执行 <aop:after-returing/> 后置通知,只有当切点正确执行时执行
<aop:after/> 和 <aop:after-returing/> 和<aop:after-throwing/>执行顺序和配置顺序有关
execution() 括号不能扩上 args 中间使用 and 不能使用&& 由 spring 把 and 解析成&&
args(名称) 名称自定义的.顺序和 demo1(参数,参数)对应
<aop:before/> arg-names=” 名 称 ” 名 称 来 源 于expression=”” 中 args(),名称必须一样
args() 有几个参数,arg-names 里面必须有几个参数 arg-names=”” 里面名称必须和通知方法参数名对应
package com.tao.advice; import org.aspectj.lang.ProceedingJoinPoint; /** * 使用AspectJ实现简单通知 */ public class CommonAdvice { public void mybefore(String name1,int age1){ System.out.println("前置"+name1+age1 ); } public void mybefore1(String name1){ System.out.println("前置:"+name1); } //后置 出异常不执行 public void myaftering(){ System.out.println("后置 2"); } //后置出异常正常执行 类似于finally public void myafter(){ System.out.println("后置 1"); } public void mythrow(){ System.out.println("异常通知"); } public Object myarround(ProceedingJoinPoint p) throws Throwable{ System.out.println("执行环绕"); System.out.println("环绕-前置"); Object result = p.proceed(); System.out.println("环绕后置"); return result; } }
package com.tao.testAop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Demo { public void demo1(){ System.out.println("demo1"); } public void demo2(){ int i =5/0; System.out.println("demo2"); } public void demo3(){ System.out.println(); } public void demo3(String name){ System.out.println("demo3"+name); } public void demo3(String name,int age){ System.out.println("demo3"+name+" "+age); } public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); Demo demo = ac.getBean("demo",Demo.class); //demo.demo1(); //.demo2(); demo.demo3(); demo.demo3("两个参数",1); demo.demo3("一个参数"); } }
applicationContext.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"> <!-- 配置 Demo 类,测试使用 --> <bean id="demo" class="com.tao.testAop.Demo"></bean> <bean id="commonAdvice" class="com.tao.advice.CommonAdvice"></bean> <!-- 使用AspectJ实现简单通知 --> <aop:config> <aop:aspect ref="commonAdvice"> <aop:pointcut expression="execution(* com.tao.testAop.Demo.demo3())" id="demo3pointnull"/> <aop:pointcut expression="execution(* com.tao.testAop.Demo.demo3(String,int)) and args(name1,age1)" id="demo3point"/> <aop:pointcut expression="execution(* com.tao.testAop.Demo.demo3(String)) and args(name1)" id="demo3point1"/> <!-- 前置通知 并获得参数值 --> <aop:before method="mybefore" pointcut-ref="demo3point" arg-names="name1,age1" /> <aop:before method="mybefore1" pointcut-ref="demo3point1" arg-names="name1" /> <!-- 后置 遇到异常继续执行 --> <aop:after method="myafter" pointcut-ref="demo3pointnull"/> <!-- 后置 遇到异常不执行 --> <aop:after-returning method="myaftering" pointcut-ref="demo3pointnull"/> <!-- 异常通知 --> <aop:after-throwing method="mythrow" pointcut-ref="demo3pointnull"/> <!-- 环绕通知 --> <aop:around method="myarround" pointcut-ref="demo3pointnull"/> </aop:aspect> </aop:config> </beans>
基于注解实现Spring Aop:
1.开启注解扫描,以及aspectj-autoproxy切面自动织入
<?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:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 指定Spring注解扫描路径 --> <context:component-scan base-package="com.tao.advice,com.tao.testAop"></context:component-scan> <!-- proxy-target-class="true" 表示使用cglib动态代理 注解这块都是基于cglib做的 proxy-target-class="false" 表示使用jdk动态代理 --> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> </beans>
2.新建一个要指定实现Aop的切点类:
@Component 注解:相当于<bean>; 如果没有参数,把类名首字母变小写,相当于<bean id=" demo"/>
也可以自定义名称:@Component(“自定义名称”) 例如下面 也可以 @component("demo123")
@Pointcut注解: 切点注解,指定方位为切点 @Pointcut(execution(* 路径)) 可参考上述xml配置中的切点表达式的写法
package com.tao.testAop; import org.aspectj.lang.annotation.Pointcut; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Component; @Component public class Demo { @Pointcut("execution(* com.tao.testAop.Demo.demo1())") public void demo1(){ System.out.println("demo1"); } public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); Demo demo = ac.getBean("demo",Demo.class); demo.demo1(); } }
3.配置切面,及指定具体的通知处理方法:
@Aspect 指定切面类
package com.tao.advice; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; /** * 使用AspectJ实现简单通知 */ @Component @Aspect public class CommonAdvice { @Before("com.tao.testAop.Demo.demo1()") public void before(){ System.out.println("前置 通知"); } //后置 出异常不执行 @AfterReturning public void myaftering(){ System.out.println("后置 通知,异常不执行"); } //后置出异常正常执行 类似于finally @After("com.tao.testAop.Demo.demo1()") public void myafter(){ System.out.println("后置 通知 异常执行"); } @AfterThrowing("com.tao.testAop.Demo.demo1()") public void mythrow(){ System.out.println("异常通知"); } @Around("com.tao.testAop.Demo.demo1()") public Object myarround(ProceedingJoinPoint p) throws Throwable{ System.out.println("执行环绕"); System.out.println("环绕-前置"); Object result = p.proceed(); System.out.println("环绕后置"); return result; } }
测试,运行Demo.java main函数:
至此Aop常见配置方式完成。