SpringFramework~AOP~Annotation

SpringAOP(面向切面编程)

AOP,即面向切面编程。AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制、异常处理等,封装起来,便于减少系统重复的代码,降低模块之间的耦合度。它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即切面。AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用拦截方法的方式,对该方法进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“切面”,从而使得编译器可以在编译期间织入有关“切面”的代码。

SpringAOP组成

切面(Aspect)

横切关注点的模块化(跨越应用程序多个模块的功能,比如 日志功能),这个关注点实现可能另外横切多个对象。

连接点(Join point)

连接点是在应用执行过程中能够插入切面的一个点。这个点可以是类的某个方法调用前、调用后、方法抛出异常后等。切面代码可以利用这些点插入到应用的正常流程之中,并添加行为。

通知(Advice)
  • 前置通知(Before):在目标方法被调用之前调用通知功能。

  • 后置通知(After):在目标方法完成之后调用通知,无论该方法是否发生异常。

  • 后置返回通知(After-returning):在目标方法成功执行之后调用通知。

  • 后置异常通知(After-throwing):在目标方法抛出异常后调用通知。

  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

切点(Pointcut)

指定一个通知将被引发的一系列连接点的集合。AOP 通过切点定位到特定的连接点。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。每个类都拥有多个连接点,例如 ArithmethicCalculator类的所有方法实际上都是连接点。

引入(Introduction)

添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口

目标对象(Target Object)

包含连接点的对象。也被称作被通知或被代理对象。

AOP代理(AOP Proxy)

AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

织入(Weaving)

织入描述的是把切面应用到目标对象来创建新的代理对象的过程。 Spring AOP 的切面是在运行时被织入,原理是使用了动态代理技术。Spring支持两种方式生成代理对象:JDK动态代理和CGLib,默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。

我的理解:SpringAOP就是规定一段代码什么时候在哪里执行

Spring AOP使用

基于注解配置
1、定义目标对象
package com.bytebeats.spring4.aop.annotation.service;

/**
 * 业务逻辑接口
 */
public interface BankService {
    boolean transfer(String from, String to, int amount);
}
业务逻辑层实现类
package com.bytebeats.spring4.aop.annotation.service;

import org.springframework.stereotype.Service;

/**
 * 业务逻辑层实现类
 */
@Service
public class BankServiceImpl implements BankService {

    @Override
    public boolean transfer(String from, String to, int amount) {
        if(amount<1){
            throw new IllegalArgumentException("transfer amount must be a positive number");
        }
        System.out.println("["+from+"]向["+to+ "]转账金额"+amount);
        return false;
    }
}
2、定义切面
package com.bytebeats.spring4.aop.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;

@Aspect		//表明该类是一个切面
@Component   //Aspect切面首先必须是一个普通的bean组件
public class TransferLogAdvice {
	/**
	 * 切入点表达式1
	 *
     * 通过@Pointcut注解定义切入点表达式
     * 此处表达式含义:拦截com.bytebeats.spring4.aop.annotation.service.BankServiceImpl包下所	 * 有类(包括子包中所有类)中的所有方法
     */
    @Pointcut("execution(* com.bytebeats.spring4.aop.annotation.service.BankServiceImpl.*(..))")
    public void pointcut1() {}
    
	/**
	 * 切入点表达式2
     */
    @Pointcut("execution(* com.bytebeats.spring4.aop.annotation.service.*ServiceImpl.*(..))")
    public void myPointcut() {}

    /**
     * 前置通知:在方法执行前执行的代码
     * @param joinPoint
     */
    @Before(value = "pointcut1() || myPointcut()")
    //@Before("execution(* com.bytebeats.spring4.aop.annotation.service.BankServiceImpl.*(..))")
    public void beforeExecute(JoinPoint joinPoint){
        
        //该方法的含义就是在切入点表达式1或者切入点表达式2配置的
        //路径下的方法执行前先执行下面的代码,下面代码的含义就是获取目标方法的名字和参数列表打印出来
        
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        
        System.out.println(this.getClass().getSimpleName()+ " before execute:"+methodName+ " begin with "+args);
    }
    
    /**
     * 后置通知:在方法执行后执行的代码(无论该方法是否发生异常),注意后置通知拿不到执行的结果
     * @param joinPoint
     */
    @After(value = "pointcut1()")
    public void afterExecute(JoinPoint joinPoint){
        
        //该方法的含义就是在切入点表达式1配置的
        //路径下的方法执行后执行下面的代码,下面代码的含义就是获取目标方法的名字和参数列表打印出来
        
        String methodName = joinPoint.getSignature().getName();
        System.out.println(this.getClass().getSimpleName()+ " after execute:"+methodName+" end!");
    }
    
    /**
     * 后置返回通知:在方法正常执行后执行的代码,可以获取到方法的返回值
     * @param joinPoint
     */
    @AfterReturning(value = "pointcut1()",
            returning="result")
    public void afterReturning(JoinPoint joinPoint, Object result){
        
        //该方法的含义就是在切入点表达式2配置的
        //路径下的方法执行后执行下面的代码,下面代码的含义就是获取目标方法的类名,方法名和返回值打印
        
        String methodName = joinPoint.getSignature().getName();
        System.out.println(this.getClass().getSimpleName()+ " afterReturning execute:"+methodName+" end with result:"+result);
    }
    
    /**
     * 后置异常通知:在方法抛出异常之后执行,可以访问到异常信息,且可以指定出现特定异常信息时执行代码
     * @param joinPoint
     */
    @AfterThrowing(value = "pointcut1()",
            throwing="exception")
    public void afterThrowing(JoinPoint joinPoint, Exception /**NullPointerException*/ exception){
        
        //该方法的含义就是在切入点表达式1配置的
        //路径下的方法抛出异常后执行下面的代码,下面代码的含义就是获取目标方法的类名,方法名和异常信息打印
        
        String methodName = joinPoint.getSignature().getName();
        System.out.println(this.getClass().getSimpleName()+ " afterThrowing execute:"+methodName+" occurs exception:"+exception);
    }
    
    /**
     * 环绕通知, 围绕着方法执行
     * 通知注解的参数代表引用一个切入点表达式
     */
    @Around(value = "pointcut1()")
    public Object around(ProceedingJoinPoint joinPoint){
        
        //该方法的含义就是在切入点表达式1配置的
        //路径下的方法执行前后执行下面的代码
        
        String methodName = joinPoint.getSignature().getName();
        System.out.println(this.getClass().getSimpleName()+ " around:"+methodName+" execute start");
        
        Object result = null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println(this.getClass().getSimpleName()+ " around:"+methodName+" execute end");
        return result;
    }
}

把代码抽离出来分析:

Spring AOP提供了 @Before,@After、@AfterReturning、@AfterThrowing、@Around注解来指定 通知类型。

	/**
     * 前置通知:在方法执行前执行的代码
     * @param joinPoint
     */
    @Before("execution(* com.bytebeats.spring4.aop.annotation.service.BankServiceImpl.*(..))")
    public void beforeExecute(JoinPoint joinPoint){
        
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        
        System.out.println(this.getClass().getSimpleName()+ " before 				execute:"+methodName+ " begin with "+args);
    }

但是,为了复用切点,推荐使用@Pointcut来定义切点,然后在 @Before、@After等注解中引用定义好的切点,代码如下:

@Pointcut("execution(* com.bytebeats.spring4.aop.annotation.service.BankServiceImpl.*(..))")
    public void pointcut1() {
    }

    /**
     * 前置通知:在方法执行前执行的代码
     * @param joinPoint
     */
    @Before(value = "pointcut1()")
    public void beforeExecute(JoinPoint joinPoint){
        
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        
        System.out.println(this.getClass().getSimpleName()+ " before execute:"+methodName+ " begin with "+args);
    }
/**
     * 后置通知:在方法执行后执行的代码(无论该方法是否发生异常),注意后置通知拿不到执行的结果
     * @param joinPoint
     */
    @After(value = "pointcut1()")
    public void afterExecute(JoinPoint joinPoint){
        
        String methodName = joinPoint.getSignature().getName();
        System.out.println(this.getClass().getSimpleName()+ " after execute:"+methodName+" end!");
    }
3、启用注解扫描

开启注解扫描后,基于注解方式配置AOP已经结束了,接下来可以验证一下。

package com.bytebeats.spring4.aop.annotation;

import com.bytebeats.spring4.aop.annotation.service.BankService;
import com.bytebeats.spring4.aop.xml.service.UserService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * 纯注解方式入口类 
 */
@Configuration
@ComponentScan(basePackages = com.bytebeats.spring4.aop.annotation.UserService")
@EnableAspectJAutoProxy //开启对AOP相关注解的处理
public class SpringAopAnnotationApp {
    public static void main(String[] args) {    
      ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringAopAnnotationApp.class);
       BankService bankService = ctx.getBean(BankService.class);
        bankService.transfer("jordan", "kobe", 2000);
        System.out.println("*********************");
        bankService.transfer("jordan", "kobe", 0);
        ctx.close();
    }
}

拓展,@Pointcut 定义切点时,还可以使用 &&||!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值