前言
本文章将介绍Spring框架的核心思想之一AOP以及其实现的案例
一、SpringAOP简介
AOP(Aspect-Oriented Programming: 面向切面编程):将那些与业务无关,
却为业务模块所共同调用的逻辑(例如事务处理、日志管理、权限控制等)封装抽
取成一个可重用的模块,这个模块被命名为“切面”(Aspect),便于减少系统的
重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性;
Spring AOP 基于动态代理实现:
○ 如果被代理的对象,已经实现某个接口,则 Spring AOP 会使用 JDK Proxy(反射),基于接口的方式,创建代理对象(JDK动态代理的核心是InvocationHandler接口和Proxy类);
○ 如果被代理的对象,没有实现某个接口,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib,基于继承的方式,生成一个被代理对象的子类来作为代理(Cglib动态代理的核心是MethodInterceptor接口和Enhancer类);
二、SpringAOP
2.1.AOP通知类型
AOP将抽取出来的共性功能称为通知;通知类型:以通知在上下文中的具体位置作为划分
- 前置通知(Before)
- 返回通知(After-returning)
- 异常通知(After-throwing)
- 环绕通知(Around)
- 后置通知(After)
2.2.AOP连接点(Join point)
AOP将所有的方法都视为连接点,不管是接口里面的抽象方法,还是实现类里面的重写方法,都是连接点
2.3.AOP切点(Pointcut)
AOP将可能被抽取共性功能的方法称为切入点。切入点是连接点的子集
2.4.目标对象(Target)
就是挖掉功能的方法对应的类生的对象,这种对象是无法直接完成最终工作的
2.5.AOP织入(Weaving)
就是将挖掉的功能回填的动态过程
2.6.AOP切面
切点+通知
三、基于xml实现AOP案例
注:如果在不使用其他依赖的情况下,Spring容器的通知类需要实现需要的通知接口,此方式强依赖了接口,耦合度高,所以推荐SpringAOP+AspectJ使用(以下案例演示)
3.1实现步骤
3.1.1坐标
创建好Maven后,需要在pom.xml中配置所需的坐标
<dependencies>
<!--Spring环境-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.28</version>
</dependency>
<!--切面依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
3.1.2配置
<!--开启aop配置-->
<aop:config>
<!--切面-->
<aop:aspect id="main" ref="loggerUtil">
<!--切点-->
<aop:pointcut id="dian" expression="execution()"/>
<!--前置通知-->
<aop:before method="" pointcut-ref="dian"></aop:before>
<!--返回通知-->
<aop:after-returning method="" pointcut-ref="dian"></aop:after-returning>
<!--异常通知-->
<aop:after-throwing method="" pointcut-ref="dian"></aop:after-throwing>
<!--后置通知-->
<aop:after method="" pointcut-ref="dian"></aop:after>
<!--环绕通知-->
<aop:around method="" pointcut-ref="dian"></aop:around>
</aop:aspect>
</aop:config>
其中
切点表达式配置语法:
execution(修饰符 返回值 包名称.类名称.方法名称(参数列表))
eg:
execution(public void com.apesource.service.ServiceImp.findAll())
1.修饰符可以省略代表任意
execution(返回值 包名称.类名称.方法名称(参数列表))
2.返回值可以使用“”代表任意
execution( 包名称.类名称.方法名称(参数列表))
3.包名可以使用“”代表任意名称
execution( ...类名称.方法名称(参数列表))
4.包名可以使用“…”代表任意个数
execution( …类名称.方法名称(参数列表))
5.类名与方法名可以使用“”代表任意
execution(* ….(参数列表))
6.参数列表可以使用"…"代表任意个数任意类型
execution( ….*(…))
3.2案例
public interface IService {
public void save(String name);
public int delete(int num);
public void findAll();
}
public class ServiceImp implements IService {
@Override
public void save(String name) {
System.out.println("===>save业务");
}
@Override
public int delete(int num) {
System.out.println("===>delete业务");
return 0;
}
@Override
public void findAll() {
System.out.println("===>findAll业务");
}
}
配置类注入
<bean id="serviceImp" class="com.apesource.service.ServiceImp"></bean>
<bean id="loggerUtil" class="com.apesource.util.LoggerUtil"></bean>
<!--开启aop配置-->
<aop:config>
<!--切面-->
<aop:aspect id="main" ref="loggerUtil">
<!--切点-->
<aop:pointcut id="dian" expression="execution(* com.apesource.service.ServiceImp.*(..))"/>
<!--前置通知-->
<aop:before method="beforeMethod" pointcut-ref="dian"></aop:before>
<!--返回通知-->
<aop:after-returning method="afterRrturnMethod" pointcut-ref="dian"></aop:after-returning>
<!--异常通知-->
<aop:after-throwing method="throwMethod" pointcut-ref="dian"></aop:after-throwing>
<!--后置通知-->
<aop:after method="afterMethod" pointcut-ref="dian"></aop:after>
<!--环绕通知-->
<aop:around method="arroundMethod" pointcut-ref="dian"></aop:around>
</aop:aspect>
</aop:config>
``
测试:
public class Test { public static void main(String[] args) { ApplicationContext applicationContext=new >ClassPathXmlApplicationContext("applicationContext.xml"); IService service = (IService) applicationContext.getBean("serviceImp"); service.findAll(); } }
结果如图
四、基于注解实现AOP案例
4.1配置
<context:component-scan base-package="com.apesource"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
4.2注解
4.1@Aspect
作用:声明这是一个切面类
位置:类
4.2@Pointcut(value = “execution()”)
作用:该注解是声明一个公用的切入点表达式(通知行为的注解的都可以直接拿来复用)
位置:方法
4.3@Before(“切点”)
作用:声明此方法是一个前置通知
位置:方法
4.3@AfterReturning(“切点”)
作用:声明此方法是一个返回通知
位置:方法
4.3@AfterThrowing(“切点”)
作用:声明此方法是一个异常通知
位置:方法
4.3@After(“切点”)
作用:声明此方法是一个后置通知
位置:方法
4.3@Around(“切点”)
作用:该注解是环绕通知是动态的,可以在前后都设置执行
参数:JoinPoint的子类ProceedingJoinPoint,通过此对象执行proceed方法
位置:方法
4.3案例
@Component
@Aspect
public class LoggerUtil {
@Pointcut(value = "execution(* com.apesource.service.*.*(..))")
public void dian() {
}
//前置通知
@Before("dian()")
public void beforeMethod(){
System.out.println("前置通知=====>"+new Date());
}
//返回通知
@AfterReturning("dian()")
public void afterRrturnMethod(){
System.out.println("返回通知=====>"+new Date());
}
//异常通知
@AfterThrowing("dian()")
public void throwMethod(){
System.out.println("异常通知=====>"+new Date());
}
//后置通知
@After("dian()")
public void afterMethod(){
System.out.println("后置通知=====>"+new Date());
}
//环绕通知
@Around("dian()")
public Object arroundMethod(ProceedingJoinPoint point){
Object returnObj = null;//执行切点方法
try {
System.out.println("环绕通知====>前置通知");
//切点
Object[] obj = point.getArgs();//切点方法的参数
returnObj = point.proceed(obj);
System.out.println("环绕通知====>返回通知");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知====>异常通知");
} finally {
System.out.println("环绕通知====>后置通知");
}
return returnObj;//切点方法的返回值
}
}
测试
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");
IService service = (IService) applicationContext.getBean("serviceImp");
service.findAll();
}
}
结果如图
总结
总结AOP开发过程:
●开发阶段(开发者完成)---------------我们自己做
◆正常的制作程序
◆将非共性功能开发到对应的目标对象类中,并制作成切入点方法
◆将共性功能独立开发出来,制作成“通知”
◆在配置文件中,声明“切入点”
◆在配置文件中,声明"切入点"与"通知"间的关系(含通知类型),即"切面"
●运行阶段(AOP完成)------------------spring帮我们做的
◆Spring容器加载配置文件,监控所有配置的“切入点”方法的执行
◆当监控到“切入点”方法被运行,使用“代理”机制,动态创建“目标对象”的“代理对象”,
根据“通知类别”,在“代理对象”的对应位置将“通知”对应的功能“织入”,完成完整的代码逻辑并运行