Spring AOP

wyplj_sir 2022-08-15 17:29:22 阅读数:869

springaop

AOP简介

AOP(Aspect Oriented Programming)称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待.简单说就是,在不改变原有的逻辑的基础上,增加一些额外的功能.

AOP和OOP

AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善.

OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合.不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能.日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用.

AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面.所谓"切面",Simply put, those are与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性.

AOP主要一般应用于签名验签、参数校验、日志记录、事务控制、权限控制、性能统计、异常处理等.如图:
在这里插入图片描述

AOP相关概念

  • 切面(Aspect):共有功能的实现.如日志切面、权限切面、验签切面等.在实际开发中通常是一个存放共有功能实现的标准Java类.当Java类使用了@Aspect注解修饰时,就能被AOP容器识别为切面.
  • 通知(Advice):切面的具体实现.就是要给目标对象织入的事情.以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种.在实际开发中通常是切面类中的一个方法,具体属于哪类通知,通过方法上的注解区分.
  • 连接点(JoinPoint):程序在运行过程中能够插入切面的地点.例如,方法调用、异常抛出等.Spring只支持方法级的连接点.一个类的所有方法前、后、抛出异常时等都是连接点.
  • 切入点(Pointcut):用于定义通知应该切入到哪些连接点上.不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的.比如,在上面所说的连接点的基础上,来定义切入点.我们有一个类,类里有10个方法,那就产生了几十个连接点.但是我们并不想在所有方法上都织入通知,我们只想让其中的几个方法,在调用之前检验下入参是否合法,那么就用切点来定义这几个方法,让切点来筛选连接点,选中我们想要的方法.切入点就是来定义哪些类里面的哪些方法会得到通知.
  • 目标对象(Target)那些即将切入切面的对象,也就是那些被通知的对象.这些对象专注业务本身的逻辑,所有的共有功能等待AOP容器的切入.
  • 代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象.可以简单地理解为,代理对象的功能等于目标对象本身业务逻辑加上共有功能.代理对象对于使用者而言是透明的,是程序运行过程中的产物.目标对象被织入共有功能后产生的对象.
  • 织入(Weaving)将切面应用到目标对象从而创建一个新的代理对象的过程.这个过程可以发生在编译时、类加载时、运行时.Spring是在运行时完成织入,运行时织入通过JavaThe reflection mechanism and dynamic proxy mechanism of the language来动态实现.

通过注解配置AOP

5种通知类型

  1. Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可

  2. After:在目标方法完成之后做增强,无论目标方法时候成功完成[email protected]可以指定一个切入点表达式

  3. AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值

  4. AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象

  5. Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意The core of programming is oneProceedingJoinPoint.

切点表达式

Pointcut格式为:

execution(modifier-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

修饰符匹配 modifier-pattern? 例:public private
返回值匹配 ret-type-pattern 可以用 * 表示任意返回值
类路径匹配 declaring-type-pattern? 全路径的类名
方法名匹配 name-pattern 可以指定方法名或者用 * 表示所有方法;set* 表示所有以set开头的方法
参数匹配 (param-pattern) 可以指定具体的参数类型,多个参数用“,”分隔;可以用 * 表示匹配任意类型的参数;可以用 (…) 表示零个或多个任意参数
异常类型匹配throws-pattern? 例:throws Exception
其中后面跟着 ? 表示可选项

例子:
限定该切点仅匹配的包是 com.sharpcj.aopdemo.test1,可以使用:

execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && within(com.sharpcj.aopdemo.test1.*)

在切点中选择 bean,可以使用:(切面只会对 Girl.java 这个类生效)

execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && bean(girl)

例子

切面类

package com.enjoy.cap10.aop;
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.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
//日志切面类
@Aspect
public class LogAspects {

//Declare pointcut expressions,The content of the method is not important,方法名也不重要,实际上它只是作为一个标识,供通知使用
@Pointcut("execution(public int com.enjoy.cap10.aop.Calculator.*(..))")
public void pointCut(){
};//can not be called herepointCut(),The following changes can be notified accordingly
//@before代表在目标方法执行前切入, 并指定在哪个方法前切入
@Before("pointCut()")
public void logStart(){

System.out.println("@Before:除法运行....参数列表是:{}");
}
@After("pointCut()")
public void logEnd(){

System.out.println("@After:除法结束......");
}
@AfterReturning("pointCut()")
public void logReturn(){

System.out.println("@AfterReturning:除法正常返回......运行结果是:{}");
}
@AfterThrowing("pointCut()")
public void logException(){

System.out.println("@AfterThrowing:运行异常......异常信息是:{}");
}
@Around("pointCut()")
public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{

System.out.println("@Arount:执行目标方法之前...");
Object obj = proceedingJoinPoint.proceed();//相当于开始调div地
System.out.println("@Arount:执行目标方法之后...");
return obj;
}
}

目标方法

package com.enjoy.cap10.aop;
public class Calculator {

//业务逻辑方法
public int div(int i, int j){

System.out.println("--------");
return i/j;
}
}

配置类

package com.enjoy.cap10.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import com.enjoy.cap10.aop.Calculator;
import com.enjoy.cap10.aop.LogAspects;
@Configuration
@EnableAspectJAutoProxy
public class Cap10MainConfig {

@Bean
public Calculator calculator(){

return new Calculator();
}
@Bean
public LogAspects logAspects(){

return new LogAspects();
}
}

测试类

public class Cap10Test {

@Test
public void test01(){

AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(Cap10MainConfig.class);
Calculator c = app.getBean(Calculator.class);
int result = c.div(4, 3);
System.out.println(result);
app.close();
}
}

执行结果

@Arount:执行目标方法之前...
@Before:除法运行....参数列表是:{
}
@Arount:执行目标方法之后...
@After除法结束......
@AfterReturning除法正常返回......运行结果是:{
}
1

切面类中值得注意的是 ,@Around 修饰的环绕通知类型,是将整个目标方法封装起来了,在使用时,我们传入了 ProceedingJoinPoint 类型的参数,这个对象是必须要有的,并且需要调用 ProceedingJoinPoint 的 proceed() 方法,如果不调用该对象的 proceed() 方法,表示原目标方法被阻塞调用.

配置类中值得注意的是,我们使用注解@EnableAspectJAutoProxy来启用Spring AOP ,It actually has a parameterproxyTargetClass,默认为false,It can be set like thistrue:
@EnableAspectJAutoProxy(proxyTargetClass = true)

  • 这跟Spring AOP 动态代理的机制有关,这个 proxyTargetClass 参数决定了代理的机制.如果proxyTargetClass 为 true,则会使用 cglib 的动态代理方式.这种方式的缺点是拓展类的方法被final修饰时,无法进行织入.当这个参数为 false 时,通过jdk的基于接口的方式进行织入,这时候代理生成的是一个接口对象.The way we get the object in the example is as follows:
    Calculator c = app.getBean(Calculator.class);
    If we use the following method to get it, an error will be reported,将这个接口对象强制转换为实现该接口的一个类,A type conversion exception is thrown.:
    Calculator c= (Calculator) context.getBean("calculator");

通过XML配置AOP

参考这个XML配置例子

Configuration via dynamic proxyAOP

See this dynamic proxy configuration example


参考链接:Spring AOP及实现方式
参考链接:深入理解Spring两大特性:IoC和AOP
参考链接:Spring AOP详细介绍
参考链接:Spring AOP

copyright:author[wyplj_sir],Please bring the original link to reprint, thank you. https://en.javamana.com/2022/227/202208151724193991.html