Spring学习第三天:AOP?

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: Spring学习第三天:AOP?

       

文章目录

转账操作的问题

假设我们想实现一个转账操作,我们在IAccountService(账户的业务层接口)中添加转账方法,

/**
     * 转账
     * @param sourceName    转出商户名称
     * @param targetName    转入账户名
     * @param money         转账金额
     */
    void transfer(String sourceName,String targetName,float money);

然后在其实现类中添加方法的实现

    @Override
    public void transfer(String sourceName, String targetName, float money) {
        //1.根据名称查询转出账户
        Account source = dao.findAccountByName(sourceName);
        //2.根据名称查询转入账户
        Account target = dao.findAccountByName(targetName);
        //3.转出账户减钱
        source.setMoney(source.getMoney()-money);
        //4.转入账户加钱
        target.setMoney(target.getMoney()+money);
        //5.更新转出账户
        dao.updateAccount(source);
        //6.更新转入账户
        dao.updateAccount(target);
    }

添加测试方法

package com.spring.test;
import com.spring.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountservicImpl {
    @Autowired
    private IAccountService service;
    /**
     * 测试转账
     */
    @Test
    public void testTransfer() {
        service.transfer("aaa","bbb",100);
    }
}

此时运行可以正常执行

但此时我们在方法的实现中动点手脚

    @Override
    public void transfer(String sourceName, String targetName, float money) {
        //1.根据名称查询转出账户
        Account source = dao.findAccountByName(sourceName);
        //2.根据名称查询转入账户
        Account target = dao.findAccountByName(targetName);
        //3.转出账户减钱
        source.setMoney(source.getMoney()-money);
        //4.转入账户加钱
        target.setMoney(target.getMoney()+money);
        //5.更新转出账户
        dao.updateAccount(source);
        //捣乱
        int a=1/0;
        //6.更新转入账户
        dao.updateAccount(target);
    }

此时运行会发生报错,并且查看数据库会发现钱被扣掉了,但转入的账户却没有收到。

经过老师的分析:

我们需要将这些操作封装到一个事务中(如果语句成功便修改,不成功则回滚)

image.png

初步解决问题

下面我们就来解决这个问题:

首先我们要创建一个ConnectionUtils来实现线程与连接的绑定:

import javax.sql.DataSource;
import java.sql.Connection;
/**
 * 连接工具类
 * 实现线程绑定
 * @author 28985
 */
public class ConnectionUtils {
    private ThreadLocal<Connection> t1 = new ThreadLocal<Connection>();
    private DataSource source;
    public void setSource(DataSource source) {
        this.source = source;
    }
    public Connection getThreadConnection(){
        try {
            //从threadlocal上获取连接
            Connection connection = t1.get();
            //判断线程上是否有连接
            if (connection==null){
                //从数据源中获取连接,并和线程绑定存入ThreadLocal中
                connection=source.getConnection();
                t1.set(connection);
            }
            return connection;
        }
        catch (Exception e){
            throw new RuntimeException(e);
        }
    }
    public void removeConnection(){
        t1.remove();
    }
}

其中关于ThreadLocal的详解在:https://www.jianshu.com/p/6fc3bba12f38

之后我们需要创建一个事务相关类为我们提供各种操作事务的方法TransactionManage

/**
 * 事务相关
 * @author 28985
 */
public class TransactionManage {
    private ConnectionUtils connectionUtils;
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }
    /**
     * 开启事务
     */
    public void openTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }
        catch (Exception e){
            throw new RuntimeException(e);
        }
    }
    /**
     * 提交事务
     */
    public void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        }
        catch (Exception e){
            throw new RuntimeException(e);
        }
    }
    /**
     * 回滚事务
     */
    public void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        }
        catch (Exception e){
            throw new RuntimeException(e);
        }
    }
    /**
     * 关闭事务
     */
    public void release(){
        try {
            connectionUtils.getThreadConnection().close();//将连接还回连接池
            connectionUtils.removeConnection();//将线程和连接解绑
        }
        catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}

之后就可以对业务层做出调整

其基本结构为:

  try {
            //1 开启事务
            //2 执行操作
            //3 提交事务
            //4 返回结果
        }
        catch (Exception e){
            //5 回滚操作
        }
        finally {
            //6 释放连接
        }

于是便有了以下代码

/**
 * 账户的业务层实现类
 * 事务控制都应该在业务层
 * @author 28985
 */
public class IAccountServiceImpl implements IAccountService {
    private IAccountDao dao;
    private TransactionManage manage;
    public void setManage(TransactionManage manage) {
        this.manage = manage;
    }
    public IAccountDao getDao() {
        return dao;
    }
    public void setDao(IAccountDao dao) {
        this.dao = dao;
    }
    public IAccountServiceImpl(IAccountDao dao) {
        this.dao = dao;
    }
    public IAccountServiceImpl() {
    }
    @Override
    public List<Account> findAllAccount() {
        try {
            //1 开启事务
            manage.openTransaction();
            //2 执行操作
            List<Account> allAccount = dao.findAllAccount();
            //3 提交事务
            manage.commit();
            //4 返回结果
            return allAccount;
        }
        catch (Exception e){
            //5 回滚操作
            manage.rollback();
            throw new RuntimeException(e);
        }
        finally {
            //6 释放连接
            manage.release();
        }
    }
    @Override
    public Account findAccountById(Integer id) {
        try {
            //1 开启事务
            manage.openTransaction();
            //2 执行操作
            Account account = dao.findAccountById(id);
            //3 提交事务
            manage.commit();
            //4 返回结果
            return account;
        }
        catch (Exception e){
            //5 回滚操作
            manage.rollback();
            throw new RuntimeException(e);
        }
        finally {
            //6 释放连接
            manage.release();
        }
    }
    @Override
    public void saveAccount(Account account) {
        try {
            //1 开启事务
            manage.openTransaction();
            //2 执行操作
            dao.saveAccount(account);
            //3 提交事务
            manage.commit();
        }
        catch (Exception e){
            //5 回滚操作
            manage.rollback();
        }
        finally {
            //6 释放连接
            manage.release();
        }
    }
    @Override
    public void updateAccount(Account account) {
        try {
            //1 开启事务
            manage.openTransaction();
            //2 执行操作
            dao.updateAccount(account);
            //3 提交事务
            manage.commit();
        }
        catch (Exception e){
            //5 回滚操作
            manage.rollback();
            throw new RuntimeException(e);
        }
        finally {
            //6 释放连接
            manage.release();
        }
    }
    @Override
    public void delAccount(int id) {
        try {
            //1 开启事务
            manage.openTransaction();
            //2 执行操作
            dao.delAccount(id);
            //3 提交事务
            manage.commit();
        }
        catch (Exception e){
            //5 回滚操作
            manage.rollback();
            throw new RuntimeException(e);
        }
        finally {
            //6 释放连接
            manage.release();
        }
    }
    @Override
    public void transfer(String sourceName, String targetName, float money) {
        try {
            //1 开启事务
            manage.openTransaction();
            //2 执行操作
            //2.1.根据名称查询转出账户
            Account source = dao.findAccountByName(sourceName);
            //2.2.根据名称查询转入账户
            Account target = dao.findAccountByName(targetName);
            //2.3.转出账户减钱
            source.setMoney(source.getMoney()-money);
            //2.4.转入账户加钱
            target.setMoney(target.getMoney()+money);
            //2.5.更新转出账户
            dao.updateAccount(source);
            //2.捣乱
            int a=1/0;
            //2.6.更新转入账户
            dao.updateAccount(target);
            //3 提交事务
            manage.commit();
        }
        catch (Exception e){
            //5 回滚操作
            manage.rollback();
            throw new RuntimeException(e);
        }
        finally {
            //6 释放连接
            manage.release();
        }
    }
}

于是我们可以发现Connection已经由QueryRunner转移到ConnectionUtils里

所以我们对每个runner.query("select * from account",new BeanListHandler<Account>(Account.class));

更改为runner.query(connectionUtil.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));

即:

/**
 * 账户的持久层实现类
 * @author 28985
 */
public class AccountDaoImpl implements IAccountDao {
    private QueryRunner runner;
    private ConnectionUtils connectionUtil;
    public void setConnectionUtil(ConnectionUtils connectionUtil) {
        this.connectionUtil = connectionUtil;
    }
    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }
    @Override
    public List<Account> findAllAccount() {
        try {
            return runner.query(connectionUtil.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public Account findAccountById(Integer id) {
        try {
            return runner.query(connectionUtil.getThreadConnection(),"select * from account where id ="+id,new BeanHandler<Account>(Account.class));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public void saveAccount(Account account) {
        try {
            runner.update(connectionUtil.getThreadConnection(),"insert into account(name,money) value(?,?)",account.getName(),account.getMoney());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public void updateAccount(Account account) {
        try {
            runner.update(connectionUtil.getThreadConnection(),"update account set name=?,money=? where id = ?",account.getName(),account.getMoney(),account.getId());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void delAccount(int id) {
        try {
            runner.update(connectionUtil.getThreadConnection(),"delete from account where id = ?",id);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    @Override
    public Account findAccountByName(String name) {
        try {
            List<Account> accounts=runner.query(connectionUtil.getThreadConnection(),"select * from account where name = ?",new BeanListHandler<Account>(Account.class),name);
            if (accounts.size()==0||accounts == null){
                return null;
            }
            if (accounts.size()>1){
                throw new RuntimeException("结果集出现问题");
            }
            return accounts.get(0);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

之后就是spring’配置文件了

注意这里已经不需要给QueryRunner注入datasource了

<?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="accountService" class="com.spring.service.impl.IAccountServiceImpl">
        <property name="dao" ref="accountDao"/>
        <property name="manage" ref="transactionManage"/>
    </bean>
    <!--配置持久层-->
    <bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl">
        <property name="runner" ref="runner"/>
        <property name="connectionUtil" ref="connectionUtils"></property>
    </bean>
    <!--配置runner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--        <constructor-arg name="ds" ref="ds"/>   此时已经不需要通过QueryRunner的连接了,连接已经交给Utils来管理-->
    </bean>
    <!--配置DataSource(数据源)-->
    <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="root"/>
        <property name="password" value="adminadmin"/>
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_test"/>
    </bean>
<!--    配置连接工具类连接与线程绑定实现类-->
    <bean id="connectionUtils" class="com.spring.utils.ConnectionUtils">
        <property name="source" ref="ds"></property>
    </bean>
<!--    配置事务相关-->
    <bean id="transactionManage" class="com.spring.utils.TransactionManage">
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
</beans>

然后运行之前的转账方法就会发现事务起到作用,回滚成功了

但这里有一个问题,代码现在非常的臃肿,复用率很低所以接下来会采用动态代理的方式来解决这些问题

动态代理

特点:字节码(class)文件随用随创建,随用随加载

作用:不修改实现类源码的基础上对方法进行增强

   或者没有实现类时对方法实现

分类:

   基于接口的动态代理

   基于子类的动态代理

https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984#0

基于接口的动态代理

涉及类:Proxy

提供者:jdk官方

如何创建代理对象:

   Proxy类中的newProxyInstance方法

创建代理对象的要求:

   被代理对象至少实现一个接口,没有则不能使用

newProxyInstance方法的参数:

   ClassLoader 类加载器

      加载代理对象字节码,和被代理对象使用相同的类加载器

   Class[] 字节码数组

      让代理对象和被代理对象有相同的方法(被代理对象的接口)

   InvocationHandler 它是让我们写如何代理,一般写一个该接口的实现类 通常为内部类

示例:

制造商接口:

package com.spring.proxy.producers;
/**
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/2 9:56
 */
public interface IProducer {
    /**
     * 购买
     * @param money 制造商收到的钱
     */
    public void buy(float money);
    /**
     * 售后
     * @param money 制造商收到的钱s
     */
    public void shouHou(float money);
}

制造商实现类

package com.spring.proxy.producers;
/**
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/2 10:00
 */
public class ProducerImpl implements IProducer{
    @Override
    public void buy(float money) {
        System.out.println("商品已经售出收到"+money+"元");
    }
    @Override
    public void shouHou(float money) {
        System.out.println("商品已售后收到"+money+"元");
    }
}

主函数

    public static void main(String[] args) {
        ProducerImpl producer = new ProducerImpl();
        IProducer producer1 = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() {
        /**
          *执行被代理对象的任何接口方法都会经过该方法
          */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                float money = (float) args[0];
                if ("buy".equals(method.getName())) {
                    money *= 0.8f;//制造商抽成0.2
                }
                return method.invoke(producer,money);
            }
        });
        producer1.buy(10000);
    }

此时运行结果为

image.png

这表示producer1对象已经经过代理了

基于子类的动态代理

涉及类:Enhancer

提供者:cglib

如何创建代理对象:

   Enhancer类中的creat方法

创建代理对象的要求:

   被代理对象不能是最终类

newProxyInstance方法的参数:

   ClassLoader 类加载器

      加载代理对象字节码,和被代理对象使用相同的类加载器

   Class[] 字节码数组

      让代理对象和被代理对象有相同的方法(被代理对象的接口)

   InvocationHandler 它是让我们写如何代理,一般写一个该接口的实现类 通常为内部类

因为使用了cglib 所以我们先导入cglib的依赖

    <dependencies>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
    </dependencies>

更改main代码

public class Client {
    public static void main(String[] args) {
        ProducerImpl producer = new ProducerImpl();
        IProducer cglibproducer = (IProducer) Enhancer.create(ProducerImpl.class, new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                float money = (float) objects[0];
                if ("buy".equals(method.getName())) {
                    money *= 0.8f;//制造商抽成0.2
                }
                return method.invoke(producer,money);
            }
        });
        cglibproducer.buy(1000);
    }
}

此时便可以运行得到结果:

image.png

使用动态代理解决事务控制代码臃肿的问题

我们先将原来service中所有关于事务控制的代码删除,将其变为原来的样子

package com.spring.service.impl;
import com.spring.dao.IAccountDao;
import com.spring.domain.Account;
import com.spring.service.IAccountService;
import com.spring.utils.TransactionManage;
import java.util.List;
/**
 * 账户的业务层实现类
 * 事务控制都应该在业务层
 * @author 28985
 */
public class IAccountServiceImpl implements IAccountService {
    private IAccountDao dao;
    public IAccountDao getDao() {
        return dao;
    }
    public void setDao(IAccountDao dao) {
        this.dao = dao;
    }
    public IAccountServiceImpl(IAccountDao dao) {
        this.dao = dao;
    }
    public IAccountServiceImpl() {
    }
    @Override
    public List<Account> findAllAccount() {
        return dao.findAllAccount();
    }
    @Override
    public Account findAccountById(Integer id) {
        return dao.findAccountById(id);
    }
    @Override
    public void saveAccount(Account account) {
        dao.saveAccount(account);
    }
    @Override
    public void updateAccount(Account account) {
        dao.updateAccount(account);
    }
    @Override
    public void delAccount(int id) {
        dao.delAccount(id);
    }
    @Override
    public void transfer(String sourceName, String targetName, float money) {
        //2.1.根据名称查询转出账户
        Account source = dao.findAccountByName(sourceName);
        //2.2.根据名称查询转入账户
        Account target = dao.findAccountByName(targetName);
        //2.3.转出账户减钱
        source.setMoney(source.getMoney()-money);
        //2.4.转入账户加钱
        target.setMoney(target.getMoney()+money);
        //2.5.更新转出账户
        dao.updateAccount(source);
        //2.捣乱
//        int a=1/0;
        //2.6.更新转入账户
        dao.updateAccount(target);
    }
}

此时再创建一个BeanFactory工厂类

里面用来创建AccountService的代理对象

/**
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/2 12:19
 */
public class BeanFactory {
    private IAccountService accountService;
    private TransactionManage manage;
    public void setManage(TransactionManage manage) {
        this.manage = manage;
    }
    public void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }
    /**
     * 获取service的代理对象
     * @return
     */
    public IAccountService getAccountService() {
        IAccountService proxyAccountService=(IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object rtObject;
                        try {
                            //1 开启事务
                            manage.openTransaction();
                            //2 执行操作
                            rtObject = method.invoke(accountService,args);
                            //3 提交事务
                            manage.commit();
                            //4 返回结果
                            return rtObject;
                        }
                        catch (Exception e){
                            //5 回滚操作
                            manage.rollback();
                            throw new RuntimeException(e);
                        }
                        finally {
                            //6 释放连接
                            manage.release();
                        }
                    }
                });
        return proxyAccountService;
    }
}

代理中增强了事务相关的的代码

然后在bean.xml中为beanfactory注入manage和accountService

并生产一个添加了代理的accountService

<?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">
<!--    工厂生产的代理AccountService-->
    <bean id="proxyService" factory-bean="beanFactory" factory-method="getAccountService"/>
<!--    配置工厂-->
    <bean id="beanFactory" class="com.spring.factory.BeanFactory">
        <property name="accountService" ref="accountService"/>
        <property name="manage" ref="transactionManage"/>
    </bean>
    <!--配置业务层-->
    <bean id="accountService" class="com.spring.service.impl.IAccountServiceImpl">
        <property name="dao" ref="accountDao"/>
    </bean>
    <!--配置持久层-->
    <bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl">
        <property name="runner" ref="runner"/>
        <property name="connectionUtil" ref="connectionUtils"></property>
    </bean>
    <!--配置runner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--        <constructor-arg name="ds" ref="ds"/>   此时已经不需要通过QueryRunner的连接了,连接已经交给Utils来管理-->
    </bean>
    <!--配置DataSource(数据源)-->
    <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="root"/>
        <property name="password" value="adminadmin"/>
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_test"/>
    </bean>
<!--    配置连接工具类连接与线程绑定实现类-->
    <bean id="connectionUtils" class="com.spring.utils.ConnectionUtils">
        <property name="source" ref="ds"></property>
    </bean>
<!--    配置事务相关-->
    <bean id="transactionManage" class="com.spring.utils.TransactionManage">
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
</beans>

运行测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountservicImpl {
    @Qualifier("proxyService")
    @Autowired
    private IAccountService service;
    /**
     * 测试转账
     */
    @Test
    public void testTransfer() {
        service.transfer("aaa","bbb",100);
    }
}

注意这里添加了注解:@Qualifier("proxyService")是因为工厂生产的代理对象和配置业务层的beanaccountService都符合Autowired的条件,这时需要我们进行指定

至此使用动态代理解决事务控制的问题就结束了,但是我们发现配置文件很繁琐,那么就需要我们spring里的AOP登场了

AOP

什么是AOP

aop的中文含义是面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。--------百度百科

AOP的作用及优势

作用:在程序运行期间,不修改源码,对已有方法进行增强

优势:

减少重复代码

提高开发效率

维护方便

spring中的AOP

AOP 相关术语(了解)

Joinpoint(连接点):

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

pointout(切入点):

所谓切入点是指我们要对哪些Joinpoint进行拦截的定义(本案例中指需要进行事务控制的方法,而不需要事务控制的连接点不能叫切入点)

Advice(通知/增强):

通知是指拦截到Joinpoint之后要做的事情

通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知

image.png

Intorduction(引介):

特殊的一种通知,在不修改代码的前提下,可以在运行期间动态的为类添加一些方法

Target(目标对象)

代理的目标对象(被代理对象)

Weaving(织入):

指吧增强应用到目标对象来创建新的代理对象的过程

srping采用动态代理织入,AspectJ采用编译期织入和类装载期织入

Proxy(代理):

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

Aspect(切面):

是切入点和通知的结合

基于xml的AOP

创建业务层接口及实现类

package com.spring.services;
/**
 * 账户的业务层接口
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/5 20:13
 */
public interface IAccountService {
    /**
     * 模拟保存账户
     */
    public void save();
    /**
     * 模拟更新账户
     * @param i
     */
    public void update(int i);
    /**
     * 模拟删除账户
     * @return
     */
    public int delete();
}
import com.spring.services.IAccountService;
/**
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/5 20:27
 */
public class AccountServiceImpl implements IAccountService {
    @Override
    public void save() {
        System.out.println("保存");
    }
    @Override
    public void update(int i) {
        System.out.println("更新");
    }
    @Override
    public int delete() {
        System.out.println("删除");
        return 0;
    }
}

在创建一个logger类提供公用方法

package com.spring.utils;
/**记录日志工具,里面提供公共的代码
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/5 20:29
 */
public class Logger {
    /**
     * 前置消息
     */
    public void beforePrint(){
        System.out.println("前置消息");
    }
    /**
     * 后置消息
     */
    public void afterPrint(){
        System.out.println("后置消息");
    }
    /**
     * 异常消息
     */
    public void tryPrint(){
        System.out.println("异常消息");
    }
    /**
     * 消息
     */
    public void finallyPrint(){
        System.out.println("最终消息");
    }
}

配置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: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">
    <!-- 配置srping的Ioc,把service对象配置进来-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
    <!-- 配置Logger类 -->
    <bean id="logger" class="com.itheima.utils.Logger"></bean>
    <!--配置AOP-->
    <aop:config>
        <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
              此标签写在aop:aspect标签内部只能当前切面使用。
              它还可以写在aop:aspect外面,此时就变成了所有切面可用
          -->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置前置通知:在切入点方法执行之前执行
            <aop:before method="beforePrint" pointcut-ref="pt1" ></aop:before>-->
            <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个
            <aop:after-returning method="afterPrint" pointcut-ref="pt1"></aop:after-returning>-->
            <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
            <aop:after-throwing method="tryPrint" pointcut-ref="pt1"></aop:after-throwing>-->
            <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行
            <aop:after method="finallyPrint" pointcut-ref="pt1"></aop:after>-->
        </aop:aspect>
    </aop:config>
</beans>

编写main方法:

import com.spring.services.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/6 15:50
 */
public class AopTest {
    public static void main(String[] args) {
        ApplicationContext appletContext = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService as = (IAccountService)appletContext.getBean("accountService");
        as.save();
        as.update(1);
        as.delete();
    }
}

运行结果:

前置消息
保存
后置消息
最终消息
前置消息
更新
后置消息
最终消息
前置消息
删除
后置消息
最终消息

我们更改下delete方法

    @Override
    public int delete() {
        System.out.println("删除");
        int k = 5/0;
        return 0;
    }

结果为:

前置消息
保存
后置消息
最终消息
前置消息
更新
后置消息
最终消息
前置消息
删除
异常消息
最终消息
Exception in thread "main" java.lang.ArithmeticException: / by zero

此时我们可以得出结论 异常消息和后置消息是冲突的,只能存在一个

基于注解的AOP

基于注解的aop的xml配置很简单

只需要指定要扫描的包和开启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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
<!--    指定要扫描的包-->
    <context:component-scan base-package="com.spring"/>
<!--    开启spring对aop注解的支持-->
    <aop:aspectj-autoproxy/>
</beans>

首先要实现之前在xml中实现的

    <!-- 配置srping的Ioc,把service对象配置进来-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
    <!-- 配置Logger类 -->
    <bean id="logger" class="com.itheima.utils.Logger"></bean>

这两行代码

我们用注解来代替

AccountServiceImpl和Logger中添加注解@Service和@Component即可

接下来就是aop部分

image.png

第一句没有实际作用这里无需进行配置

第二句pointcut这里 我们改为使用pointcut注解,定义了一个空函数

    @Pointcut("execution(* com.spring.services.impl.*.*(..))")
    public void pointcut(){}

第三句为声明切面

我们需要为类添加@Aspect注解

@Aspect

下面这些对应注解

@Before("pointcut()")
@AfterReturning("pointcut()")
@AfterThrowing("pointcut()")
@After("pointcut()")
@Around("pointcut()")

所以logger更改后为

package com.spring.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**记录日志工具,里面提供公共的代码
 * @author GeGeGe
 * @version 1.0
 * @date 2021/10/5 20:29
 */
@Component
@Aspect//表示当前类是一个切面类
public class Logger {
    @Pointcut("execution(* com.spring.services.impl.*.*(..))")
    public void pointcut(){}
    /**
     * 前置消息
     */
    @Before("pointcut()")
    public void beforePrint(){
        System.out.println("前置消息");
    }
    /**
     * 后置消息
     */
    @AfterReturning("pointcut()")
    public void afterPrint(){
        System.out.println("后置消息");
    }
    /**
     * 异常消息
     */
    @AfterThrowing("pointcut()")
    public void tryPrint(){
        System.out.println("异常消息");
    }
    /**
     * 消息
     */
    @After("pointcut()")
    public void finallyPrint(){
        System.out.println("最终消息");
    }
    /**
     * 环绕通知
     * @param pjp
     */
//    @Around("pointcut()")
    public void aroundPrint(ProceedingJoinPoint pjp){
        try {
            System.out.println("前置");
            Object proceed = pjp.proceed(pjp.getArgs());
            System.out.println("后置");
        } catch (Throwable e) {
            System.out.println("异常");
            throw new RuntimeException(e);
        }finally {
            System.out.println("最终");
        }
    }
}

最后执行便可以得到xml配置相同的结果 注意在spring5.0.x某些版本中 后置消息和最终消息是有顺序问题的bug

完全不使用xml

@Configuration
@ComponentScan(basePackages="com.spring")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1天前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
2天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
21 9
|
22天前
|
存储 缓存 Java
Spring高手之路23——AOP触发机制与代理逻辑的执行
本篇文章深入解析了Spring AOP代理的触发机制和执行流程,从源码角度详细讲解了Bean如何被AOP代理,包括代理对象的创建、配置与执行逻辑,帮助读者全面掌握Spring AOP的核心技术。
29 3
Spring高手之路23——AOP触发机制与代理逻辑的执行
|
7天前
|
Java Spring
[Spring]aop的配置与使用
本文介绍了AOP(面向切面编程)的基本概念和核心思想。AOP是Spring框架的核心功能之一,通过动态代理在不修改原代码的情况下注入新功能。文章详细解释了连接点、切入点、通知、切面等关键概念,并列举了前置通知、后置通知、最终通知、异常通知和环绕通知五种通知类型。
18 1
|
22天前
|
前端开发 Java 数据库
SpringBoot学习
【10月更文挑战第7天】Spring学习
33 9
|
23天前
|
XML Java 数据格式
Spring学习
【10月更文挑战第6天】Spring学习
19 1
|
3天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
7 0
|
27天前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
52 2
|
27天前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
48 1
|
27天前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
19 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
下一篇
无影云桌面