Spring-003-AOP篇

1、AOP

AOP,Aspect Oriented Programming,即面向切面编程,是OOP思想的升华

  • OOP是纵向对一个事务的抽象,一个对象包括静态的属性信息,包括动态的方法信息等
  • AOP是横向的对不同事务的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,将OOP中重复的代码部分可以定义为一个切面,是换了一个维度进行的思考,而用这种思维去设计编程的方式叫做面向切面编程

AOP的相关概念:

请添加图片描述

AOP概念讲解:

请添加图片描述

2、模拟实现AOP

AOP思想的实现:

动态代理。在运行期间,对目标对象的方法进行增强,代理对象同名方法内可以执行原有逻辑的同时嵌入执行其他增强逻辑或其他对象的方法

public class UserServiceImpl implements UserService {
    @Override
    public void show1() {
        System.out.println("show1....");
    }

    @Override
    public void show2() {
        System.out.println("show2....");
    }
}
/**
 * Description: 增强类,内部提供增强方法
 */
public class MyAdvice {
    public void beforeAdvice() {
        System.out.println("这里是前置增强!");
    }

    public void afterAdvice() {
        System.out.println("这里是后置增强!");
    }
}
/**
 * Description: bean后处理器
 */
public class MockAopBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 对 UserServiceImpl中的 show1和 show2方法进行增强,增强的方法存在于 MyAdvice中
        if (bean.getClass().getPackage().getName().equals("com.yao.service.impl")) {
            // 指定方法增强的 bean的范围,并生成当前 bean的Proxy对象 (这里采用 jdk代理方式)
            Object beanProxy = Proxy.newProxyInstance(
                    bean.getClass().getClassLoader(),
                    bean.getClass().getInterfaces(),
                    (Object proxy, Method method, Object[] args) -> {
                        // 获取到增强对象
                        MyAdvice myAdvice = applicationContext.getBean(MyAdvice.class);
                        // 执行增强对象的 before方法
                        myAdvice.beforeAdvice();
                        // 执行目标对象的目标方法
                        Object targetMethod = method.invoke(bean, args);
                        // 执行增强对象的 after方法
                        myAdvice.afterAdvice();
                        // 返回
                        return targetMethod;
                    }
            );
            return beanProxy;
        }
        return bean;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // 通过Aware接口注入 applicaionContext
        this.applicationContext = applicationContext;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="userService" class="com.yao.service.impl.UserServiceImpl"></bean>
    <bean id="myAdvice" class="com.yao.advice.MyAdvice"></bean>
    <bean class="com.yao.processor.MockAopBeanPostProcessor"></bean>
</beans>
    @Test
    public void testMyAOP() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.show1();
    }

结果:

这里是前置增强!
show1....
这里是后置增强!

3、基于XML的Spring AOP程序

xml方式配置AOP的步骤:

  1. 导入AOP相关坐标
  2. 准备目标类、准备增强类,并配置给Spring管理
  3. 配置切点表达式 (哪些方法被增强)
  4. 配置织入 (切点被哪些通知方法增强,是前值增强还是后置增强)

导入坐标:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.7</version>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>

准备目标类:

public class UserServiceImpl implements UserService {
    @Override
    public void show1() {
        System.out.println("show1....");
    }

    @Override
    public void show2() {
        System.out.println("show2....");
    }
}

准备增强类:

public class MyAdvice {
    public void beforeAdvice() {
        System.out.println("这里是前置增强!");
    }

    public void afterAdvice() {
        System.out.println("这里是后置增强!");
    }
}

交给Spring进行管理,并且配置AOP相关信息:

<?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:context="http://www.springframework.org/schema/context"
       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/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       ">

    <!-- 配置目标类 -->
    <bean id="userService" class="com.yao.service.impl.UserServiceImpl"></bean>
    
    <!-- 配置通知类 -->
    <bean id="myAdvice" class="com.yao.advice.MyAdvice"></bean>
    
    <!-- 配置 AOP -->
    <aop:config>
        
        <!-- 配置切点表达式,目的是指定哪些方法被增强 -->
        <aop:pointcut id="myPointcut" expression="execution(void com.yao.service.impl.UserServiceImpl.show1())"/>
        <!-- 配置织入,目的是要执行哪些切点与哪些通知进行结合 -->
        <aop:aspect ref="myAdvice">
            <!-- 配置方法,且要指定对应的切面表达式的 id (因为可以配置很多切面表达式) -->
            <aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
        </aop:aspect>
        
    </aop:config>
</beans>

结果:

这里是前置增强!
show1....

4、基于XML配置AOP

4.1 切面表达式

如果一个切点对应的只有一个通知,则可以直接在织入时填入切面表达式

<aop:config>
    <aop:aspect ref="myAdvice">
        <aop:after-returning method="afterAdvice" pointcut="execution(* com.yao.service.impl.*.*(..))"/>
    </aop:aspect>
</aop:config>

切面表达式语法: execution([访问修饰符] 返回值类型 包名.类名.方法名(参数))

  • 访问修饰符可以不写
  • 返回值类型、某一级包名、类名、方法名 可以使用*表示任意
  • 包名与类名之间使用单点.表示该包下的类,使用双点..表示该包及其子包下的类
  • 参数列表可以使用双点..表示任意参数

几个栗子:

请添加图片描述

4.2 五种通知类型

请添加图片描述

可传入的参数:

请添加图片描述
请添加图片描述

举个栗子

public class UserServiceImpl implements UserService {
    @Override
    public void show1() {
        System.out.println("show1....");
    }

    @Override
    public void show2() {
        System.out.println("show2....");
    }
}
public class MyAdvice {

    public void afterAdvice() {
        System.out.println("这里是后置增强!");
    }

    /**
     * 环绕增强
     * @param proceedingJoinPoint 传入的切点
     * @return Object
     * @throws Throwable
     */
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕前增强!");
        // 执行目标方法
        Object res = proceedingJoinPoint.proceed();

        System.out.println("环绕后增强!");
        return res;
    }

    public void afterThrowingAdvice() {
        System.out.println("报异常才会执行!");
    }
}
    <!-- 配置目标类 -->
    <bean id="userService" class="com.yao.service.impl.UserServiceImpl"></bean>
    <!-- 配置通知类 -->
    <bean id="myAdvice" class="com.yao.advice.MyAdvice"></bean>

    <!-- 配置 AOP -->
    <aop:config>
        <aop:pointcut id="myPointcut" expression="execution(* com.yao.service.impl.*.*(..))"/>
        <aop:aspect ref="myAdvice">
            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="myPointcut"/>
            <!-- 异常通知 -->
            <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="myPointcut"/>
            <!-- 最终通知 -->
            <aop:after method="afterAdvice" pointcut-ref="myPointcut"/>
        </aop:aspect>
    </aop:config>
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.show1();
环绕前增强!
show1....
环绕后增强!
这里是后置增强!

4.3 两种配置方式

如下:

  • 使用<aspect>配置切面

  • 使用<advisor>配置切面

Spring定义了一个Advice接口,实现了该接口的类都可以作为通知类出现

public interface Advice {
}

虽然该接口没有东西,但其子接口会帮助我们实现AOP过程。

上面的配置过程就是通过<aspect>来配置切面。下面举个<advisor>的配置栗子:

public class MyAdvisor implements MethodBeforeAdvice, AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("后置通知在这里!");
    }

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("前置通知在这里!");
    }
}
<!-- 配置目标类 -->
<bean id="userService" class="com.yao.service.impl.UserServiceImpl"></bean>
<!-- 配置通知类 -->
<bean id="myAdvice" class="com.yao.advice.MyAdvisor"></bean>

<!-- 配置 AOP - advisor -->
<aop:config>
    <!-- 配置切点 -->
    <aop:pointcut id="myPointcut" expression="execution(* com.yao.service.impl.*.*(..))"/>
    <aop:advisor advice-ref="myAdvice" pointcut-ref="myPointcut"/>
</aop:config>
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.show1();
前置通知在这里!
show1....
后置通知在这里!

其实两种配置方式先不谈优劣之分,差别是很明显的:

  • <aspect>是通过在配置文件中指定哪个方式是前置通知,哪个方法是后置通知,哪个方法是环绕通知
  • <adviosr>是通过实现对应的接口来指定方法的通知类型

两种配置方式的总结

语法形式的不同

  • advisor是通过实现接口来确认通知的类型
  • aspect是通过配置确认通知的类型,相比advisor这种方式更加灵活

可配置的切面数量不同

  • 一个advisor只能配置一个固定通知和一个切点表达式
  • 一个aspect可以配置多个通知和多个切面表达式任意组合

使用场景不同

  • 允许随意搭配情况下可以使用aspect进行配置
  • 如果通知类型单一、切面单一的情况下可以使用advisor进行配置
  • 在通知类型已经固定,不用人为指定通知类型时,可以使用advisor进行配置,例如Spring事务控制的配置

4.4 xml配置AOP原理

  1. 首先进行自定义命名空间解析,为这个aop标签去制定一个解析器 (parser解析器)
  2. 这个paser解析器只做一件事,即往spring容器中注入AspectJAwareAdvisorAutoProxyCreator,它的本质是一个**BeanPostProcessor**
  3. AspectJAwareAdvisorAutoProxyCreator BeanPostProcessor对应的after方法生成bean的代理对象,最后把这个生成的代理对象存储到容器当中

动态代理的实现选择,在调用getProxy()方法时,可以选用 AopProxy 接口的两个实现类。一种基于JDK,一种基于Cglib:

请添加图片描述

当通过这两种动态代理任意一中代理方式创造代理对象后,在getBean时get的就是代理对象,通过这个代理对象里面的方法执行顺序来对目标对象进行增强。

请添加图片描述

5、基于注解配置AOP

在前面我们基于xml配置AOP时,主要配置了三个部分:

  • 将目标类给Spring容器管理
  • 将通知类给Spring容器管理
  • 配置AOP的织入

基于注解的配置也需要配置这三部分,交给Spring容器管理的部分只需要通过@Component注解即可,主要学习的是配置AOP的相关注解,举个栗子吧:

@Service("userService")
public class UserServiceImpl2 implements UserService {
    @Override
    public void show1() {
        System.out.println("show1....");
    }

    @Override
    public void show2() {
        System.out.println("show2....");
    }
}
@Component
@Aspect
public class MyAdviceAop {

    @Before("execution(* com.yao.service.impl.*.*(..))")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("前置增强!");
    }

    @AfterReturning("execution(* com.yao.service.impl.*.*(..))")
    public void afterReturningAdvice() {
        System.out.println("后置增强!");
    }

//    @Around("execution(* com.yao.service.impl.*.*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕前增强~");
        Object res = proceedingJoinPoint.proceed();  // 执行目标方法
        System.out.println("环绕后增强~");
        return res;
    }

//    @AfterThrowing(pointcut = "execution(* com.yao.service.impl.*.*(..))", throwing = "e")
    public void afterThrowing(Throwable e) {
        System.out.println("当前异常是: " + e);
        System.out.println("这里是当异常抛出通知后才会执行......");
    }

    @After("execution(* com.yao.service.impl.*.*(..))")
    public void afterAdvice() {
        System.out.println("最终的增强!");
    }
}
<!-- 开启包扫描 -->
<context:component-scan base-package="com.yao"/>

<!-- 开启 aop注解扫描,如果不开启,Spring不会默认扫描到 AOP的注解 -->
<aop:aspectj-autoproxy/>
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application2.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.show1();

切面表达式的抽取

@Component
@Aspect
public class MyAdviceAop {

    // 切面表达式的抽取
    @Pointcut("execution(* com.yao.service.impl.*.*(..))")
    public void myPointCut() {}

    // 只需要通过 【类名 + 切面表达式抽取的方法】 即可
    @Before("MyAdviceAop.myPointCut()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("前置增强!");
    }

    @AfterReturning("MyAdviceAop.myPointCut()")
    public void afterReturningAdvice() {
        System.out.println("后置增强!");
    }

    @After("MyAdviceAop.myPointCut()")
    public void afterAdvice() {
        System.out.println("最终的增强!");
    }
}

再进一步:用配置类完全替换xml配置

@Configuration                // 标明这是一个配置类
@ComponentScan("com.yao")     // <context:component-scan base-package="com.yao"/>
@EnableAspectJAutoProxy       // <aop:aspectj-autoproxy/>
public class SpringConfig {
}
ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService bean = app.getBean(UserService.class);
bean.show2();

注解配置AOP原理

在之前提到的xml配置AOP的原理,它是借助了Spring的外部命名空间的加载方式完成的,使用注解配置后,抛弃了<aop:config>,而该标签最终加载了名为AspectJAwareAdvisorAutoProxyCreatorBeanPostProcessor,最终,在该BeanPostProcessor中完成了代理对象的生成。注解配置最终也是创建了这样一个BeanPostProcessor

各种方式配置AOP的原理
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值