Spring之旅 - 记录学习 Spring 框架的过程和经验(十)基于注解配置的AOP使用

前言

本博客中,我们将通过具体的代码示例,逐步演示如何使用 Spring 的注解来实现 AOP。我们将涵盖切面(Aspect)、通知(Advice)、连接点(Join Point)等基本概念,并展示如何将这些概念应用于实际项目中。无论你是 AOP 的新手还是希望深入了解其高级用法的开发者,都能从本文中获得有价值的见解。


XML方式原理剖析

动态代理的实现的选择,在调用getProxy(方法时,我们可选用的AopProxy接口有两个实现类,如上图,这两种都是动态生成代理对象的方式,一种就是基于JDK的,一种是基于Cglib的

java 复制代码
package com.itheima.test;

import com.itheima.advice.MyAdvice;
import com.itheima.service.impl.UserServiceImpl;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGlibTest {

    public static void main(String[] args) {

        //CGlib基于父类(目标类)生成Proxy

        //目标对象
        Target target = new Target();
        //通知对象(增强对象)
        MyAdvice4 myAdvice4 = new MyAdvice4();

        //编写CGlib的代码
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(Target.class);//生成的代理对象就是Target的子类
        //设置回调
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            //intercept方法相当于JDK的Proxy的invoke方法
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                myAdvice4.before();
                Object res = method.invoke(target, objects); //执行目标方法
                myAdvice4.after();
                return res;
            }
        });

        //生成代理对象
        Target proxy = (Target) enhancer.create();

        //测试
        proxy.show();

    }

}
java 复制代码
package com.itheima.test;

public class MyAdvice4 {

    public void before(){
        System.out.println("前置增强");
    }

    public void after(){
        System.out.println("后置增强");
    }

}

package com.itheima.test;

public class Target {

    public void show(){
        System.out.println("show..");
    }


}

基于注解配置的AOP

注解方式AOP的基本使用

Spring的AOP也提供了注解方式配置,使用相应的注解替代之前的xml配置,xml配置AOP时,我们主要配置了三部分:目标类被Spring容器管理、通知类被Spring管理、通知与切点的织入(切面),如下:

xml 复制代码
<!--配置目标-->
<bean id="target" class="com.itheima.aop.TargetImpl"></bean>
<!--配置通知-->
<bean id="advices" class="com.itheima.aop.Advices"></bean>
<!--配置aop-->
<aop:config proxy-target-class=" true">
	<aop:aspect ref="advices">
	<aop:around method-"around" Pointcut="execution(* com.itheima.aop.*,*(..))"/></aop:aspect>
<aop:config>
java 复制代码
//增强类,内部提供增强方法
@Component("myAdvice")
@Aspect
public class MyAdvice {

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

}
java 复制代码
<?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
       http://www.springframework.org/schema/aop/spring-aop.xsd
">

   <!--使用注解配置AOP,需要开启AOP的自动代理-->
    <aop:aspectj-autoproxy/>
    <!--组件扫描-->
    <context:component-scan base-package="com.itheima"></context:component-scan>

</beans>

配置aop,其实配置aop主要就是配置通知类中的哪个方法(通知类型)对应的切点表达式是什么

注解@Aspect、@Around需要被Spring解析,所以在Spring核心配置文件中需要配置aspectj的自动代理

xml 复制代码
<aop:aspectj-autoproxy/>

注解方式配置AOP详解

各种注解方式通知类型

java 复制代码
//前置通知
@Before("execution(* com.itheima.aop.*.*(..))")
publie void before (JoinPoint joinPoint) {}
//后置通知
@AfterReturning("execution(* com.itheima.aop.*.*(..))")
publie void AfterReturning(JoinPoint joinPoint) {}
//环绕通知
@Around("execution(* com.itheima.aop.*.*(..))")
pub1lic void around(ProceedingJoinPoint joinPoint) throws Throwable {}
//异常通知
@AfterThrowing("execution(* com.itheima.aop.*.*(..))")
publie void AfterThrowing(JoinPoint joinPoint) {}
//最终通知
@After("execution(* com.itheima.aop.*.*(..))")
publie void After (JoinPoint joinPoint) {}
java 复制代码
@Component("myAdvice")
@Aspect
public class MyAdvice {

    @Before("MyAdvice.myPointcut()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("前置的增强");
    }
    ...
    //切点表达式的抽取
    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    public void myPointcut(){
    }
}

@Before("MyAdvice.myPointcut()"): 定义一个前置通知,该通知将在执行切点方法之前执行。这里的切点引用到 myPointcut() 方法。

@Pointcut: 用来定义切点表达式。在这个例子中,它指的是 com.itheima.service.impl 包下的所有类的所有方法。

java 复制代码
package com.itheima.config;

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

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfig {
}

测试代码:

java 复制代码
	public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService bean = applicationContext.getBean(UserService.class);
        bean.show1();
    }

运行结果:

之前在使用xml配置AOP时,是借助的Spring的外部命名空间的加载方式完成的,使用注解配置后,就抛弃了< aop:config>标签,而该标签最终加载了名为AspectAwareAdvisorAutoProxyCreator的BeanPostProcessor,最终,在该BeanPostProcessor中完成了代理对象的生成。

同样,从aspectj-autoproxy标签的解析器入手

java 复制代码
this.registerBeanDefinitionParser("aspectj-autoproxy",newAspectJAutoProxyBeanDefinitionParser ());

基于AOP的声明式事务控制

Spring事务编程概述

事务是开发中必不可少的东西,使用JDBC开发时,我们使用connnection对事务进行控制,使用MyBatis时,我们使用SqISession对事务进行控制,缺点显而易见,当我们切换数据库访问技术时,事务控制的方式总会变化,Spring就将这些技术基础上,提供了统一的控制事务的接口。Spring的事务分为:编程式事务控制和声明式事务控制

Spring事务编程相关的类主要有如下三个

搭建测试环境

搭建一个转账的环境,dao层一个转出钱的方法,一个转入钱的方法,service层一个转账业务方法,内部分别调用dao层转出钱和转入钱的方法,准备工作如下:

  • 数据库准备一个账户表tb account;
  • dao层准备一个AccountMapper,包括incrMoney和decrMoney两个方法;
  • service层准备一个transferMoney方法,分别调用incrMoney和decrMoney方法;
  • 在applicationContext文件中进行Bean的管理配置;测试正常转账与异常转账。
java 复制代码
package com.itheima.mapper;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;

public interface AccountMapper {
    //+钱
    @Update("update account set money = money + #{money} where account_name = #{countName} ")
    public void incrMoney(@Param("countName")String countName,@Param("money") double money);

    //-钱
    @Update("update account set money = money - #{money} where account_name = #{countName} ")
    public void decrMoney(@Param("countName")String countName,@Param("money")double money);

}
java 复制代码
package com.itheima.service;

public interface AccountService {

    void transforMoney(String outAccount,String inAccount,double money);
}
java 复制代码
package com.itheima.service.impl;

import com.itheima.mapper.AccountMapper;
import com.itheima.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service("accountService")
public class AccountServiceImpl  implements AccountService {

    @Autowired
    private AccountMapper accountMapper;

    @Override
    public void transforMoney(String outAccount, String inAccount, double money) {
        accountMapper.decrMoney(outAccount,money);
        accountMapper.incrMoney(inAccount,money);
    }
}
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: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

       ">
    <!--组件扫描-->
    <context:component-scan base-package="com.itheima"/>

    <!--加载properties文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--配置数据源信息-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <!--配置SqlSessionFactoryBean,作用将SqlSessionFactory存储到spring容器-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--MapperScannerConfigurer,作用扫描指定的包,产生Mapper对象存储到Spring容器-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.itheima.mapper"></property>
    </bean>

</beans>

运行前:

运行后:


总结

希望通过本文的学习,能够帮助你更好地理解和运用 AOP,提升你在日常开发中的效率与质量。AOP 是一种强大的工具,掌握它将使你在软件开发的道路上更加游刃有余。

相关推荐
im_AMBER2 分钟前
Leetcode 97 移除链表元素
c++·笔记·学习·算法·leetcode·链表
悟空码字2 分钟前
SpringBoot + Redis分布式锁深度剖析,性能暴涨的秘密全在这里
java·spring boot·后端
奋进的芋圆3 分钟前
Spring Boot中实现定时任务
java·spring boot·后端
Jasmine_llq5 分钟前
《P3200 [HNOI2009] 有趣的数列》
java·前端·算法·线性筛法(欧拉筛)·快速幂算法(二进制幂)·勒让德定理(质因子次数统计)·组合数的质因子分解取模法
sww_10268 分钟前
xxl-job原理分析
java
星环处相逢8 分钟前
K8s 实战笔记:3 种发布策略 + YAML 配置全攻略
java·docker·kubernetes
BD_Marathon10 分钟前
Spring——容器
java·后端·spring
@zulnger13 分钟前
python 学习笔记(异常对象)
笔记·python·学习
其美杰布-富贵-李13 分钟前
x-transformers 完整学习笔记
笔记·学习·transformer
星火开发设计15 分钟前
链表详解及C++实现
数据结构·c++·学习·链表·指针·知识