Spring框架(2)---AOP

一、AOP概念引入

编写入门案例

创建maven的项目,引入开发的坐标

<dependencies>
        <!--spring的核心依赖jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--日志相关-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <!--有单元测试的环境,Spring5版本,Junit4.12版本-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--连接池  阿里巴巴  第三方-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--mysql驱动包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>
         <!--Spring整合Junit测试的jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

 传统方式

1、service业务层实现(包含事务管理)

package com.qcby.service;

import com.qcby.model.Account;

/**
 * AOP概念引入需要的业务层接口
 * 模拟转账业务
 */
public interface AccountService {
    //转账逻辑方法   account1 扣款   account2 增款
    public void saveAll(Account account1,Account account2);

}



@Service("accountService")
public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao;
    
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public void saveAll(Account account1, Account account2) {
        try {
            // 开启事务
            TxUtils.startTransaction();
            
            // 保存1账号
            accountDao.save(account1);
            
            // 模拟异常
            // int a = 1/0;
            
            // 保存2账号
            accountDao.save(account2);
            
            // 提交事务
            TxUtils.commit();
        } catch (Exception e) {
            // 打印异常信息
            e.printStackTrace();
            // 回滚事务
            TxUtils.rollback();
        } finally {
            // 关闭资源
            TxUtils.close();
        }
    }
}

 2、Dao持久层实现

package com.qcby.dao;

import com.qcby.model.Account;

//持久层  account转账业务
//转账逻辑操作数据库
public interface AccountDao {
    public void save(Account account);

}


public class AccountDaoImpl implements AccountDao {
    @Override
    public void save(Account account) throws SQLException {
        Connection conn = TxUtils.getConnection();
        String sql = "insert into account values(null,?,?)";
        PreparedStatement stmt = conn.prepareStatement(sql);
        stmt.setString(1, account.getName());
        stmt.setDouble(2, account.getMoney());
        stmt.executeUpdate();
        stmt.close();
    }
}

问题分析

  • 代码重复:每个Service方法都需要写相同的事务管理代码

  • 耦合度高:业务逻辑和事务管理代码混合在一起

  • 维护困难:修改事务逻辑需要修改所有相关方法

使用动态代理的AOP方式 

service实现(无事务代码)

package com.qcby.service.impl;

import com.qcby.dao.AccountDao;
import com.qcby.model.Account;
import com.qcby.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * AOP概念引入需要的业务层实现类
 * 模拟转账业务
 */
@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    @Qualifier("accountDao")
    private AccountDao accountDao;

    //转账逻辑方法   account1 扣款   account2 增款  实现
    public void saveAll(Account account1, Account account2) {
        try {
            //开启事务
//            TxUtils.startTransaction();
            //保存账号1
            accountDao.save(account1);
            //模拟异常
            //int a=1/0;
            //保存账号二
            accountDao.save(account2);
            //提交事务/回滚事务
//            TxUtils.commit();
        }catch (Exception e){
            //打印异常信息
            System.out.println("回滚");
            e.printStackTrace();
//            TxUtils.rollback();
        }finally {
            //关闭资源
            System.out.println("结束");
//            TxUtils.close();
        }

    }
}

 JDK动态代理实现

package com.qcby.JDKUtils;

import com.qcby.service.AccountService;
import com.qcby.utils.TxUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//JDK代理对象
public class JdkProxy {

    public static Object getPoxy(final AccountService accountService){

        /**
         *使用Jdk的动态代理生成代理对象
         */
         Object proxy=Proxy.newProxyInstance(JdkProxy.class.getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {

             /**
              *调用代理对象的方法,invoke方法就会去执行
              *@paramproxy
              *@parammethod
              *@paramargs
              *@return
              *@throwsThrowable
              * 用到反射
              */
           public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
               Object result= null;
               try {
                   //开启事务
                   TxUtils.startTransaction();
                   //对象目标对象的方法进行增强
                   result =method.invoke(accountService,args);
                   //提交事务
                   TxUtils.commit();
               }catch (Exception e){
                   e.printStackTrace();
                   TxUtils.rollback();
               }finally {
                   TxUtils.close();
               }
              return result;
           }
       });
         return proxy;
    }

}

测试代码

package com.qcby.test;

import com.qcby.JDKUtils.JdkProxy;
import com.qcby.model.Account;
import com.qcby.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

//整合junit文件
@RunWith(SpringJUnit4ClassRunner.class) // 使用Spring的测试运行器
@ContextConfiguration(locations = "classpath:applicationContext.xml") // 加载Spring配置文件
public class AccountTest {
    @Autowired
    @Qualifier("accountService")
    private AccountService accountService;

    @Test
    public void testSaveAll(){

//        ApplicationContext ac =new ClassPathXmlApplicationContext("applicationContext.xml");
//        AccountService accountService=(AccountService) ac.getBean("accountService");
        // 模拟两个账户
        Account account1 = new Account();
        account1.setName("abc");
        account1.setMoney(1000.0);

        Account account2 = new Account();
        account2.setName("qqq");
        account2.setMoney(2000.0);

        //生成代理对象
        Object proxyobj = JdkProxy.getPoxy(accountService);
        //强转
        AccountService proxy =(AccountService)proxyobj;
        //调用代理对象的方法
        proxy.saveAll(account1,account2);
        // 调用转账方法
//        accountService.saveAll(account1, account2);

        System.out.println("转账成功!");
    }

}

AOP方式的优势

  1. 业务解耦:业务代码不再包含事务管理逻辑

  2. 代码复用:事务管理逻辑集中处理

  3. 维护方便:修改事务逻辑只需修改代理类

  4. 灵活性强:可以动态为不同方法添加不同增强

二、Spring的AOP相关概念

AOP的概述

通俗理解:AOP(面向切面编程)就像给程序"打补丁",可以在不修改原有代码的情况下,给程序添加新功能。

生活比喻

  • 就像给手机贴膜:不需要改变手机本身,就能增加防刮功能

  • 类似快递包装:在原有商品外增加保护层,不影响商品本身

解决了什么问题

  1. 传统OOP(面向对象)中,像日志记录、权限检查这些通用功能需要在每个方法里重复写

  2. AOP把这些"横切关注点"抽离出来,实现"一次编写,多处使用"

核心特点

  • 横向抽取:不同于继承的纵向扩展

  • 非侵入式:不改动原有代码

  • 动态增强:运行时给方法添加功能 

AOP的优势 

三大好处

  1. 消灭重复代码

    • 把日志、事务等通用功能集中管理

    • 示例:不用在每个支付方法里写日志代码

  2. 提升开发效率

    • 专注业务逻辑开发

    • 通用功能通过配置实现

  3. 维护更方便

    • 修改日志格式只需改一处

    • 不影响业务代码

AOP底层原理 

两种代理方式

  1. JDK动态代理(适合接口)

    • 过程:创建接口的代理类 → 加载到JVM → 调用代理方法

    • 特点:要求目标类必须实现接口

  2. CGLIB代理(适合类)

    • 过程:生成目标类的子类作为代理

    • 特点:通过继承实现,无需接口 

工作流程:原始方法 → [代理拦截] → 添加增强功能 → 执行原始方法 → [代理拦截] → 返回结果 

 AOP相关的术语

Joinpoint(连接点) 所谓连接点是指那些被拦截到的点。在spring中,这些点指的 是方法,因为spring只支持方法类型的连接点

Pointcut(切入点)-- 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义

Advice(通知/增强)-- 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知. 通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)

Target(目标对象)-- 代理的目标对象

Weaving(织入)-- 是指把增强应用到目标对象来创建新的代理对象的过程

Proxy(代理)--一个类被AOP织入增强后,就产生一个结果代理类

Aspect(切面)--是切入点和通知的结合,以后咱们自己来编写和配置的 

 

三、Spring的AOP技术--配置文件方式

AOP配置文件方式入门程序

基本实现步骤:

1、创建Maven工程并导入坐标依赖


    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--AOP联盟-->
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <!--SpringAspects-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--aspectj-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.3</version>
        </dependency>
    </dependencies>

2、创建Spring配置文件

<?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">
</beans>

3、定义目标类

public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        System.out.println("业务层:保存用户...");
    }
}

4、配置目标类

<bean id="userService" class="com.qcbyjy.demo2.UserServiceImpl"/>

5、定义切面类

public class MyXmlAspect {
    public void log() {
        System.out.println("增强的方法执行了...");
    }
}

6、配置AOP

<aop:config>
    <aop:aspect ref="myXmlAspect">
        <aop:before method="log" 
                    pointcut="execution(public void com.qcbyjy.demo2.UserServiceImpl.save())"/>
    </aop:aspect>
</aop:config>

7、编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext_demo2.xml")
public class Demo2 {
    @Autowired
    private UserService userService;
    
    @Test
    public void run1() {
        userService.save();
    }
}

切入点表达式 

表达式语法:基本格式:execution([修饰符] 返回值类型 包名.类名.方法名(参数))

  • 修饰符可以省略不写,不是必须要出现的。
  • 返回值类型是不能省略不写的,根据你的方法来编写返回值。可以使用*代替。
  • 包名例如:com.tx.demo3.BookDaoImpl
  • 首先com是不能省略不写的,但是可以使用*代替
  • 中间的包名可以使用*号代替
  • 如果想省略中间的包名可以使用
  • 类名也可以使用*号代替,也有类似的写法:*DaoImpl
  • 方法也可以使用*号代替 参数如果是一个参数可以使用*号代替,如果想代表任意参数使用

通用表达式:

<aop:before method="log" 
            pointcut="execution(* com.qcbyjy.*.*ServiceImpl.save*(..))"/>

 AOP通知类型

五种通知类型配置

<aop:config>
    <aop:aspect ref="myXmlAspect">
        <!-- 1. 前置通知 -->
        <aop:before method="beforeAdvice" 
                    pointcut="execution(* com.qcbyjy..*.*(..))"/>
        
        <!-- 2. 最终通知 -->
        <aop:after method="afterAdvice" 
                  pointcut="execution(* com.qcbyjy..*.*(..))"/>
        
        <!-- 3. 后置通知 -->
        <aop:after-returning method="afterReturningAdvice" 
                            pointcut="execution(* com.qcbyjy..*.*(..))"/>
        
        <!-- 4. 异常通知 -->
        <aop:after-throwing method="afterThrowingAdvice" 
                           pointcut="execution(* com.qcbyjy..*.*(..))"/>
        
        <!-- 5. 环绕通知 -->
        <aop:around method="aroundAdvice" 
                   pointcut="execution(* com.qcbyjy..*.*(..))"/>
    </aop:aspect>
</aop:config>

切面类实现

public class MyXmlAspect {
    // 前置通知
    public void beforeAdvice() {
        System.out.println("前置通知:方法执行前调用");
    }
    
    // 最终通知
    public void afterAdvice() {
        System.out.println("最终通知:无论是否异常都会执行");
    }
    
    // 后置通知
    public void afterReturningAdvice(Object result) {
        System.out.println("后置通知:方法正常返回,返回值: " + result);
    }
    
    // 异常通知
    public void afterThrowingAdvice(Exception ex) {
        System.out.println("异常通知:方法抛出异常: " + ex.getMessage());
    }
    
    // 环绕通知
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知-前");
        Object result = pjp.proceed();  // 必须手动调用目标方法
        System.out.println("环绕通知-后");
        return result;
    }
}

关键点总结

  1. 执行顺序

    • 环绕通知前 → 前置通知 → 目标方法 → 环绕通知后 → 后置通知/异常通知 → 最终通知

  2. XML配置要点

    • 必须引入AOP命名空间

    • 使用<aop:config>作为根标签

    • 每个切面使用<aop:aspect>配置

    • 通知类型通过不同子标签配置

  3. 通知选择

    • 需要获取返回值 → 后置通知

    • 需要处理异常 → 异常通知

    • 需要方法前后都处理 → 环绕通知

    • 无论成功失败都要执行 → 最终通知

四、Spring的AOP技术--注解方式

AOP注解方式入门程序

基本实现步骤: 

1、创建Maven工程并导入坐标

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--AOP联盟-->
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <!--SpringAspects-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--aspectj-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.3</version>
        </dependency>
    </dependencies>

2、编写接口和实现类

public interface OrderService {
    void save();
}

@Service
public class OrderServiceImpl implements OrderService {
    @Override
    public void save() {
        System.out.println("订单保存操作...");
    }
}

3、编写切面类

package com.qcbyjy.demo3;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component  // 把该类交给IOC管理
@Aspect     // 声明是切面类
public class MyAnnoAspect {
    
    // 前置通知
    @Before("execution(public * com.qcbyjy.demo3.OrderServiceImpl.save(..))")
    public void log() {
        System.out.println("增强了...");
    }
}

4、配置自动代理(XML)

<!-- applicationContext2.xml -->
<context:component-scan base-package="com.qcbyjy.demo3"/>
<aop:aspectj-autoproxy/>

 5、编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class Demo3 {
    
    @Autowired
    private OrderService orderService;
    
    @Test
    public void run1() {
        orderService.save();
    }
}

 通知类型的注解

五种通知类型

1、@Before - 前置通知

@Before("execution(* com.qcbyjy.demo3.*.*(..))")
public void beforeAdvice() {
    System.out.println("前置通知:方法执行前调用");
}

 2、@AfterReturning - 后置通知

@AfterReturning(pointcut="execution(* com.qcbyjy.demo3.*.*(..))", 
                returning="result")
public void afterReturningAdvice(Object result) {
    System.out.println("后置通知:方法正常返回,返回值: " + result);
}

3、@Around - 环绕通知

@Around("execution(* com.qcbyjy.demo3.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("环绕通知-前");
    Object result = pjp.proceed();  // 必须手动调用目标方法
    System.out.println("环绕通知-后");
    return result;
}

4、@After - 最终通知

@After("execution(* com.qcbyjy.demo3.*.*(..))")
public void afterAdvice() {
    System.out.println("最终通知:无论是否异常都会执行");
}

5、@AfterThrowing - 异常抛出通知

@AfterThrowing(pointcut="execution(* com.qcbyjy.demo3.*.*(..))", 
               throwing="ex")
public void afterThrowingAdvice(Exception ex) {
    System.out.println("异常通知:方法抛出异常: " + ex.getMessage());
}

纯注解的方式

 完全使用Java配置替代XML,使用配置类

package com.qcbyjy.demo3;

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

@Configuration  // 声明为配置类
@ComponentScan("com.qcbyjy.demo3")  // 组件扫描
@EnableAspectJAutoProxy  // 等价于XML中的<aop:aspectj-autoproxy/>
public class SpringConfig {
}

测试类调整

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)  // 使用配置类
public class Demo3 {
    
    @Autowired
    private OrderService orderService;
    
    @Test
    public void run1() {
        orderService.save();
    }
}

关键点总结

  1. 切面类必须使用@Aspect@Component注解标记

  2. 通知注解需要指定切入点表达式

  3. 纯注解配置核心是@EnableAspectJAutoProxy注解

  4. 执行顺序:Around前→Before→目标方法→Around后→AfterReturning/AfterThrowing→After

  5. XML与注解对比

    • XML配置更集中,适合大型项目

    • 注解更简洁,适合中小型项目

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值