Spring框架的关键组件之Aop配置

1、AOP相关概念的介绍

Spring框架的关键组件之一是AOP框架,虽然Spring IoC容器不依赖于AOP,但在Spring应用中,经常会使用AOP来简化编程。

优势

  • 提供声明式企业服务,可以进行声明式事务管理
  • 允许实现自定义的切面,某些不适用OOP编程的场景中,采用AOP来补充。
  • 可以对业务逻辑的各个部分进行隔离,从而降低业务逻辑之间的耦合度,提高程序的可用性,同时提高了开发的小路。

Spring要使用AOP模块,需要导入spirng-aop模块对应的jar。

AOP核心概念

AOP的概念使用大多数AOP框架,如AspectJSprng AOP

  1. Aspect(切面):将关注点模块化,可以横跨多个对象,例如事务管理。在SpringAOP中,切面可以使用常规类(基于模式的方法)或者使用@Apsect注解的常规类来实现。
  2. Join Point(连接点):在程序执行中抽取出来的特定点,例如方法调用时或处理处理时。Spring AOP中,连接点总是代表一个方法的执行。
  3. Advice(通知):在切面的某个特定的连接点上执行的动作,通知的类型有before、after等通知
  4. Pointcut(切入点):匹配连接点的断言,通知和一个切入点表达式关联。并在满足这个连接的上运行。(切入点表达式和连接点的匹配时AOP的核心)。
  5. Introduction(引入):声明额外的方法或者某个类型库的字段,SpringAOP的使用从允许一个新的接口(加上对应的实现类,使用的是JDK动态代理)到任何被通知的对象(不需要实现接口,普通的Java类,使用的是CGLIB代理)。
  6. Target Object(目标对象):被一个及以上的切面所通知的对象。Spring主要时通过运行代理实现的,所以目标对象来永远是一个Proxied(被代理)对象
  7. AOP Proxy(AOP代理):AOP框架创建的对象,用来实现Apsect Contract(切面契约)包含通知方法执行功能等。
  8. Weaving(织入):把切面连接到对象上,并创建一个Advised对象(代理对象)的过程。可以在编译时(AspectJ编译器)、类加载时和运行时完成(Spring AOP)

支持的通知:

  • Before(前置通知):在连接点之前执行的通知(这个通知不能阻止连接前的执行,除非出异常)
  • AferReturning(返回通知):在方法(连接点)正常执行后,没有异常的时候,就会执行到该方法
  • AfterThrowing(抛出异常通知):在方法抛出异常推出的时候执行的通知
  • After(最后通知):当方法退出时执行的语句,无论是否出现异常,都会执行
  • Around(环绕通知):包围一个连接点的通知,是前面四个通知的总和。

Spriing目前只支持方法调用作为连接点使用,默认使用的JDK动态代理,如果代理类没有实现接口再使用CGLIB代理

2、使用注解配置Spring AOP

Java配置类

package com.example.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration  // 表明该类是配置类
@EnableAspectJAutoProxy // 启用Spring AOP
@ComponentScan("com.example") // 指定扫描的包
public class SpringAopConfig {
    
    

}

2.1、目标对象没有是接口

目标类

package com.example.aop;

import org.springframework.stereotype.Component;
import java.math.BigDecimal;

@Component
public class GeneralPerson {
    
    
    public void say(){
    
    
        System.out.println("说活");
    }
    public void goHome(String tool){
    
    
        System.out.println("使用交通工具"+tool+"回家");
    }

    public double pay(double money){
    
    
        System.out.println("已经支付了"+String.format("%.2f",money)+"元");
        BigDecimal bg = new BigDecimal(money);
        /**
         *
         * 方法:setScale
         * 参数:
         * newScale - 要返回的 BigDecimal 值的标度。
         * roundingMode - 要应用的舍入模式。
         * 返回:
         * 一个 BigDecimal,其标度为指定值,其非标度值可以通过此 BigDecimal 的非标度值乘以或除以十的适当次幂来确定。
         */
        double result = bg.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
        System.out.println(result);
        return result;
    }

    /**
     * 如果number为零,出现异常,被除数不能为0
     * */
    public int divide(int number){
    
    
        return  10/number;
    }
}

切面类

package com.example.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Order(1) // 指定执行顺序
@Aspect // 定义为一个切面
@Component
public class AdviceForPerson {
    
    
    /**
     * 对GeneralPerson的所有方法进行拦截
     * */
    @Pointcut("execution(* com.example.aop.GeneralPerson.*(..))")
    public void pointcut(){
    
    }

    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
    
    
        System.out.println("--------------------------------");
        System.out.println("前置通知-----------");
        String name = joinPoint.getSignature().getName();
        System.out.println(name);
        Object[] args = joinPoint.getArgs();
        if(args.length> 0){
    
    
            for (Object o: args){
    
    
                System.out.println("-----------参数分隔符---------------");
                System.out.println("参数类型:" + o.getClass().getName());
                System.out.println("参数值:" + o);
            }
        }else{
    
    
            System.out.println("该方法没有参数");
        }
    }

    /**
     * 使用returning,将目标对象方法的返回绑定到参数result中,
     * */
    @AfterReturning(value = "pointcut()",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
    
    
        System.out.println("后置后置通知--------");
        if(result != null ){
    
    
            System.out.println("返回值类型:"+result.getClass().getName());
        }else{
    
    
            System.out.println("返回值类型:void");
        }
    }

    /**
     * 绑定出现的异常:throwing = "ex"
     * */
    @AfterThrowing(value = "pointcut()",throwing = "ex")
    public void afterThrowException(JoinPoint joinPoint , Exception ex){
    
    
        System.out.println("异常通知----------");
        System.out.println("ex"+ex.getMessage());
    }
    
    @After("pointcut()")
    public void after(JoinPoint joinPoint){
    
    
        System.out.println("后置通知----------");
    }
}

测试:(普通类,没有实现接口的,使用CGLIB实现)

    @Test
    public void testSpringAop(){
    
    
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringAopConfig.class);
        GeneralPerson person = context.getBean(GeneralPerson.class);
        person.say();
        person.goHome("地铁");
        person.pay(6.23556);
        person.divide(0);

    }

2.2、目标对象继承接口的:

接口

package com.example.interfaces;

public interface Say {
    
    
    void say(int number);
}

实现类

package com.example.aop;

import com.example.interfaces.Say;
import org.springframework.stereotype.Component;

@Component
public class AroundPerson implements Say {
    
    
    @Override
    public void say(int number) {
    
    
        System.out.println("测试Around");
        int divide = 10/number;
        System.out.println("divide:"+divide);
    }
}

切面类

package com.example.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AdviceForAround {
    
    
    /**
     * 对AroundPerson的所有方法进行拦截
     * */
    @Pointcut("execution(* com.example.aop.AroundPerson.say(..))")
    public void pointcutAround(){
    
    }


    @Around("pointcutAround()")
    public  Object Around(ProceedingJoinPoint proceedingJoinPoint){
    
    
        Signature signature = proceedingJoinPoint.getSignature();
        System.out.println("获取方法签名:"+signature.getDeclaringTypeName());
        // 获取参数
        Object[] args = proceedingJoinPoint.getArgs();
        if(args!=null && args.length >0){
    
    
            for (Object o: args){
    
    
                System.out.println("-----------参数分隔符---------------");
                System.out.println("参数类型:" + o.getClass().getName());
                System.out.println("参数值:" + o);
            }
        }else{
    
    
            System.out.println("该方法没有参数");
        }

        Object result = null; // 返回值
        try {
    
    
            System.out.println("Around-前置通知:");
            result = proceedingJoinPoint.proceed();
            System.out.println("Around-后置返回通知:");


        } catch (Throwable throwable) {
    
    
            System.out.println("Around-出现异常:"+throwable.getMessage());
//            throwable.printStackTrace();
        }finally {
    
    
            System.out.println("Around-最终通知");
            return result;
        }
    }
}

测试:(目标对象实现接口,使用的是动态代理)

	// 主要是测试Around通知
    @Test
    public void testSpringAopAround(){
    
    
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringAopConfig.class);
        Say aroundPerson = context.getBean(Say.class);
        aroundPerson.say(10);
    }

备注:在目标类实现接口的时候,获取的需要是通过接口来获取bean,如果通过目标类来获取,会出现异常,例如上面的获取需要用到类型是com.example.interfaces.Say

2.3、表达式:常见的execution表达式:

更多的表达式

  • 在目标方法上使用自定义的注解进行AOP:@PointCut("@annotation(自定义注解)")
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void pointcut(){
    
    }
  • 在目标类上使用自定义的注解进行AOP:@Pointcut("@within(自定义注解)")
该类的所有方法(不包含子类方法)执行aop方法
@Pointcut("@within(com.example.annotations.MyAnnotation)")
public void pointcut(){
    
    }
  • 指定目标包(不含子包)下所有的类的所有方法进行AOP:@Pointcut("within(包名前缀.*)")
// 在com.example.aop.inter下的类的所有方法都会执行AOP(这里是不包含子包的)
@Pointcut("within(com.example.aop.inter.*)") 
public void pointcut(){
    
    }
  • 指定目标包以及子包下所有的类的所有方法进行AOP:@Around("within(包名前缀..*")
// 在com.example.aop.inter下的类的所有方法都会执行AOP(这里是含子包的)
@Pointcut("within(com.example.aop.inter..*)")
public void pointcut(){
    
    }
  • 实现了该接口的类、继承该类、该类本身的类—的所有方法:@Pointcut("this(java类或接口)")
// 包括不是接口定义的方法,但不包含父类的方法)都会执行aop方法
@Around("this(com.example.service.TestService)")
public void pointcut(){
    
    }
  • 实现了该接口的类、继承该类、该类本身的类的所有方法:@PointCut("target(java类或接口)")
// (包括不是接口定义的方法,包含父类的方法)
@PointCut("target(com.aop.service.TestService)")
public void pointcut(){
    
    }

备注:上面的@PointCut可以和@Before、@AfterThrowing、@AfterReturning、@After和@Around进行替换。

3、基于XML的Spring AOP:

上面的提供是完全基于Java注解的方式进行配置Spring AOP的方法,Spring提供了基于XML的AOP支持,提供类新的 "aop"命名空间

可以在xml文件中使用下面的配置,取代上文中com.example.config.SpringAopConfig配置类的,同样可以在Spring扫描的包下使用注解配置AOP。

<!--指定扫描的包-->
<context:component-scan base-package="com.example"></context:component-scan>
<!--使用注解进行AOP的配置-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

如果需要完全通过XML进行配置:

配置:所有的apsect 和advisor元素必须配置在aop:config元素中

  • 一个程序的XML配置文件中可以有多个aop:config元素
  • 元素中可以配置pointcut、advisor、aspect等元素

声明一个pointcut:在aop:config元素中,这样可以在apsect和advice之间使用

声明advice:支持的前面提到的5种类型的advice

在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">

	<!-- 指定Spring扫描的包及其子包 -->
<!--	<context:component-scan base-package="com.example"></context:component-scan>-->
<!--	<aop:aspectj-autoproxy></aop:aspectj-autoproxy> -->

	<bean id="genericPerson" class="com.example.aop.GeneralPerson"></bean>
	<bean id="adviceForPerson" class="com.example.aop.AdviceForPerson"></bean>
	<aop:config>
		<aop:pointcut id="pointPerson" expression="execution(* com.example.aop.GeneralPerson.*(..))"/>
		<aop:aspect id="personAdvice" ref="adviceForPerson">
			<aop:before method="before" pointcut-ref="pointPerson"></aop:before>
			<aop:after-returning method="afterReturning" pointcut-ref="pointPerson" returning="result"></aop:after-returning>
			<aop:after-throwing method="afterThrowException" pointcut-ref="pointPerson" throwing="ex"></aop:after-throwing>
			<aop:after method="after" pointcut-ref="pointPerson"></aop:after>
		</aop:aspect>
	</aop:config>

	<bean id="aroundPerson" class="com.example.aop.AroundPerson"></bean>
	<bean id="adviceForAround" class="com.example.aop.AdviceForAround"></bean>
	<aop:config>
		<aop:pointcut id="aroundPoint" expression="execution(* com.example.aop.AroundPerson.*(..))"/>
		<aop:aspect id="aroundAdvice" ref="adviceForAround">
			<aop:around method="around" pointcut-ref="aroundPoint"></aop:around>
		</aop:aspect>
	</aop:config>
</beans>

测试:

    @Test
    public void testSpringAopXml(){
    
    
        System.out.println("xml配置的方式");
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
        GeneralPerson person = context.getBean(GeneralPerson.class);
        person.say();
        person.goHome("地铁");
        person.pay(6.23556);
        person.divide(10);
    }


    @Test
    public void testSpringAopAroundXml(){
    
    
        System.out.println("xml配置的方式");
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
        Say aroundPerson = context.getBean(Say.class);
        aroundPerson.say(10);
    }

4、Spring AOP与AspectJ,如何选择

决定因素有很多,其中最主要的的是应用程序的需求、开发工具和团队对AOP的熟悉程度等

相同点:目的一致,都是处理横切业务

不同点

  • Spring AOP并不提供完整的AOP功能,主要是在方法层面的AOP功能,这里注重于Spring IOC容器相结合,和Spring的框架的结合有天生的优势
  • 在AOP的功能方面,Aspect的功能更为完善,在完善性方面各有优势

猜你喜欢

转载自blog.csdn.net/Hicodden/article/details/110873885
今日推荐