Spring AOP 基础及原理

AOP是什么?


    AOP是一种设计思想,是面向切面的编程,他是面向对象的编程(oop)的一种补充和完善。在预编译和运行期动态代理方式,实现给程序动态统一添加额外功能的一种技术。通常将面向对象理解为一个静态过程,面向切面的运行期代理方式,可以理解为一个动态过程,可以在对象运行时动态织入一些扩展功能。

AOP应用场景分析?


    实际项目中通常会将系统分为两大部分,一部分是核心业务,一部分是非核业务。在编程实现时我们首先要完成的是核心业务的实现,非核心业务一般是通过特定方式切入到系统中,这种特定方式一般就是借助AOP进行实现。
    AOP就是要基于OCP(开闭原则),在不改变原有系统核心业务代码的基础上动态添加一些扩展功能并可以"控制"对象的执行。例如AOP应用于项目中的日志处理,事务处理,权限处理,缓存处理等等。

AOP应用原理分析


    SpringAOP底层是基于代理机制(动态方式)实现功能扩展:
    1)目标对象有实现接口,底层可采用JDK动态代理机制(目标类和代理类会实现共同接口),假如目标对象没有实现接口,则不可以采用JDK为目标对象创建代理对象。
    2)目标对象没有实现接口,底层可采用CGLIB代理机制(默认创建的代理类会继承目标对象类型),假如目标对象实现了接口也可以基于CGLIB为目标对象创建代理对象,但是对象类型假如使用了final修饰,则不可以使用CGLIB。


Spring中AOP相关术语分析


(1)切面:横切面对象,一般为一个具体类对象(可以借助@Aspect声明)。
(2)通知:在切面的某个特定连接点上执行的动作(扩展功能),例如around,before,after等。
(3)连接点:程序执行过程中某个特定的点,一般指被拦截到的的方法。
(4)切入点:对多个连接点(Joinpoint)一种定义,一般可以理解为多个连接点的集合。

Spring AOP快速实践


业务描述
    基于项目中的核心业务,添加简单的日志操作。(前提,不能修改目标方法代码-遵循OCP原则)

首先创建一个接口:

package com.cy.pj.common.service;

public interface MailService {
      boolean sendMail(String msg);
}


接着写一个实现类

package com.cy.pj.common.service;

import org.springframework.stereotype.Service;

@Service
public class MailServiceImpl implements  MailService{

    @Override
    public boolean sendMail(String msg) {//OCP(开闭原则-->对扩展开放,对修改关闭)  
         System.out.println("send->"+msg);
        return true;
    }

}

    基于原生方式实现功能扩展有两种设计模式:
    (1)自己动手写子类,重写父类方法,进行功能扩展。

class TimeMailServiceImpl extends  MailServiceImpl{ //CGLIB
    @Override
    public boolean sendMail(String msg) {
       long t1=System.nanoTime();
        boolean flag= super.sendMail(msg);
        long t2=System.nanoTime();
        System.out.println("send time:"+(t2-t1));
        return flag;
    }
}


    (2)自己写兄弟类对目标对象(兄弟类)进行功能扩展,这种方式又叫组合。

class TimeMailServiceImpl implements  MailService{//JDK

    private MailService mailService;
    public TimeMailServiceImpl(MailService mailService){
        this.mailService=mailService;
    }
    @Override
    public boolean sendMail(String msg) {
        long t1=System.nanoTime();
        boolean flag=mailService.sendMail(msg);
        long t2=System.nanoTime();
        System.out.println("send time:"+(t2-t1));
        return flag;
    }
}

写一个测试类进行验证

package com.cy.pj.common.service;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class MailServiceTests {

  
    @Test
    void testSendMail01(){
        //自己动手写的子类测试
        new TimeMailServiceImpl().sendMail("hello aop");
        //自己动手写的兄弟类测试
        new TimeMailServiceImpl(new MailServiceImpl()).sendMail("hello cgb2007");
    }
}

通过这段代码测试,我们可以大致了解了SpringAOP是怎样实现功能扩展的。我们先了解下Spring 整合AspectJ框架实现AOP的实现方式,然后在探索Spring原生AOP的实现,这样可以增强我们对AOP的了解,也能知道AspectJ框架底层实现的原理。对于AspectJ框架的学习和应用也更能得心应手。

现在我们通过引用AspectJ框架快速完成AOP的基本实现。

首先,我们要创建maven项目或在已有项目基础上添加AOP启动依赖:

<dependency>

       <groupId>org.springframework.boot</groupId>

       <artifactId>spring-boot-starter-aop</artifactId>

</dependency>

通过添加此依赖,spring可以整合AspectJ框架,快速完成AOP的基本实现。

然后创建日志切面类,将此类作为核心业务增强类,在不改变源码的情况下,给日志类动态的织入一些扩展功能(用于输出业务执行时长)。其关键代码如下:

package com.cy.pj.common.aspect;


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

@Aspect //告诉spring这是一个切面对象,这样的对象中要包含两部分内容(1.切入点,2.扩展逻辑-advice)
@Component //表示在spring中做一个注册
public class SysLogAspect {

    //定义切入点
    //bean表达式为spring中的一种粗粒度切入点表达式(不能精确到具体方法)
    //这里的mailServiceImpl名字为spring容器中一个bean对象的名字
    @Pointcut("bean(mailServiceImpl)")
    public void doLogPointCut(){}//这个方法仅仅是承载切入点注解的一个载体,方法体内不需要写任何内容

    /**按照Aspect规范定义一个@Around通知*/
    //@Around("bean(mailServiceImpl)") //直接在advice注解内部定义切入点表达式
    @Around("doLogPointCut()")//也可以在advice注解内部通过方法引用切入点表达式。
    //对于@Around注解描述的方法其规范要求:
    //1)返回值类型为Object (用于封装目标方法的执行结果)
    //2)参数类型ProceedingJoinPoint (用于封装要执行的目标方法信息)
    //3)抛出的异常为Throwable (用于封装执行目标方法时抛出的异常)
    //4)在@Around注解描述的方法内部,可以手动调用目标方法
    public Object doAround(ProceedingJoinPoint joinPoint)throws Throwable{
        long t1=System.nanoTime();
        Object result=joinPoint.proceed();//表示调用目标方法
        long t2=System.nanoTime();
        System.out.println("SysLogAspect->Time->"+(t2-t1));
        return result;
    }
}

在MailServiceTests测试类中,进行测试,代码如下:

package com.cy.pj.common.service;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class MailServiceTests {

    @Autowired
    private MailService mailService;
    @Test
    void testSendMail02(){
        //FAQ?这个的mailService指向的对象是谁?Proxy。
        mailService.sendMail("hello mailService");
    }

对于测试类中的userService对象而言,它有可能指向JDK代理,也有可能指向CGLIB代理,具体是什么类型的代理对象,要看application.yml配置文件中的配置。

spring.aop.proxy-target-class=true(默认,可以不写)为CGLB代理

spring.aop.proxy-target-class=false 为JDK代理

总结和分析

在业务应用,AOP相关对象分析,如图所示:

 


Spring AOP 编程增强

通知类型

在基于Spring AOP编程的过程中,基于AspectJ框架标准,spring中定义了五种类型的通知(通知描述的是一种扩展业务),它们分别是:

  • @Before
  • @AfterReturning
  • @AfterThrowing
  • @After
  • @Around.重点掌握(优先级最高)

  说明:在切面类中使用什么通知,由具体情况进行选择。

Spring AOP原生方式实现

Spring AOP原生方式实现的核心有三大部分构成,分别是:

(1)JDK代理。

(2)CGLIB代理。

(3 )org.aopalliance包下的拦截体系。

案例架构分析

本小节以Spring中一种原生AOP架构的基本实现为例进行原理分析和说明 :

简单案例业务的实现

案例描述

创建SpringBoot项目,并基于Spring原生AOP做出简单的逻辑扩展的实现。

核心业务接口定义及实现

首先创建一个新的项目

这里我们不添加任何依赖

点击finish,完成项目的创建

将项目转换成maven项目

在这样的一个包下,创建一个接口,并在接口中写一个方法

package com.cy.pj.common.service;

public interface MailService {

    boolean sendMsg (String message);
}

然后在这个包下写一个基于这个接口的一个实现类

 

package com.cy.pj.common.service;

import org.springframework.stereotype.Service;

/**
 * @author Robert
 */
@Service
public class MailServiceImpl implements MailService{
    @Override
    public boolean sendMsg(String message) {
        System.out.println("send"+message);
        return false;
    }
}

再创建一个包,和一个类

继承一个StaticMethodMatcherPointcutAdvisor接口,并添加一个他的抽象方法,代码如下:

package com.cy.pj.common.advisor;

import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;

import java.lang.reflect.Method;

public class LogAdvisor extends StaticMethodMatcherPointcutAdvisor {
    /**
     * Perform static checking whether the given method matches.
     * <p>If this returns {@code false} or if the {@link #isRuntime()}
     * method returns {@code false}, no runtime check (i.e. no
     * {@link #matches(Method, Class, Object[])} call)
     * will be made.
     *
     * @param method      the candidate method
     * @param targetClass the target class
     * @return whether or not this method matches statically
     */
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        return false;
    }
}

此Advisor中定义了一种规范:
 1)定义了哪些方法为切入点方法
 2)定义了在切入点方法执行时要织入的通知(扩展逻辑)。

创建一个advice类,代表通知

在这个类中实现一个接口MethodInterceptor并重写一个方法。

package com.cy.pj.common.advisor;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class LogAdvice implements MethodInterceptor {
    /**
     * Implement this method to perform extra treatments before and
     * after the invocation. Polite implementations would certainly
     * like to invoke {@link Joinpoint#proceed()}.
     *
     * @param invocation the method invocation joinpoint
     * @return the result of the call to {@link Joinpoint#proceed()};
     * might be intercepted by the interceptor
     * @throws Throwable if the interceptors or the target object
     *                   throws an exception
     */
    /**此方法可以在目标业务方法执行之前和之后添加扩展逻辑*/
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        return null;
    }
}

在方法中写一个具体的简单的方法(扩展逻辑)

package com.cy.pj.common.advisor;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**封装了扩展业务逻辑的对象,这样的对象在原生的aop中需要在advisor中注册*/
public class LogAdvice implements MethodInterceptor {
    /**
     * Implement this method to perform extra treatments before and
     * after the invocation. Polite implementations would certainly
     * like to invoke {@link Joinpoint#proceed()}.
     *
     * @param invocation the method invocation joinpoint
     * @return the result of the call to {@link Joinpoint#proceed()};
     * might be intercepted by the interceptor
     * @throws Throwable if the interceptors or the target object
     *                   throws an exception
     */
    /**此方法可以在目标业务方法执行之前和之后添加扩展逻辑*/
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("start:"+System.nanoTime());
        /**执行目标方法*/
        Object result=methodInvocation.proceed();
        System.out.println("end:"+System.nanoTime());
        return result;
    }//Advice
}

在LogAdvisor中添加注解@component,表示将该类交给spring管理

package com.cy.pj.common.advisor;

import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @component注解是将该adisor类交给spring去管理
 * 此Advisor中定义了一中规范:
 * 1)定义了哪些方法为切入点方法
 * 2)定义了在切入点方法执行时要织入的通知(扩展逻辑)。
 */
@Component
public class LogAdvisor extends StaticMethodMatcherPointcutAdvisor {
    public LogAdvisor(){
        setAdvice(new LogAdvice());
    }
    /**matches方法中可以获取我们要执行的目标方法,并且我们可以在此定义这个目标方法是否为我们的一个切入点方法
     *1)返回值为true表示目标方法为切入点方法(在此方法执行时可以织入扩展逻辑)
     *2)返回值为false表示目标方法为非切入点方法 */
    /**
     * Perform static checking whether the given method matches.
     * <p>If this returns {@code false} or if the {@link #isRuntime()}
     * method returns {@code false}, no runtime check (i.e. no
     * {@link #matches(Method, Class, Object[])} call)
     * will be made.
     *
     * @param method      the candidate method
     * @param targetClass the target class
     * @return whether or not this method matches statically
     */
    @Override
    public boolean matches(Method method, Class<?> aClass) {
        try {
            Method targetMethod = aClass.getMethod(method.getName(), method.getParameterTypes());
            return targetMethod.getName().equals("sendMsg");
        }catch(Exception e){
            return false;
        }
    }
}

我们可以单独写一个配置类完成代理对象的创建,当然我们也可以在启动类中(因为我们启动类本身也是一个配置类),当启动类运行时在启动类的内部初始化对象

package com.cy;

import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {
    /**
     * @Bean注解描述方法时,这个方法的返回值交给spring管理
     */
    @Bean 
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        return new DefaultAdvisorAutoProxyCreator();
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

现在我们写一个测试类进行测试,首先我们要创建一个一样的包

在类上一定要加@SpringBootTest注解

package com.cy.pj.common.service;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class MailServiceTests {
    @Autowired
    private MailService mailService;

    @Test
    void testSendMsg(){
      mailService.sendMsg("hello spring aop");
    }
}

点击测试,得到结果。方法扩展成功。 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值