AOP(Aspect Oriented Programming)面向切面编程
概述:
AOP即面向切面编程,其本质是动态代理,
指在委托类前/后/前后进行处理,从而达到对目标实现类某一模块功能的扩充,
AOP的核心是切面(切点+通知)
AOP的实现方式:
1.基于Schema-based的传统方式实现(实现相应的通知接口)
2.基于XML文件AspectJ方式实现
3.基于Annotation注解AspectJ方式实现
一、AOP的实现方式–基于Schema-based的传统方式实现(实现相应的通知接口)
1. 目标实现类定义
CalculateImpl.java
(功能:用于实现简单的加减乘除方法)
package com.xxx.aspectj.xml;
public class CalculateImpl {
public int add(int a, int b) {
int c=a+b;
//int d=5/0;
return c;
}
public int subtract(int a, int b) {
int c=a-b;
return c;
}
public int multiply(int a, int b) {
int c=a*b;
return c;
}
public int divide(int a, int b) {
int c=a/b;
return c;
}
}
2.相应的通知实现类(有5个类型的通知:前置通知(MethodBeforeAdvice)、后置通知(AfterReturningAdvice)、环绕通知(MethodInterceptor)、异常通知(ThrowsAdvice)、最终通知(AfterMethod),通过实现相应的接口以及重写接口中相应的方法来实现)
通知类型 | 实现接口名称以及重写的方法名称 |
---|---|
前置通知 | MethodBeforeAdvice 接口,重写接口中的before(Method arg0, Object[] arg1, Object arg2)方法 |
后置通知 | AfterReturningAdvice ,重写接口中的afterReturning(Object returnValue, Method method, Object[] args, Object target)方法 |
环绕通知 | MethodInterceptor 重写接口中的invoke(MethodInvocation arg0)方法 |
异常通知 | ThrowsAdvice ,重写接口中的afterThrowing (Exception ex)方法 |
最终通知 | AfterMethod |
2-1 前置通知实现类
package com.xxx.schema;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.aop.MethodBeforeAdvice;
public class BeforeAdvices implements MethodBeforeAdvice{
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("切面方法对象是"+arg0);
System.out.println("切面方法参数列表是"+Arrays.asList(arg1));
System.out.println("切面方法应用的对象(实现类)是"+arg2);
System.out.println("前置通知执行");
}
}
2-2 后置通知实现类
package com.xxx.schema;
import java.lang.reflect.Method;
import org.springframework.aop.*;
public class AfterReturningsAdvice implements AfterReturningAdvice{
/*
* 实现后置通知接口
* 重写接口中的afterreturning方法
* 该方法的第一个参数是值目标方法对象返回的结果
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("后置通知执行");
System.out.println("执行结果是"+returnValue);
}
}
2-3 环绕通知实现类
package com.xxx.schema;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class Arounds implements MethodInterceptor{
/*
* 实现环绕通知接口
* 重写接口中的invoke方法(调用)
* 方法第一个参数指的是目标方法对象执行环绕通知后的结果
* 过程:前置通知
* 环绕通知中的前置通知
* 环绕通知中的后置通知
* 后置通知
* 假如在环绕通知的前置通知执行的过程中发生异常了,不会执行之后的
* 会执行异常通知和最终通知
*/
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
System.out.println("前置通知执行中");
Object proceed = arg0.proceed();//得到一个objrct类型,指的是目标对象方法执行环绕通知后的结果
System.out.println("环绕通知的结果"+proceed);
System.out.println("后置通知执行中");
return proceed;
}
}
2-4 异常通知实现类
package com.xxx.schema;
import org.springframework.aop.ThrowsAdvice;
public class ThrowAdvice implements ThrowsAdvice{
//注意异常类型
public void afterThrowing(Exception ex) throws Throwable {
System.out.println("异常信息是"+ex.getMessage());
System.out.println("异常通知执行");
}
}
2-5 最终通知实现类
package com.xxx.schema;
import org.springframework.aop.AfterAdvice;
public class Afters implements AfterAdvice{
}
3. .XML文件的配置
<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/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 使用Schema-based方式实现Aop -->
<!--实现5个类对象:
1.目标对象实现类实例化
2.前置通知实现类
3.后置通知实现类
4.异常通知实现类
5.环绕通知实现类
-->
<bean id="calculateImpl" class="com.xxx.schema.CalculateImpl"></bean>
<!-- 实例化通知对象,把通知类注入到IOC容器中 ,在切面中引用 -->
<bean id="before" class="com.xxx.schema.BeforeAdvices"></bean>
<!-- 注意:最终通知实现类没有重写方法 -->
<!-- <bean id="after" class="com.xxx.schema.Afters"></bean> -->
<bean id="throw" class="com.xxx.schema.ThrowAdvice"></bean>
<bean id="around" class="com.xxx.schema.Arounds"></bean>
<bean id="afterreturn" class="com.xxx.schema.AfterReturningsAdvice"></bean>
<!-- 配置AOP,把切面织入到目标对象 -->
<!-- 注意使用到对应的命名空间 -->
<aop:config><!-- 根元素:配置一个切面(使目标实现类的功能增强):切点(何时)+通知(增强何地)-->
<!-- 配置切点(切入点)(何时:时间),*(代表的是合格类中的所有方法)是通配符(指的是方法),方法参数(..)(不管这个方法有几个参数都可以) -->
<!-- 使用属性expression配置切点何时的表达式
public(公有方法)(可以使用*),返回类型是int -->
<!-- 该切点的范围是整个切面,所有切面 -->
<aop:pointcut expression="execution(public int com.xxx.schema.CalculateImpl.*(..))" id="point"/>
<!-- 配置通知(增强)(何地:哪个地方,什么方法)
advice-ref:引用的通知对象(bean元素的id属性)
pointcut-ref:引用切点对象(切点<aop:pointcut/>的id属性)
-->
<!-- <aop:advisor advice-ref="before" pointcut-ref="point"/> -->
<!-- <aop:advisor advice-ref="after" pointcut-ref="point"/> -->
<!-- <aop:advisor advice-ref="throw" pointcut-ref="point"/>
<aop:advisor advice-ref="around" pointcut-ref="point"/> -->
<aop:advisor advice-ref="afterreturn" pointcut-ref="point"/>
</aop:config>
</beans>
4.测试类
package com.xxx.schema;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestSchema {
public static void main(String[] args) {
//加载配置文件
//实例化IOC容器对象
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
//根据IOC容器对象来实例化对象
CalculateImpl cal=(CalculateImpl) ac.getBean("calculateImpl");//参数是bean元素的id属性的值
System.out.println(cal.add(2, 2));//调用其中的一个目标对象方法
}
}
二、AOP的实现方式–基于XML文件AspectJ方式实现
1. 目标实现类定义
CalculateImpl.java
(功能:用于实现简单的加减乘除方法)
略(跟第一种方式类似)
2.切面类
MyAspect.java
(该类的功能用于声明五大通知的方法)
package com.xxx.aspectj.xml;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
//在Java文件中选择类名,然后Copy Qualified Name;
//怎样访问当前连接点的细节
/*
* 前置通知,一个参数:连接点(每个方法可以对应一个连接点)
*/
public void one(JoinPoint jp){
String name = jp.getSignature().getName();//方法的名字
Object[] args = jp.getArgs();//方法的参数列表
System.out.println(name+"前置通知执行"+",参数列表是"+Arrays.asList(args));
}
/*
* 后置通知
* 有两个参数:第一个是连接点
* 第二个参数是返回的结果
*/
public void two(JoinPoint jp,Object re){
String name = jp.getSignature().getName();
System.out.println("后置通知执行的返回结果是"+re);
}
/*
* 异常通知
* 该方法有两个参数
* 第一个参数是连接点
* 第二个参数是异常通知执行的异常信息
*/
public void three(JoinPoint jp,Exception ex){
System.out.println("异常通知执行的异常信息为"+ex);
}
/*
* 最终通知
*/
public void four(){
System.out.println("最终通知执行");
}
/*
* 环绕通知(把所有的通知串起来了)
* 该方法的返回类型是Object(前面四个方法类型是void)
* 有一个参数:ProceedingJoinPoint是JoinPoint的子接口
* 环绕通知的结果通过proceed()方法得到
*
*/
public Object five(ProceedingJoinPoint pjp){
Object[] args = pjp.getArgs();//方法的参数列表
String name = pjp.getSignature().getName();//方法的名字
Object result=null;
try {
System.out.println(name+"环绕准备就绪,参数列表是:"+Arrays.asList(args));
System.out.println("前置通知执行");
result = pjp.proceed();//目标对方方法执行环绕通知的返回结果
System.out.println(name+"的返回结果是:"+result);
System.out.println("后置通知执行");
} catch (Throwable e) {
e.printStackTrace();
System.out.println("异常通知执行");//发生异常时执行
}finally{
System.out.println(name+"环绕执行完毕,参数列表是:"+Arrays.asList(args));
System.out.println("最终通知执行");
}
return result;
}
}
3. .xml文件的配置
<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/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- AspectJ之XML文件方式实现AOP -->
<bean id="calculateImpl" class="com.xxx.aspectj.xml.CalculateImpl"></bean>
<bean id="aspects" class="com.xxx.aspectj.xml.MyAspect" />
<!-- 配置切面(切点(地点)+通知(时间) -->
<aop:config>
<aop:aspect id="aspect" ref="aspects">
<!-- 配置切点表达式,语法:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
问号表示当前项可以有也可以没有,其中各项的语义如下:
modifiers-pattern:方法的可见性,如public,protected;
ret-type-pattern:方法的返回值类型,如int,void等;
declaring-type-pattern:方法所在类的全路径名(方法所在的完全限定名路径),如com.spring.Aspect;
name-pattern:方法名类型,如buisinessService();
param-pattern:方法的参数类型,如java.lang.String;
throws-pattern:方法抛出的异常类型,如java.lang.Exception;
-->
<!-- 配置切点-->
<!-- 该切点的范围是针对当前的切面 -->
<aop:pointcut expression="execution(* com.xxx.aspectj.xml.CalculateImpl.add(..)) " id="pointcut"/>
<!-- 配置不同的通知(增强)类型对应不同的子元素
属性method是切面类中对应方法的名称
属性pointcut-ref是切点的id的值
-->
<!--环绕通知配置 -->
<aop:around method="five" pointcut-ref="pointcut" />
<!-- 后置通知配置 -->
<!-- <aop:after-returning method="two" pointcut-ref="pointcut" returning="re"/>
前置通知配置
<aop:before method="one" pointcut-ref="pointcut" />
异常通知配置
<aop:after-throwing method="three" pointcut-ref="pointcut" throwing="ex"/>
最终通知配置
<aop:after method="four" pointcut-ref="pointcut" />
-->
</aop:aspect>
</aop:config>
</beans>
4.测试类
略(跟第一种方式类似)
三、AOP的实现方式–基于Annotation注解AspectJ方式实现
1. 目标实现类定义
CalculateImpl.java
(功能:用于实现简单的加减乘除方法)
略(跟第一种方式类似)
2.切面类
MyAspect.java
(该类的功能用于声明五大通知的方法)
package com.xxx.aspectj.annotation;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
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.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component//使用该注解,说明已经将该类放入了Ioc容器,已经实例化了一个类
@Aspect//使用这两个注解,说明该类是一个切面类
//通过@Order(*)标注指定切面优先级,*整数数值越小,优先级越高
public class MyAspect {
//定义切点(地点:使用切点表达式来确定)
/*假如每个切点表达式是一样的
那么,我们可以定义一个方法,当想使用的时候可以直接引用*/
@Pointcut("execution(* com.xxx.aspectj.annotation.CalculateImpl.*(..))")
public void pointcut(){
}
/*
* 当使用同一种类型通知的时候
* 通知的执行顺序和优先级有关(优先级越高就先执行谁)
* 执行顺序和方法名的ASCII码有关,值越低优先级越高
*/
@Before("pointcut()")//该注解是引用了方法:方法名称是ponitcut()
public void beforeone(JoinPoint jp){
String name = jp.getSignature().getName();//调用目标对象中方法的名称
Object[] args = jp.getArgs();//方法的参数列表
System.out.println(name+"前置通知1执行"+",参数列表是"+Arrays.asList(args));
}
/*@Before("execution(* com.xxx.aspectj.annotation.CalculateImpl.*(..))")
public void beforefive(JoinPoint jp){
String name = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println(name+"前置通知2执行"+",参数列表是"+Arrays.asList(args));
}
@After("execution(public int com.xxx.aspectj.annotation.CalculateImpl.*(..))")
public void four(){
System.out.println("最终通知执行");
}*/
@AfterReturning(pointcut="execution(publi0c int com.xxx.aspectj.annotation.CalculateImpl.*(..))",returning="re")
// 注解中第二个属性的值为方法中的第二个形参的名称
public void two(JoinPoint jp,Object re){
String name = jp.getSignature().getName();
System.out.println(name+"后置通知执行的返回结果是"+re);
}
/*@AfterThrowing(pointcut="execution(public int com.xxx.aspectj.annotation.CalculateImpl.*(..))",throwing="ex")
// 注解中第二个属性的值为方法中的第二个形参的名称
public void three(JoinPoint jp,Exception ex){
System.out.println("异常通知执行的异常信息为"+ex);
}*/
/*@Around("execution(public int com.xxx.aspectj.annotation.CalculateImpl.*(..))")
public Object five(ProceedingJoinPoint pjp){
Object[] args = pjp.getArgs();
String name = pjp.getSignature().getName();
System.out.println(name+"环绕前置就绪,参数列表是:"+Arrays.asList(args));
Object result=null;
try {
result = pjp.proceed();
System.out.println(name+"的环绕返回结果是:"+result);
} catch (Throwable e) {
e.printStackTrace();
System.out.println("异常通知执行");
}finally{
System.out.println(name+"环绕后置执行完毕,参数列表是:"+Arrays.asList(args));
}
return result;
}*/
}
3. .xml文件配置(虽然使用了注解,但是spring不自动寻找带注解的类,需要告诉哪些包中存在带注解的类)
<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/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- AspectJ之注解方式实现AOP -->
<!-- spring不自动寻找带注解的类,需要告诉哪些包中存在带注解的类 -->
<!-- 注意引用相应的命名空间-->
<context:component-scan base-package="com.xxx.aspectj.annotation"></context:component-scan>
<!-- aop使用了动态代理
元素aop:aspectj-autoproxy的属性
1.expose-proxy="false"
2.proxy-target-class="false"
-->
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
</beans>
4.实现类
略(和第一种方式类似)
注:
1.当使用同一种类型的通知时,执行顺序和优先级有关
和方法的ASCII码的值有关,值越小越优先执行
2.当通知的切点表达式是一样的时候,可以通过声明一个方法来定义切点
当使用的时候直接调用即可