一、简介
SpringAop 2.X 中的增强类,创是基于命名空间(方法空间中,给方法的参数配置指定的参数)的配置,原理是使用后处理器(即:是通过后置处理器直接来修改目标对象,例如:BeanPosProcessor),更简单,具有以下特点:
- 简化配置
- 非侵入性:编写通知时不需要实现任何接口
- 使用AspectJ表达式定义切点
二、基本用法
(一) 配置Advice
定义增强类,不需要实现任何接口,但有多种写法:
写法 | 说明 |
---|---|
public void 方法名(JoinPoint) | 前置增强 |
public void 方法名(JoinPoint,Object) | 后置增强 |
public void 方法名(JoinPoint,Exception) | 异常增强 |
public Object 方法名(ProceedingJoinPoint) | 环绕增强 |
下面的代码示例为自定义增强类,自定义增强类不需要实现任何接口,且类名 和里面的方法名都可以是依据自己随意定义,但方法中的参数依据上表格中的不同来确明方法是前置、后置、环绕和异常增强,代码示例如下:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
/**
* ClassName: LogAdvice;
* User: FWJWORK;
* PackageName: aop2.advice;
* Date:2021/6/28;
* Time:17:59;
* Descritpion: 增强类
*/
// TODO: 2021/6/28 在springaop2.x中,增强类的定义有了进一步简化,不需要实现于接口,类名是自已随意定义
// 同时可以在一个类中定义前置、后置、环绕及异常增强方法,定义的方法时,参数的不同只是匹配位置的需要,
// 不能仅凭参数不一样而确定在方位法增强的位置,还需要在配置文件中进行指定。
public class LogAdvice {
// TODO: 2021/6/28 这个方法是前置增强方法,是一个普通的方法,方法名依据自己随意取名,参数的决定了他只能匹配前置增强
public void beforeAdvice(JoinPoint joinPoint) {
// TODO: 2021/6/28 获得签名
Signature signature = joinPoint.getSignature();
// TODO: 2021/6/28 得到方法结构信息的方法名
String name = signature.getName();
// TODO: 2021/6/28 获得方法签名
MethodSignature methodSignature = (MethodSignature) signature;
// TODO: 2021/6/28 从方法签名中得到方法结构信息
Method method = methodSignature.getMethod();
// TODO: 2021/6/28 得到方法的参数信息
Object[] args = joinPoint.getArgs();
// TODO: 2021/6/28 得到目标对像,可以通过下面两种方法来实现
// Object aThis = joinPoint.getThis();
Object target = joinPoint.getTarget();
// TODO: 2021/6/28 输出方法名,参数及目标对象
System.out.println("beforeAdvice-MethodName:" + name + ";args:" + args + ";target:" + target + ";");
}
// TODO: 2021/6/28 方法名可以随意定,但要便于理解,方法参数决定了访方法只能匹配后置增强,具体还需有要配置文件中<aop:after-runing>进行配合
public void afterAdvice(JoinPoint joinPoint, Object returesult) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
String methodSignatureName = methodSignature.getName();
Object[] args = joinPoint.getArgs();
Object aThis = joinPoint.getThis();
System.out.println("afterAdvice-MethonName:" + methodSignatureName + ";args:" + args + ";target:" + aThis + ";returnResult:" + returesult);
}
// TODO: 2021/6/28 此方法为环绕增强,方法参数决定了访方法只能用于环绕增强,具体还需在配置文件中进行配置
public Object beforeAndAfterAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// TODO: 2021/6/28 前轩增强代码
Object[] args = proceedingJoinPoint.getArgs();
Object target = proceedingJoinPoint.getTarget();
Signature signature = proceedingJoinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
String methodSignatureName = methodSignature.getName();
long startTime = System.currentTimeMillis();
System.out.println("beforeProceeding-MethonName:" + methodSignatureName + ";args:" + args + ";target:" + target);
// TODO: 2021/6/28 环绕增强的分界代码,此代码之前为前置增强代码,之后为后置增强代码
Object proceed = proceedingJoinPoint.proceed();
// TODO: 2021/6/28 下面的代码为环绕增强后置代码,也就是在目标对象方法执行完比后,再执行的代码
Thread.sleep(1000);
long endTime = System.currentTimeMillis();
System.out.println("afterProceeding-MethonName:" + methodSignatureName + ";args:" + args + ";target:" + target + ";runTime=" + (endTime - startTime));
return proceed;
}
// TODO: 2021/6/28 此方法为异常增强,方法名可以随意定义,但一定要便于理解,方法参数决定了该方法只能用于异常增强,还需在有配置文件中进行配置
public void exceptionAdvice(JoinPoint joinPoint,Exception exception){
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
String methodSignatureName = methodSignature.getName();
Object[] args = joinPoint.getArgs();
Object aThis = joinPoint.getThis();
System.out.println("exception-MethonName:" + methodSignatureName + ";args:" + args + ";target:" + aThis + ";Exception:" + exception);
}
}
(二) 配置Pointcut(切入点)并织入
在SpringAop2.X中,指定切入点也是基于命名空间来确定的,他是基于AspectJ表达式表达式(切点表达式),主要用来定义切点的位置。他主要用两种用法
- within
语法:within(包名.类名) 他能匹配该类名所有的方法,同时,他还可以使用通配符: * - execution
语法: execution(表达式) 他能匹配特定包中的特定类中特定返回值类型的特定参数的特定方法。其里面的表达式可以是:
- 返回值类型 包名.类名.方法名(参数类型)
- 可以运用通配符 * 和 …(两个点)
参考配置示例如下:
<!-- TODO:这个地方是定义切入点,采取的是 AspectJ表达式来指定切入点的位置-->
<!-- TODO:配置切入点,还有另外一个方法,即:execution(),他可以细化到具体的切入点(方法),可以使用通配符* 和 .. -->
<!-- TODO:execution()方法的参数可以是:方法返回值类型(void *),包名.类名.方法名(参数类型 通配符..) -->
<!-- TODO:具体示例 execution(void aop2.UserServiceImpl.showInfo(String) -->
<aop:pointcut id="pc" expression="within(aop2.impl.UserServiceImpl)"/>
<aop:pointcut id="pc2" expression="execution(void aop2.impl.*(..))"/>
(三)核心配置文件xml中的配置细节
SpringAop2.X中,不需要指定代理类生成类(字节码操作工具类),是因为采取了后置处理器:BeanPosProcessor,在这个后置处理器中,将AOP容器中的Bean传递进入后置处理器,后置处理器通过字节码操作工作:Enhandler来重新生成一个代理类,并返回,这个代理类是目标对象(被代理类)的字代,所以可以采取多态形式。在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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- TODO:配置目标对象,即被代理对象,这个配置的目标对象其实是被后置处理器BeanPosProcessor处理过的,重新生成的字节码所产生代理类 -->
<bean id="userserviceimpl" class="aop2.impl.UserServiceImpl"/>
<!-- TODO:配置增强类,此处的增强类没有实现与任何接口,但里面的增强方法参数决定了该方法只能适用于那个位置的增强,具体还得和配置文件中相对应的设置 -->
<bean id="userserviceadvice" class="aop2.advice.LogAdvice"/>
<!-- TODO:配置切入点 -->
<aop:config>
<!-- TODO:这个地方是定义切入点,采取的是 AspectJ表达式来指定切入点的位置-->
<!-- TODO:配置切入点,还有另外一个方法,即:execution(),他可以细化到具体的切入点(方法),可以使用通配符* 和 .. -->
<!-- TODO:execution()方法的参数可以是:方法返回值类型(void *),包名.类名.方法名(参数类型 通配符..) -->
<!-- TODO:具体示例 execution(void aop2.UserServiceImpl.showInfo(String) -->
<aop:pointcut id="pc" expression="within(aop2.impl.UserServiceImpl)"/>
<aop:pointcut id="pc2" expression="execution(void aop2.impl.*(..))"/>
<!-- TODO:切入点指定完成后,需要指定增强类中那些增强方法用于切入点上,在这个地方需要注意的是,前置、后置、环绕及异常增强方法在增加类中定义时,
方法的参数是不同的,因此,在配置的时候,需确保增强位置与增强类中的方法相对应 -->
<!-- TODO:ref=“”为指定的增强类,也是一个切面 -->
<aop:aspect ref="userserviceadvice">
<!-- TODO:使用增强类中的那个方法(beforeAdvice方法),用在定义切入点(pc)的方法的哪个位置(是前置、后置、环绕不是增强:aop:before) -->
<aop:before method="beforeAdvice" pointcut-ref="pc"/>
<!-- TODO:使用增强类中的后置增强方法,增强类中的方法参数告诉他只能用于后置增强,增强方法用于定义的切点(pc:指向的是一个类的合部方法或者是具体方法),
并将调用目标对象得到的返回结果传递结returning -->
<aop:after-returning method="afterAdvice" pointcut-ref="pc" returning="returesult"/>
<!-- TODO:使用增强类中的异常增强方法,增强类中的方法参数告诉他只能用于异常增强,增强方法用于定义的切点(pc:指向的是一个类的全部方法或者是具体方法),
具体还得根据within()和execution()来决定,并将目标对抛出的异常传递给throwing指定的参数 -->
<aop:after-throwing method="exceptionAdvice" pointcut-ref="pc" throwing="exception"/>
<!-- TODO:环绕增强,使用增强类中的环绕方法,在增强类中定义的环绕前后置的分界代码 -->
<aop:around method="beforeAndAfterAdvice" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
</beans>
(四)运行测试
成生Aop容器,调用容器中的代理对象(目标对象通过后置处理器BeanPosProcessor来形成的代理对象)
@Test
public void test33() {
ApplicationContext ac = new ClassPathXmlApplicationContext("aop2/aop2.xml");
UserServiceInteface userserviceimpl = (UserServiceInteface) ac.getBean("userserviceimpl");
userserviceimpl.showAllInfo();
}