Spring的AOP开发-基于xml配置的AOP
xml方式AOP快速入门
通过配置文件的方式解决以下问题
- 配置哪些包、哪些类、哪些方法需要被增强
- 配置目标方法要被哪些通知方法所增强,在目标方法执行之前还是之后执行增强
配置方式的设计、配置文件(注解),Spring已经帮我们封装好了
xml方式配置AOP的步骤:
1、 导入AOP相关坐标;
2、准备目标类、准备增强类,并配置给Spring管理;
3、配置切点表达式(哪些方法被增强);
4、配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)。
package com.luxifa.service;
public interface UserService {
void show1();
void show2();
}
package com.luxifa.service.impl;
public class UserServiceImpl implements UserService {
@Override
public void show1() {
System.out.println("show1...")
}
@Override
void show2() {
System.out.println("show2...")
}
}
package com.luxifa.advice;
//增强类.内部提供增强方法
public class MyAdvice {
public void beforeAdvice() {
System.out.println("前置的增强...");
}
public void afterAdvice() {
System.out.println("后置的增强...");
}
}
<!--配置目标类-->
<bean id="userService" class="com.luxifa.service.impl.UserServiceImpl"/><bean>
<!--配置的通知类-->
<bean id="myAdvice" class="com.luxifa.advice.MyAdvice"></bean>
<!--aop配置-->
<aop:config>
<!--配置切点表达式,目的是要指定哪些方法被增强-->
<aop:pointcut id="myPoincut" expression="execution(void com.luxifa.service.impl.UserServiceImpl.show1())"/>
<!--配置织入,目的是要执行哪些切点与哪些通知进行结合-->
<aop:aspect ref="myAdvice">
<aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
测试类:
public class ApplicationContextTest {
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = app.getBean(UserService.class);
userService.show1();
}
}
控制台打印:
前置的增强....
show1....
xml方式AOP配置详解
- 切点表达式的配置方式
切点表达式的配置方式有两种,直接将切点表达式配置在通知上,也可以将切点表达式抽取到外面,在通知上进行引用
<aop:config>
<!--配置切点表达式,对哪些方法进行增强-->
<aop:pointcut id="myPoincut" expression="execution(void com.luxifa.service.impl.UserServiceImpl.show1())"/>
<!--切面=切点+通知-->
<aop:aspect ref="myAdvice">
<!--指定前置通知方法是beforeAdvice-->
<aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
<!--指定后置通知方法是afterAdvice-->
<aop:after-returning method="afterAdvice" pointcut="excution(void com.luxifa.service.impl.UserServiceImpl.show1())"/>
</aop:aspect>
</aop:config>
- 切点表达式的配置语法
切点表达式是配置要对哪些连接点(哪些类的哪些方法)进行通知的增强,语法如下:
execution([访问修饰符]返回值类型 包名.类名.方法名(参数))
其中:
- 访问修饰符可以省略不写;
- 返回值的类型、某一级包名、类名、方法名 可以使用*表示任意;
- 包名与类型之间使用单点 . 表示该包下的累,使用双点 … 表示该包及其子包下的类;
- 参数列表可以使用两个点 … 表示任意参数。
切点表达式举例:
//表示访问修饰符为public、无返回值、在com.luxifa.aop包下的TargetImpl类的无参方法show
execution(public void com.luxifa.aop.TargetImpl.show())
//表示com.luxifa.aop包下的TargetImpl类的任意方法
execution(* com.luxifa.aop.TargetImpl.*.(..))
//表示com.luxifa.aop包下的任意类的任意方法
execution(* com.luxifa.aop.*.*(..))
//表示om.luxifa.aop包及其子包下的任意类的任意方法
execution(* com.luxifa.aop..*.*(..))
//表示任意包中的任意类的任意方法
execution(* *..*.*(..))
- 通知的类型
AspectJ的通知由以下五种类型
通知名称 | 配置方式 | 执行时机 |
---|---|---|
前置通知 | aop:before | 目标方法执行之前执行 |
后置通知 | aop:after-returning | 目标方法执行之后执行,目标方法异常时,不再执行 |
环绕通知 | aop:around | 目标方法执行前后执行,目标方法异常时,环绕后方法不再执行 |
异常通知 | aop:after-throwing | 目标方法抛出异常时执行 |
最终通知 | aop:after | 不管目标方法是否有异常,最终都会执行 |
环绕通知:
package com.luxifa.service;
public interface UserService {
void show1();
void show2();
}
package com.luxifa.service.impl;
public class UserServiceImpl implements UserService {
@Override
public void show1() {
System.out.println("show1...")
}
@Override
void show2() {
System.out.println("show2...")
}
}
package com.luxifa.advice;
//增强类.内部提供增强方法
public class MyAdvice {
public void beforeAdvice() {
System.out.println("前置的增强...");
}
public void afterReturningAdvice() {
System.out.println("后置的增强...");
}
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕前的增强...");
//执行目标方法
Object res = proceedingJoinPoint.proceed();
System.out.println("环绕后的增强...");
return res;
}
}
<!--配置目标类-->
<bean id="userService" class="com.luxifa.service.impl.UserServiceImpl"/><bean>
<!--配置的通知类-->
<bean id="myAdvice" class="com.luxifa.advice.MyAdvice"></bean>
<!--aop配置-->
<aop:config>
<!--配置切点表达式,目的是要指定哪些方法被增强-->
<aop:pointcut id="myPoincut" expression="execution(void com.luxifa.service.impl.UserServiceImpl.show1())"/>
<aop:pointcut id="myPoincut2" expression="execution(* com.luxifa.service.impl.*.*(..))"/>
<!--配置织入,目的是要执行哪些切点与哪些通知进行结合-->
<aop:aspect ref="myAdvice">
<!--环绕通知合-->
<aop:around method="around" pointcut-ref="myPointcut2"/>
</aop:aspect>
</aop:config>
通知方法在被调用时,Spring可以为其传递一些必要的参数
参数类型 | 作用 |
---|---|
JoinPoint | 连接点对象,任何通知都可使用,可以获得当前目标对象、目标方法参数等信息 |
ProceedingJoinPoint | JoinPoint子类对象,主要是在环绕通知中执行proceed(),进而执行目标方法 |
Throwable | 异常对象,使用在异常通知类中,需要在配置文件中指出异常对象名称 |
JointPoint对象
public void 通知方法名称(JointPoint joinPoint) {
//获得目标方法的参数
System.out.println(joinPoint.getArgs());
//获得目标对象
System.out.println(joinPoint.getTarget());
//获得精确的切点表达式信息
System.out.println(joinPoint.getStaticPart());
}
ProceedingJoinPoint对象
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//获得目标方法的参数
System.out.println(joinPoint.getArgs());
//获得目标对象
System.out.println(joinPoint.getTarget());
//获得精确的切点表达式信息
System.out.println(joinPoint.getStaticPart());
//执行目标方法
Object result = joinPoint.proceed();
//返回目标方法返回值
return result;
}
Throwable 对象
public void afterThrowing(JointPoint joinPoint,Throwable th) {
//获得异常信息
System.out.println("异常对象是:"+th+"异常信息是:"+th.getMessage());
}
<aop:after-throwing method="afterThrowing" pointcut-ref="mypointcut" throwing="th"/>
- AOP的配置的两种方式
AOP配置的两种语法形式
AOP的xml有两种配置方式,如下:
- 使用advisor配置切面
- 使用aspect配置切面
Spring定义了一个Advice接口,实现了该接口的类都可以作为通知类出现
public interface Advice{
}
advisor需要的通知类需要实现Advice的子功能接口,例如:MethodBeforeAdvice、AfterReturningAdvice等,是通过实现的接口去确定具备哪些通知增强的。
AOP配置的两种语法形式不同点
语法形式不同:
- advisor是通过实现接口来确定通知的类型
- aspect是通过配置确认通知的类型,更加灵活
可配置的切面数量不同:
- 一个advisor只能配置一个固定通知和一个切点表达式
- 一个aspect可以配置多个通知和多个切点表达式任意组合
使用场景不同:
- 允许随意搭配情况下可以使用aspect进行配置
- 如果通知类型单一、切面单一的情况下可以使用advisor进行配置
- 在通知类型已经固定,不用人为指定通知类型时,可以使用advisor进行配置,例如Spring事务控制的配置
xml方式AOP原理剖析
两种生成动态代理对象的方式,一种是基于JDK,一种基于Chlib
代理技术 | 使用条件 | 配置方式 |
---|---|---|
JDK动态代理技术 | 目标类有接口,是基于接口动态生成实现类的代理对象 | 目标类有接口的情况下,默认方式 |
Cglib动态代理技术 | 目标类无接口且不能使用final修饰,是基于被代理对象动态生成子对象为代理对象 | 目标类无接口时,默认使用该方式;目标类有接口时,手动配置aop:config |
Cglib基于超类的动态代理
//目标对象
Target target = new Target();
//通知对象
Advices advices = new Advices();
//增强器对象
Enhancer enhancer = new Enhancer();
//增强器设置父类
enhancer.setSuperclass(Target.class);
//增强器设置回调
enhancer.setCallback((MethodInterceptor)(o.method.methodProxy)->{
advice.before();
Object object = method.invoke(target,Objects);
advice.afterReturning();
return result;
});
//创建代理对象
Target targetProxy = (Target)enhancer.create();
//测试
String result = targetProxy.show("路西法");