“IOC控制反转完成了解耦合,那么功能扩展就交由我AOP切面编程来完成吧”
什么是切面
当前某一类下有三个方法名曰:func1()、func2()、func3(),在实现方法中我们依次调用,那么执行结果则是竖向调用。可现在面临的问题是——项目的整体架构已经完成,我们需要在不破坏原先设计的条件下去添加新的功能,这该怎么实现嘞?
这一问题就引入了切面:现在把某一方法当成切点(这里假设成func2),在切点处横斩一刀产生一个切面,这个切面就是用于引入新功能的载体。(新技能,get成功!)
几个常用名词:
切点:即原有功能(func2)、前置通知:走在切点之前执行的功能(功能1)、后置通知:切点执行后紧随其后的功能(功能2)、异常通知:切点处出现异常时执行的异常抛出。以上功能总和构成整个切面,将切面嵌入的过程称为织入
切面编程怎么实现
在Spring中提供了两种方式用于实现切面编程——Schema-base和AsceptJ,这里下面细写,先导Jar包(从现在开始本蒟蒻就开始使用开发神器IDEA啦)
Java类编写部分
首先先手写一个Demo类和一个Test 测试类,Demo类下的三个方法用于设置切点方便测试,Test 用于测试功能。在func2() 方法里故意设置了一个异常,用于测试切面编程的异常抛出,测试时将注释松开
package com.yang.test;
public class Demo {
/**
* AOP 切面编程测试类
*/
public void func1() {
System.out.println("func1");
}
public void func2() throws Exception{
//给切点故意设置一个异常
// int i=5/0;
System.out.println("func2");
}
public void func3() {
System.out.println("func3");
}
}
/这里是两个类之间吴迪的分界线/
package com.yang.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
// Demo demo=new Demo(); //对象的创建和管理交由Spring处理
// demo.func1();
// demo.func2();
// demo.func3();
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo=ac.getBean("demo", Demo.class);
demo.func1();
try {
demo.func2(); //测试在func2方法上添加切面
} catch (Exception e) {
// e.printStackTrace();
}
demo.func3();
}
}
人都说Spring的配bean劝退了不少初学者,确实怪麻烦的,不过自从换了IDEA之后啊,腰也不疼了,腿也不酸了(走错片场了额 )倒是节省了不少功夫。
Spring映射文件添加aop命名空间,编写aop映射雏形。在官方帮助文档可以查看或CRTL+C(\滑稽)
<?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">
</beans>
前置通知、后置通知、异常抛出、还有环绕通知这四个类,在这里一并写好,后面的就交托给Spring映射文件里配bean处理
- 前置通知,这里实现接口MethodBeforeAdvice,为了Schema-base方式下实现
-
import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class MyBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("执行前置通知"); } }
- 后置通知,也实现了一个接口,原因往上看
-
import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; public class MyAfterAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable { System.out.println("执行后置通知"); System.out.println("切面编程运行成功"); //测试切面结果 } }
- 异常通知:在这个类里有两个方法,第二个方法是为了测试AsceptJ方式而编写的,后续测试中松开注释即可
-
import org.springframework.aop.ThrowsAdvice; import java.rmi.RemoteException; public class MyThrowAdvice implements ThrowsAdvice { //多个参数只有最后一个异常类型参数是必须传入的 //参数必须一个或四个 public void afterThrowing(Exception ex) throws Throwable{ System.out.println("执行异常通知,schema-base"+ex.getMessage()); } // public void myexception(Exception e){ // System.out.println("执行异常通知,message:"+e.getMessage()); // } }
- 环绕通知:这里引入了一个我也不太明白的东西——拦截器,先不管别跑题了,以后遇见了再研究
-
import org.aopalliance.intercept.MethodInterceptor; //拦截器 import org.aopalliance.intercept.MethodInvocation; public class MyArround implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("环绕-前置通知"); Object result=methodInvocation.proceed(); //放行,调用切点方法 System.out.println("环绕-后置通知"); return result; } }
Schema-base实现方式
- 在此方式下,因为实现类都采用了接口,因此绑定上比较简便些,只需要将全类名写成bean引入即可
一、前置通知,后置通知,异常抛出 三类通知的映射编写,运行时先屏蔽 func2() 方法中设置的异常,因为当触发异常时,程序会中断执行,走不到后置通知。看每个advice-ref 就可以字面翻译出功能类型。
<bean id="mybefore" class="com.yang.advice.MyBeforeAdvice"> </bean>
<bean id="myafter" class="com.yang.advice.MyAfterAdvice"></bean>
<aop:config>
<aop:pointcut id="mypoint" expression="execution(* com.yang.test.Demo.func2())"/>
<aop:advisor advice-ref="mybefore" pointcut-ref="mypoint"/>
<aop:advisor advice-ref="myafter" pointcut-ref="mypoint"/>
<aop:advisor advice-ref="mythrow" pointcut-ref="mypoint"/>
</aop:config>
func1
执行前置通知
func2
执行后置通知
切面编程运行成功
func3
func1
执行前置通知
执行异常通知,schema-base/ by zero
func3
二、环绕通知:其意思大概就是 前置通知+后置通知,实现方式也挺简便
<bean id="myarround" class="com.yang.advice.MyArround"> </bean>
<aop:config>
<aop:pointcut id="mypoint" expression="execution(* com.yang.test.Demo.func2())"/>
<aop:advisor advice-ref="myarround" pointcut-ref="mypoint"/>
</aop:config>
func1
环绕-前置通知
func2
环绕-后置通知
func3
AsceptJ实现方式
- 这种实现方式下,通知类的编写比较随意,只要写一个方法就行,不需要实现接口,但映射文件里的绑定匹配比较严密,要具体到某一个类下的某一个方法。
在这里为了方便管理,把所有的通知方法写入一个MyAdvice 类下,直接用AsceptJ方式一个一个引入。
package com.yang.advice;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAdvice {
public void mybefore(){
System.out.println("这是一个前置通知");
}
public void myafter(){
System.out.println("这是一个后置通知");
}
public void myaftering(){
System.out.println("这是afterReturning式的后置通知");
}
public void mythrow() {
System.out.println("切点在这里抛出了一个异常");
}
public Object myarround(ProceedingJoinPoint p) throws Throwable {
System.out.println("这里执行了环绕前置通知");
Object result=p.proceed(); //放行
System.out.println("这里执行了环绕后置通知");
return result;
}
}
映射部分则分标签逐步定位到每一个类下的每一个方法,需要注意的是:第一、after 和 after-returning 同代表后置通知,但在映射里谁靠前先执行谁。第二、after-returning只能在切点正常运行下才会执行,而after则无论有没有异常都会执行
<bean id="demo" class="com.yang.test.Demo"> </bean>
<bean id="myadvice" class="com.yang.advice.MyAdvice"> </bean>
<aop:config>
<aop:aspect ref="myadvice">
<aop:pointcut id="mypoint" expression="execution(* com.yang.test.Demo.func2())"/>
<aop:before method="mybefore" pointcut-ref="mypoint"/>
<!--这里的先后顺序决定执行顺序-->
<aop:after method="myafter" pointcut-ref="mypoint"/>
<!--after-returning只能在切点正常运行下执行
而after则是无论有没有异常抛出都会执行-->
<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>
func1
这是一个前置通知
这里执行了环绕前置通知
func2
这里执行了环绕后置通知
这是afterReturning式的后置通知
这是一个后置通知
func3
带参数的切点
如果切点带参数,则在映射里需要给绑定的方法添加参数,否则无法匹配
<aop:pointcut id="mypoint1" expression="execution(* com.yang.test.Demo.func2(String)) and args(name1)"/>
另外,如果通知方法中需要传参数,则应在对用通知的映射中添加 arg-names 属性
(2020.05.12添加)
通过注解完成切面
这种方式要先在applicationContext.xml中引入context命名空间
<!--扫描可能存在注解的包-->
<context:component-scan base-package="com.duebass.advice,com.duebass.test"> </context:component-scan>
<aop:aspectj-autoproxy expose-proxy="true"> </aop:aspectj-autoproxy>
随后配置注解,切点处
@Component
public class Demo {
@Pointcut("execution(* com.duebass.test.Demo.func1())") //切点注解
public void func1(){
// int i=5/0;
System.out.println("func1");
}
}
通知处
@Component
@Aspect //表示该类是个切面通知类
public class MyAdvice {
@Before("com.duebass.test.Demo.func1()")
public void mybefore() {
System.out.println("前置通知执行成功");
}
}
到这就暂时就无了无了无了...