Spring的Aop

1、AOP的相关术语

Joinpoint(连接点): 目标类中的所有方法都是连接点
Pointcut(切入点): 目标类类中会被增强的方法都是切入点
Advice(通知): 所谓通知是指拦截到Pointcut之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知
Aspect(切面): 通知与切入点的结合(所谓的切面就是用来说明通知与切入点的关系,即:通知在切入点执行的什么时候执行)
Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field
Target(目标对象): 目标类对象(要增强的类)
Proxy(代理对象): 一个目标类被AOP织入增强后,就产生一个结果代理类
Weaving(织入): 是把增强功能应用到目标的过程,即:把advice应用到target的过程

2、Spring的AOP的基本配置步骤

需求:在不修改已有代码的前提下,在项目现有所有类的方法前后打印日志

接口

public interface AccountService {
    /**
     * 模拟保存账户
     */
    void saveAccount();

    /**
     * 模拟更新账户
     */
    void updateAccount(int i);

    /**
     * 模拟删除账户
     */
    int deleteAccount();
}

实现类

public class AccountServiceImpl implements AccountService {

    public void saveAccount() {
        System.out.println("执行了保存");
    }

    public void updateAccount(int i) {
        System.out.println("执行了更新");
    }

    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}

通知类

/**
 * 日志打印类
 */
public class Logger {
    /**
     * 用于打印日志,计划让其在切入点方法执行前执行(切入点方法就是业务层方法)
     */
    public void printLog() {
        System.out.println("Logger的printLog()执行了");
    }
}

配置文件




    
    

    
    

    
        
            
            
        
    
    

测试类

public class TestAOP {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService accountService = applicationContext.getBean("accountServiceImpl",AccountService.class);
        accountService.saveAccount();
        accountService.updateAccount(1);
        accountService.deleteAccount();
    }
}

测试结果

Logger的printLog()执行了
执行了保存
Logger的printLog()执行了
执行了更新
Logger的printLog()执行了
执行了删除

3、四种常用的通知类型

XML中的AOP配置




    
    

    

    
        
        

        
            
            
            
            

            
            

            
            

            
            
            -->

            

            
            

            
            

            
            

            
            
        
        
    

测试类

public class TestAOP {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService accountService = applicationContext.getBean("accountServiceImpl",AccountService.class);
        accountService.saveAccount();
    }
}

测试结果

前置通知:Logger的printLog()执行了
执行了保存
后置通知:Logger的printLog()执行了
最终通知:Logger的printLog()执行了

4、配置环绕通知

通知类

/**
 * 用于记录日志的工具类
 */
public class Logger {
    /**
     * 前置通知
     */
    public void beforePrintLog() {
        System.out.println("前置通知:Logger的printLog()执行了");
    }

    /**
     * 后置通知
     */
    public void afterReturningPrintLog() {
        System.out.println("后置通知:Logger的printLog()执行了");
    }

    /**
     * 异常通知
     */
    public void afterThrowingPrintLog() {
        System.out.println("异常通知:Logger的printLog()执行了");
    }

    /**
     * 最终通知
     */
    public void afterPrintLog() {
        System.out.println("最终通知:Logger的printLog()执行了");
    }

    /**
     * 环绕通知
     * 问题:
     *      当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
     * 分析:
     *      通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用
     * 解决:
     *      Spring框架为我们提供了一个接口,Pro
   */
    public Object aroundPrintLog(ProceedingJoinPoint joinPoint) {
        // 调用proceed()方法即调用切入点方法
        Object retValue = null;
        try {
            System.out.println("前置通知:Logger的aroundPrintLog()执行了");
            // 获取切入点方法执行时所需的参数
            Object args[] = joinPoint.getArgs();
            retValue = joinPoint.proceed(args);
            System.out.println("后置通知:Logger的aroundPrintLog()执行了");
            return retValue;
        }catch (Throwable throwable) {
            System.out.println("异常通知:Logger的aroundPrintLog()执行了");
            throw new RuntimeException(throwable);
        }finally {
            System.out.println("最终通知:Logger的aroundPrintLog()执行了");
        }
    }
}

XML中的AOP配置




    
    

    

    
        
        

        
            
            
        
    

测试类

public class TestAOP {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService accountService = applicationContext.getBean("accountServiceImpl",AccountService.class);
        accountService.saveAccount();
    }
}

测试结果

前置通知:Logger的aroundPrintLog()执行了
执行了保存
后置通知:Logger的aroundPrintLog()执行了
最终通知:Logger的aroundPrintLog()执行了

5、基于注解的AOP配置

接口

/**
 * 账户的业务层接口
 */
public interface AccountService {

    /**
     * 模拟保存账户
     */
   void saveAccount();

    /**
     * 模拟更新账户
     * @param i
     */
   void updateAccount(int i);

    /**
     * 删除账户
     * @return
     */
   int  deleteAccount();
}

实现类

/**
 * 账户的业务层实现类
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService{

    @Override
    public void saveAccount() {
        System.out.println("执行了保存");
    }

    @Override
    public void updateAccount(int i) {
        System.out.println("执行了更新");

    }

    @Override
    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}

XML配置文件




    
    

    
    

通知类

在这里插入代码片/**
 * 用于记录日志的工具类,它里面提供了公共的代码
 */
@Component("logger")
//表示当前类是一个切面类
@Aspect
public class Logger {

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){}

    /**
     * 前置通知
     */
    @Before("pt1()")
    public  void beforePrintLog(){
        System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
    }

    /**
     * 后置通知
     */
    @AfterReturning("pt1()")
    public  void afterReturningPrintLog(){
        System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
    }
    /**
     * 异常通知
     */
    @AfterThrowing("pt1()")
    public  void afterThrowingPrintLog(){
        System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
    }

    /**
     * 最终通知
     */
    @After("pt1()")
    public  void afterPrintLog(){
        System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
    }
}

测试类

/**
 * 测试AOP的配置
 */
public class AOPTest {
    public static void main(String[] args) {
        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        IAccountService as = (IAccountService)ac.getBean("accountService");
        //3.执行方法
        as.saveAccount();
    }
}

测试结果

前置通知Logger类中的beforePrintLog方法开始记录日志了。。。
执行了保存
最终通知Logger类中的afterPrintLog方法开始记录日志了。。。
后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。

观察返回结果就会发现通知的输出顺序有问题,这个时spring在使用注解环绕通知时存在的问题

基于注解的AOP,使用环绕通知不会存在顺序问题

/**
 * 用于记录日志的工具类,它里面提供了公共的代码
 */
@Component("logger")
//表示当前类是一个切面类
@Aspect
public class Logger {

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){}
    
    /**
     * 环绕通知
     * 问题:
     *      当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
     * 分析:
     *      通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
     * 解决:
     *      Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
     *      该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
     *
     * spring中的环绕通知:
     *      它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
     */
    @Around("pt1()")
    public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//得到方法执行所需的参数

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");

            rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

            return rtValue;
        }catch (Throwable t){
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
        }
    }
}

测试类

/**
 * 测试AOP的配置
 */
public class AOPTest {
    public static void main(String[] args) {
        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        IAccountService as = (IAccountService)ac.getBean("accountService");
        //3.执行方法
        as.saveAccount();
    }
}

测试结果

Logger类中的aroundPringLog方法开始记录日志了。。。前置
执行了保存
Logger类中的aroundPringLog方法开始记录日志了。。。后置
Logger类中的aroundPringLog方法开始记录日志了。。。最终

你可能感兴趣的:(spring,aop)