Spring学习第三天:AOP?

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用版 2核4GB 50GB
简介: 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
相关文章
|
4天前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
4天前
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
|
6天前
|
安全 Java 开发者
Java 新手入门:Spring 两大利器IoC 和 AOP,小白也能轻松理解!
Java 新手入门:Spring 两大利器IoC 和 AOP,小白也能轻松理解!
14 1
|
6天前
|
Java Spring
Spring的AOP组件详解
该文章主要介绍了Spring AOP(面向切面编程)组件的实现原理,包括Spring AOP的基础概念、动态代理模式、AOP组件的实现以及Spring选择JDK动态代理或CGLIB动态代理的依据。
Spring的AOP组件详解
|
19天前
|
Java API Spring
Spring Boot 中的 AOP 处理
对 Spring Boot 中的切面 AOP 做了详细的讲解,主要介绍了 Spring Boot 中 AOP 的引入,常用注解的使用,参数的使用,以及常用 api 的介绍。AOP 在实际项目中很有用,对切面方法执行前后都可以根据具体的业务,做相应的预处理或者增强处理,同时也可以用作异常捕获处理,可以根据具体业务场景,合理去使用 AOP。
|
1月前
|
Java 数据格式 微服务
2024最新首发,全网最全 Spring Boot 学习宝典(附思维导图)
📚 《滚雪球学Spring Boot》是由CSDN博主bug菌创作的全面Spring Boot教程。作者是全栈开发专家,在多个技术社区如CSDN、掘金、InfoQ、51CTO等担任博客专家,并拥有超过20万的全网粉丝。该教程分为入门篇和进阶篇,每篇包含详细的教学步骤,涵盖Spring Boot的基础和高级主题。
108 4
2024最新首发,全网最全 Spring Boot 学习宝典(附思维导图)
|
20天前
|
安全 Java 数据库
三更草堂 Spring Security学习总结(思路整理)
Spring Security学习总结(思路整理)
|
27天前
|
Java Spring 容器
Spring问题之Spring AOP是如何实现面向切面编程的
Spring问题之Spring AOP是如何实现面向切面编程的
|
23天前
|
缓存 安全 Java
Spring高手之路21——深入剖析Spring AOP代理对象的创建
本文详细介绍了Spring AOP代理对象的创建过程,分为三个核心步骤:判断是否增强、匹配增强器和创建代理对象。通过源码分析和时序图展示,深入剖析了Spring AOP的工作原理,帮助读者全面理解Spring AOP代理对象的生成机制及其实现细节。
17 0
Spring高手之路21——深入剖析Spring AOP代理对象的创建
|
4天前
|
XML Java 数据库
Spring5入门到实战------10、操作术语解释--Aspectj注解开发实例。AOP切面编程的实际应用
这篇文章是Spring5框架的实战教程,详细解释了AOP的关键术语,包括连接点、切入点、通知、切面,并展示了如何使用AspectJ注解来开发AOP实例,包括切入点表达式的编写、增强方法的配置、代理对象的创建和优先级设置,以及如何通过注解方式实现完全的AOP配置。