Spring2-AOP
一.AOP
1、名称:面向切面编程(Aspect Oriented Programming)
2、正常程序执行流程都是纵向执行流程
面向切面编程,在原有纵向执行流程中添加横切面
不需要修改原有程序代码
高扩展性
原有功能相当于释放了部分逻辑.让职责更加明确
3、面向切面编程概念:在程序原有纵向执行流程中,针对某一个或某一些方法添加通知,形成横切面过程就叫做面向切面编程.
4、常用概念
1)原有功能: 切点,pointcut
2)前置通知: 在切点之前执行的功能.beforeadvice
3)后置通知: 在切点之后执行的功能,afteradvice
4)如果切点执行过程中出现异常,会触发异常通知.throwsadvice
5)切面:所有功能总称。(切入点pointcut和通知advice的结合)
6)织入:把切面嵌入到原有功能的过程叫做织入
5、spring 提供了 2 种 AOP 实现方式
1)Schema-based
每个通知都需要实现接口或类
配置 spring 配置文件时在<aop:config>配置
2)AspectJ
每个通知不需要实现接口或类
配置 spring 配置文件是在<aop:config>的子标签<aop:aspect>中配置
二. Schema-based 实现
(一)前置、后置通知(Schema-based 方式)
1、导入 jar
2、新建通知类
1)新建前置通知类
arg0: 切点方法对象 Method 对象
arg1: 切点方法参数
arg2:切点在哪个对象中
public class MyBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { System.out.println("执行前置通知"); } }
2)新建后置通知类
arg0: 切点方法返回值
arg1:切点方法对象
arg2:切点方法参数
arg3:切点方法所在类的对象
public class MyAfterAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { System.out.println("执行后置通知"); } }
3)配置 spring 配置文件
引入 aop 命名空间
配置通知类的<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" 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"> <!-- 配置通知类对象,在切面中引入 --> <bean id="mybefore" class="com.su.advice.MyBeforeAdvice"></bean> <bean id="myafter" class="com.su.advice.MyAfterAdvice"></bean> <!-- 配置切面 --> <aop:config> <!-- 配置切点 --> <aop:pointcut expression="execution(* com.su.test.Demo.demo2())" id="mypoint"/> <!-- 通知 --> <aop:advisor advice-ref="mybefore" pointcut-ref="mypoint"/> <aop:advisor advice-ref="myafter" pointcut-ref="mypoint"/> </aop:config> <!-- 配置 Demo 类,测试使用 --> <bean id="demo" class="com.su.test.Demo"></bean> </beans>
4)编写测试代码
public class Test { public static void main(String[] args) { //原始方法 // Demo demo = new Demo(); // demo.demo1(); // demo.demo2(); // demo.demo3(); ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); Demo demo = ac.getBean("demo",Demo.class); demo.demo1(); demo.demo2(); demo.demo3(); } }
5) 运行结果
demo1
执行前置通知
demo2
执行后置通知
demo3
(二)异常通知(Schema-based 方式)
1、新建一个类实现 throwsAdvice 接口
1)必须自己写方法,且必须叫 afterThrowing
2)有两种参数方式
必须是 1 个或 4 个
3)异常类型要与切点报的异常类型一致
public class MyThrow implements ThrowsAdvice{ // public void afterThrowing(Method m, Object[] args, Object target, Exception ex) { // System.out.println("执行异常通知"); // } public void afterThrowing(Exception ex) throws Throwable { System.out.println("执行异常通过-schema-base 方式 "); } }
2、在 ApplicationContext.xml 配置
<bean id="mythrow" class="com.su.advice.MyThrow"></bean> <aop:config> <aop:pointcut expression="execution(* com.su.test.Demo.demo1())" id="mypoint"/> <aop:advisor advice-ref="mythrow" pointcut-ref="mypoint" /> </aop:config> <bean id="demo" class="com.su.test.Demo"></bean>
(三)环绕通知(Schema-based 方式)
1、把前置通知和后置通知都写到一个通知中,组成了环绕通知
2、实现步骤
1)新建一个类实现 MethodInterceptor
public class MyArround implements MethodInterceptor { @Override public Object invoke(MethodInvocation arg0) throws Throwable { System.out.println("环绕-前置"); Object result = arg0.proceed();//放行,调用切点方式 System.out.println("环绕-后置"); return result; } }
2)配置 applicationContext.xml
<bean id="myarround" class="com.su.advice.MyArround"></bean> <aop:config> <aop:pointcut expression="execution(* com.su.test.Demo.demo1())" id="mypoint"/> <aop:advisor advice-ref="myarround" pointcut-ref="mypoint" /> </aop:config> <bean id="demo" class="com.su.test.Demo"></bean>
三. AspectJ 实现
(一)前置、后置、环绕通知(AspectJ 方式)
1、新建类,不用实现
1)类中方法名任意
public class MyAdvice { public void mybefore(String name1,int age1){ System.out.println("前置"+name1 ); } public void mybefore1(String name1){ System.out.println("前置:"+name1); } public void myaftering(){ System.out.println("后置 2"); } 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; } }
2)配置 spring 配置文件
<aop:after/> 后置通知,是否出现异常都执行
<aop:after-returning/> 后置通知,只有当切点正确执行时执行
<aop:after/> 和 <aop:after-returning/> 和<aop:after-throwing/>执行顺序和配置顺序有关
execution() 括号不能扩上 args
中间使用 and 不能使用&&, 由 spring 把 and 解析成&&
args(名称) 名称自定义的.顺序和 demo1(参数,参数)对应
<aop:before/> arg-names="名称" 名称来源于expression="" 中 args(),名称必须一样
args() 有几个参数,arg-names 里面必须有几个参数
arg-names="" 里面名称必须和通知方法参数名对应
<?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"> <bean id="demo" class="com.su.test.Demo"></bean> <bean id="myadvice" class="com.su.advice.MyAdvice"></bean> <aop:config> <aop:aspect ref="myadvice"> <aop:pointcut expression="execution(* com.su.test.Demo.demo1(String,int)) and args(name1,age1)" id="mypoint"/> <aop:pointcut expression="execution(* com.su.test.Demo.demo1(String)) and args(name1)" id="mypoint1"/> <aop:before method="mybefore" pointcut-ref="mypoint" arg-names="name1,age1"/> <aop:before method="mybefore1" pointcut-ref="mypoint1" arg-names="name1"/> <!-- <aop:after method="myafter" pointcut-ref="mypoint"/> <aop:after-returning method="myaftering" pointcut-ref="mypoint"/> <aop:after-throwing method="mythrow" pointcut-ref="mypoint"/> <aop:around method="myarround" pointcut-ref="mypoint"/>--> </aop:aspect> </aop:config> </beans>
(二)异常通知(AspectJ 方式)
1、 只有当切点报异常才能触发异常通知
2、在 spring 中有 AspectJ 方式提供了异常通知的办法
3、实现步骤:
1)新建类,在类写任意名称的方法
public class MyThrowAdvice{ public void myexception(Exception e1){ System.out.println("执行异常通知 "+e1.getMessage()); } }
2)在 spring 配置文件中配置
<aop:aspect>的 ref 属性表示:方法在哪个类中.
<aop:xxxx/> 表示什么通知
method: 当触发这个通知时,调用哪个方法
throwing: 异常对象名,必须和通知中方法参数名相同(可以不在通知中声明异常对象)
<bean id="mythrow" class="com.su.advice.MyThrowAdvice"></bean> <aop:config> <aop:aspect ref="mythrow"> <aop:pointcut expression="execution(* com.su.test.Demo.demo1())" id="mypoint"/> <aop:after-throwing method="myexception" pointcut-ref="mypoint" throwing="e1"/> </aop:aspect> </aop:config> <bean id="demo" class="com.su.test.Demo"></bean>
四.使用注解(基于 Aspect)
1、spring 不会自动去寻找注解,必须告诉 spring 哪些包下的类中可能有注解
1)引入 xmlns:context
<context:component-scan base-package="com.su.advice"></context:component-scan>
2、@Component
1)相当于<bean/>
2)如果没有参数,把类名首字母变小写,相当于<bean id=""/>
3)@Component(“自定义名称”)
3、实现步骤:
1)在 spring 配置文件中设置注解在哪些包中
<context:component-scan base-package="com.su.advice,com.su.test"></context:component-scan>
2)在 Demo 类中添加@Component
在方法上添加@Pointcut("") 定义切点
@Component public class Demo { @Pointcut("execution(* com.su.test.Demo.demo1())") public void demo1() throws Exception{ // int i = 5/0; System.out.println("demo1"); } }
3)在通知类中配置
@Component 类被 spring 管理
@Aspect 相当于<aop:aspect/>表示通知方法在当前类中
@Component @Aspect public class MyAdvice { @Before("com.su.test.Demo.demo1()") public void mybefore(){ System.out.println("前置"); } @After("com.su.test.Demo.demo1()") public void myafter(){ System.out.println("后置通知"); } @AfterThrowing("com.su.test.Demo.demo1()") public void mythrow(){ System.out.println("异常通知"); } @Around("com.su.test.Demo.demo1()") public Object myarround(ProceedingJoinPoint p) throws Throwable{ System.out.println("环绕-前置"); Object result = p.proceed(); System.out.println("环绕-后置"); return result; } }