[Spring]AOP基础

来源:互联网 发布:js添加图片二维数组 编辑:程序博客网 时间:2024/05/16 08:51

  • 背景问题
  • AOP简介
  • AOP术语
  • 用AspectJ注解声明切面
    • 前置通知
    • 后置通知
    • 返回通知
    • 异常通知
    • 环绕通知
    • 引入通知较少使用
    • 切面的优先级
    • 重用切点表达式
  • 基于配置文件配置AOP

背景问题

  1. 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀.每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点
  2. 代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志打印代码.如果日志需求发生变化,必须修改所有模块

AOP简介

  1. AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统OOP(Object-Oriented Programming,面向对象编程)的补充
  2. AOP的主要编程对象是切面(aspect),而切面模块化横切关注点
  3. 在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类.这样一来横切关注点就被模块化到特殊的对象(切面)里.
  4. AOP的好处:
    1. 每个事物逻辑位于一个位置,代码不分散,便于维护和升级
    2. 业务模块更简洁,只包含核心业务代码

AOP术语

  1. 切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
  2. 通知(Advice):切面必须要完成的工作
  3. 目标(Target):被通知的对象
  4. 代理(Proxy):向目标对象应用通知之后创建的对象
  5. 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如ArithmethicCalculator#add()方法执行前的连接点,执行点为ArithmethicCalculator#add();方位为该方法执行前的位置
  6. 切点(pointcut):每个类都拥有多个连接点:例如ArithmethicCalculator的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务AOP通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件

用AspectJ注解声明切面

  1. 要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为Bean实例.当在Spring IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那么与AspectJ切面相匹配的Bean创建代理
  2. 在AspectJ注解中,切面只是一个带有@Aspect注解的Java类
  3. 通知是标注有某种注解的简单的Java方法
  4. AspectJ支持5中类型的通知注解:
    1. @Before:前置通知,在方法执行之前执行
    2. @After:后置通知,在方法执行之后执行
    3. @AfterRunning:返回通知,在方法返回结果之后执行
    4. @AfterThrowing:异常通知,在方法抛出异常之后执行
    5. @Around:环绕通知,围绕着方法执行

前置通知

  1. @Before(“execution(方法路径)”)
  2. AspectJ:Java社区里最完整最流行的AOP框架,在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP
  3. 在Spring中启用AspectJ注解支持:
    1. 要在Spring应用中使用AspectJ注解,必须在classpath下包含AspectJ类库:aoplliance.jar、aspectj.weaver.jar和spring-aspects.jar
    2. 将aopSchema添加到<beans>根元素中.
    3. 要在SpringIOC容器中启用AspectJ注解支持,只要在Bean配置文件中定义一个空的XML元素<aop:aspectj-autoproxy>
    4. 当Spring IOC容器侦测到Bean配置文件中的<aop:aspectj-autoproxy>元素时,会自动为与AspectJ切面匹配的Bean创建代理

/声明该方法是一个前置通知:在目标方法开始前执行/

@Before("execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))")public void beforeMethod(JoinPoint joinPoint){    String methodName = joinPoint.getSignature().getName();    List<Object> args = Arrays.asList(joinPoint.getArgs());    System.out.println("The method is begining");    System.out.println("The method is " + methodName + " and args are " + args);}

后置通知

// 声明该方法是一个后置通知:在目标方法结束后执行(无论是否发生异常)// 在后置通知中不能访问目标方法执行的结果@After("execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))")public void afterMethod(JoinPoint joinPoint){    String methodName = joinPoint.getSignature().getName();    List<Object> args = Arrays.asList(joinPoint.getArgs());    System.out.println("The method is end");    System.out.println("The method is " + methodName + " and args are " + args);}

返回通知

// 声明该方法是一个返回通知:在目标方法正常结束后执行// 在返回通知中可以访问目标方法执行的结果@AfterReturning(value = "execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))",  returning = "result")public void afterReturnMethod(Object result){    System.out.println("The method is end");    System.out.println("The result is " + result);}

异常通知

// 声明该方法是一个异常通知:在目标方法抛出异常后执行@AfterThrowing(value = "execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))",  throwing = "exception")public void afterThrowMethod(NullPointerException exception){    System.out.println("The method throwing exception ");    System.out.println("The exception is " + exception);}*/

环绕通知

// 声明该方法是一个环绕通知,需要携带ProceedingJoinPoint类型的参数// 环绕通知类似于动态代理的全过程;ProceedingJoinPoint类型的参数可以决定是否执行目标方法// 且环绕通知必须有返回值,返回值即为目标方法的返回值@Around("execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))")public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){    Object result = null;    String methodName = proceedingJoinPoint.getSignature().getName();    // 执行目标方法    try{        // 前置通知        System.out.println("The method:" + methodName + " begings with:" + Arrays.asList(proceedingJoinPoint.getArgs()));        result = proceedingJoinPoint.proceed();        System.out.println("The result is:" + result);    } catch (Throwable throwable) {        System.out.println("异常通知");        throwable.printStackTrace();    }    // 后置通知    System.out.println("The method is ended");    return result;}

引入通知(较少使用)

引入通知是一种特殊的通知类型。它通过为接口提供实现类,允许对象动态的实现接口,就像对象已经在运行时扩展了实现类一样


切面的优先级

使用@Order类注解指定优先级,值越小优先级越高如@Order(2)


重用切点表达式

可以定义一个方法,用于声明切入点表达式.一般地,该方法中不再需要填入其他的代码,例如:
在切面类中定义方法:

//使用@Pointcut来声明切入点表达式,其他通知直接使用方法名来引用当前的切入点表达式@Pointcut("execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))")public void declareJointPointExpression(){}

然后,上面前置通知、后置通知…使用引用“execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))”的地方可以替换为declareJointPointExpression(),如果是在其他类(包)中引用,需要带上类名(及包名)


基于配置文件配置AOP

  1. 先在配置文件中配置bean
  2. 配置AOP:
//<!-- 配置AOP -->    <aop:config>        <!-- 配置切点表达式 -->        <aop:pointcut expression="execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))" id="pointcut">        <!-- 配置切面及通知-->        <aop:aspect ref="切面bean的id" order="2">            <aop:before method="beforeMethod" pointcut-ref="pointcut">        </aop:aspect>    </aop:config>
原创粉丝点击