1、Spring AOP配置所需包
2、面向切面编程(AOP): 面向切面编程就是在原有纵向执行的程序中,对某个或者某一些方法添加横向通知形成横向切面的过程。
2.1、原有功能方法称为切点 - pointCut
2.2、前置通知:在切点之前执行的方法 - before advice
2.3、后置通知:在切点之后执行的方法 - after advice
2.4、异常通知:在切点发生异常的时候才会执行 - throw advice
2.5、以上四点称为切面
2.6、将以上方法嵌入到原有方法中的过程称为织入。
3、在本文中主要介绍通过Schema-based和AspectJ的方式实现AOP的过程:
3.1、Schema-based方式:
3.1.1、手写通知类对象,实现相应的接口,重写接口方法,在重写的方法中实现通知的功能;
3.1.2、在Spring配置文件中使用标签aop:config</aop:config>进行配置。
3.2、AspectJ方式:
3.2.1、在Spring配置文件中使用标签aop:config</aop:config>进行配置;
3.2.2、在Spring配置文件中使用aop:config
aop:aspect</aop:aspect>
</aop:config>标签进行配置
4、Schema-based方式实现前置通知、后置通知、异常通知及环绕通知
4.1、前置通知对象
BeforeAdvisor.java:
package Advisor;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
* 前置通知:使用类实现前置通知接口(MethodBeforeAdvice)的方式
*/
public class BeforeAdvisor implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("前置通知设置!");
/**
* 前置通知重写方法中的参数:
* Method method:切点方法对象
* Object[] objects:切点方法参数列表
* Object o:切点方法所属对象
*/
System.out.println("切点方法对象:"+method.getName());
if(objects.length > 0){
System.out.println("切点方法参数列表:"+objects.length);
}else{
System.out.println("该切点方法没有参数!");
}
System.out.println("该切点方法所属对象:"+o);
}
}
实现MethodBeforeAdvice 接口,重写接口中的before(…)方法.
4.2、后置通知对象:
AfterAdvisorDemo.java
package Advisor;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterAdvisorDemo implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
/**
* 后置通知方法中的参数;
* Object o:切点方法返回值
* Method method:切点方法对象
* Object[] objects:切点方法的参数
* Object o1:切点方法所属对象
*/
System.out.println("后置通知设置!");
System.out.println("切点方法返回值:"+o);
System.out.println("切点方法对象:"+method.getName());
if(objects.length > 0){
System.out.println("切点方法参数数组:"+objects.length);
}else{
System.out.println("切点方法没有参数##########");
}
System.out.println("切点方法所属对象:"+o1);
System.out.println("===================<后置通知结束>===================");
}
}
后置通知对象实现AfterReturningAdvice 接口,重写afterReturning(…)接口方法。
4.3、异常对象:
ThrowAdvisor .java
package Advisor;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
public class ThrowAdvisor implements ThrowsAdvice {
/**
* 使用Schema-based方式实现ThrowsAdvice接口,配置异常通知的方法,需要手写方法afterThrowing(...)方法,
* 方法名必须叫afterThrowing.
*/
/*public void afterThrowing(Exception ex){
System.out.println("使用Schema-based实现配置异常通知");
}*/
/**
* 多参数的异常方法
*/
public void afterThrowing(Method method, Object[] o1, Object o2, Exception ex) throws Throwable{
System.out.println("调用异常的方法是:"+method.getName());
}
}
注:使用Schema-based方式实现ThrowsAdvice**接口,配置异常通知的方法,需要手写方法afterThrowing(…)方法,
方法名必须叫afterThrowing.
4.4、环绕通知对象:
AroundAdvisor.java:
package Advisor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/***
* Schema-based方式实现环绕通知,需要实现MethodInterceptor接口,重写invoke(..)方法
*/
public class AroundAdvisor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("Schema-based方式配置的环绕通知模拟的前置通知!!!");
Object obj = methodInvocation.proceed(); //调用切点方式,放行
System.out.println("Schema-based方式配置的环绕通知模拟的后置通知");
return obj;
}
}
Schema-based方式实现环绕通知,需要实现MethodInterceptor接口,重写invoke(…)方法
4.5、Spring配置文件:
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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
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-4.0.xsd">
<!--aop:config:面向切面编程配置-->
<!--前置通知对象创建-->
<bean id="beforeAdvisor" class="Advisor.BeforeAdvisor"></bean>
<!--后置通知对象创建-->
<bean id="afterAdvisor" class="Advisor.AfterAdvisorDemo"></bean>
<!--异常通知对象创建-->
<bean id="throwAdvisor" class="Advisor.ThrowAdvisor"></bean>
<!--环绕通知配置-->
<bean id="aroundAdvisor" class="Advisor.AroundAdvisor"></bean>
<aop:config>
<!--首先配置切点-->
<!--expression:该属性值是一个固定的表达式
* 通配符是使用:可以指定对象下的所有方法:
Demo.SpringAOPTest.*(..):SpringAOPTest对象中的所有方法都设置了前置通知和后置通知,其中(..)的意思是任意参数的方法
类似的也可以使用通配符(*)指定任意包名、任意类名
-->
<aop:pointcut id="demo02" expression="execution(* Demo.*.*(..))"></aop:pointcut>
<!--配置前置通知-->
<!--
<aop:advisor advice-ref="beforeAdvisor" pointcut-ref="demo02"></aop:advisor>
-->
<!--配置后置通知-->
<!--
<aop:advisor advice-ref="afterAdvisor" pointcut-ref="demo02"></aop:advisor>
-->
<!--异常通知配置-->
<!--
<aop:advisor advice-ref="throwAdvisor" pointcut-ref="demo02"></aop:advisor>
-->
<!--环绕通知配置-->
<aop:advisor advice-ref="aroundAdvisor" pointcut-ref="demo02"></aop:advisor>
</aop:config>
<!--创建目标类对象-->
<bean id="springAop" class="Demo.SpringAOPTest"></bean>
<bean id="AOPDemo02" class="Demo.AOPTestDemo02"></bean>
</beans>
4.6、测试类
package Demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args){
ApplicationContext ac = new ClassPathXmlApplicationContext("ApplicationContext.xml");
/*SpringAOPTest sat = ac.getBean("springAop",SpringAOPTest.class);
sat.Demo01();
sat.Demo02();
sat.Demo03();*/
/**
* 运行结果:
这是Demo01!!!
前置通知设置!
这是Demo02!!!
后置通知设置!
这是Demo03!!!
*/
AOPTestDemo02 aop = ac.getBean("AOPDemo02",AOPTestDemo02.class);
//aop.Demo01("张三");
String age = null;
//此处需要捕获异常
try {
age = aop.Demo02(21);
} catch (Throwable throwable) {
}
System.out.println(age);
/**
* AspectJ方式配置的异常通知
*/
/*AspectJAdvisorDemo aad = ac.getBean("aspectJPointCut",AspectJAdvisorDemo.class);
String name = null;
try {
name = aad.Demo01("这是AspectJ方式配置的异常通知!!!");
} catch (Throwable throwable) {
//throwable.printStackTrace();
}
System.out.println(name);*/
}
}
5、AspectJ方式配置AOP:
5.1、使用AspectJ配置通知时,前置通知、后置通知、异常通知方法是在一个类中的,并且不用实现任何接口。
AspectJ通知对象:
AspectJAdvisorAll.java
package Advisor.AspectJ;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 使用AspectJ配置通知,所有通知方法最好写在写在一个类中,方便配置。
* 通知方法不用实现相关通知接口
*/
public class AspectJAdvisorAll {
/**
* 前置通知
*/
public void beforeAdvisor(){
System.out.println("无参的前置通知方法!!!");
}
/**
* 前置通知(一个参数)
*/
public void beforeAdvisor1(String name){
System.out.println("一个参数的前置通知方法!!!"+name);
}
/**
* 前置通知(多个参数)
*/
public void beforeAdvisor2(String name1,int age1){
System.out.println("两个参数的前置通知方法!!!"+name1+" : "+age1);
}
/**
* 后置通知
*/
public void afterAdvisoring(){
System.out.println("只有当切点方法无异常才会执行的后置通知!!!");
}
/**
* 后置通知
*/
public void afterAdvisor(){
System.out.println("切点方法有无异常都执行的后置通知!!!");
}
/**
* 异常通知配置
*/
public void throwAdvisor(Exception e){
System.out.println("这是一个异常通知!!!"+e.getMessage());
}
/**
* 环绕通知
*/
public Object aroundAdvisor(ProceedingJoinPoint pjp,String name) throws Throwable {
System.out.println("这是"+name+"de环绕通知!!!--- start");
System.out.println("这是"+name+"de环绕通知模拟的前置通知!!!");
Object obj = pjp.proceed();
System.out.println("这是"+name+"de环绕通知模拟的后置通知!!!");
return obj;
}
}
5.2、Spring配置文件配置(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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
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-4.0.xsd">
<!--使用AspectJ方式实现异常通知配置-->
<!-- <aop:config>
<aop:aspect ref="ObjectThrows">
<!--在<aop:aspect>标签中引入异常通知对象,只有当切点方法发生异常时调用这个异常对象-->
<aop:pointcut id="aspectJPoint" expression="execution(* Advisor.AspectJ.AspectJAdvisorDemo.*(..))"></aop:pointcut>
<!--异常通知配置:method="ThrowsAdvisor": 异常通知方法名配置;
pointcut-ref="aspectJPoint":引入切点对象
throwing="exx":异常方法中的参数名配置,此处配置异常的变量名,在异常方法中的参数变量名就必须与此保持一致
-->
<aop:after-throwing method="ThrowsAdvisor" pointcut-ref="aspectJPoint" throwing="exx"></aop:after-throwing>
</aop:aspect>
</aop:config>-->
-----------------------------------------------------------------------
<!--AspectJ配置通知总结-->
<!--AspectJ切点方法-->
<bean id="aspectJDemo" class="Demo.AspectDemo"></bean>
<!--通知方法所在对象-->
<bean id="aspectJAllAdvisor" class="Advisor.AspectJ.AspectJAdvisorAll"></bean>
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* Demo.AspectDemo.Demo01())"></aop:pointcut>
<aop:pointcut id="pointCut1" expression="execution(* Demo.AspectDemo.Demo02(String)) and args(name)"/>
<aop:pointcut id="pointCut2" expression="execution(* Demo.AspectDemo.Demo03(String,int)) and args(name1,age1)"/>
<aop:aspect ref="aspectJAllAdvisor">
<aop:before method="beforeAdvisor" pointcut-ref="pointCut"></aop:before>
<!--<aop:before method="beforeAdvisor1" pointcut-ref="pointCut1" arg-names="name"/>-->
<aop:before method="beforeAdvisor2" pointcut-ref="pointCut2" arg-names="name1,age1"/>
<!--后置通知-->
<aop:after-returning method="afterAdvisoring" pointcut-ref="pointCut"></aop:after-returning>
<aop:after method="afterAdvisor" pointcut-ref="pointCut"></aop:after>
<!--异常通知-->
<aop:after-throwing method="throwAdvisor" pointcut-ref="pointCut" throwing="e"></aop:after-throwing>
<!--环绕通知-->
<aop:around method="aroundAdvisor" pointcut-ref="pointCut1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
注:
此处需要注意的是,1、配置的切点方法不能使用 (…) 参数通配符,如果切点方法有参数要注明参数类型和参数个数
2、切点方法可以配置多个,切点方法参数个数及类型要与通知的参数个数和类型等信息一致才能使用,不然会报错
3、如果通知织入的切点有参数,通知方法可以不配置参数,要是通知方法需要配置参数,参数名称要与切点配置的参数一致,并且通知方法中的参数名称也要与配置的一致
4、此处需要注意,要是需要配置多个切点方法,要把aop:pointcut/标签放在aop:aspect标签外边;
5、后置通知有两种aop:after、aop:after-returning;区别是:aop:after:切点方法有无异常都会执行;aop:after-returning:只有切点方法无异常时才会执行
6、aop:after、aop:after-returning、aop:after-throwing配置的顺序影响执行的顺序;
7、环绕通知方法需要参数ProceedingJoinPoint(必须的),也可以使用自定义的参数
5.3、测试方法:
```bash
package Demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AspectJTestDemo {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("ApplicationContext.xml");
AspectDemo pd = ac.getBean("aspectJDemo",AspectDemo.class);
/*pd.Demo02("一个参数");
System.out.println("++++++++++++++++++++++++++++++++++++");
pd.Demo03("张三",21);*/
/*try {
pd.Demo01();
} catch (Throwable throwable) {
//throwable.printStackTrace();
}*/
/**
* 环绕通知
*/
pd.Demo02("1122366945");
}
}