Spring学习笔记第三天,AOP的相关概念、Spring中的AOP、Spring中基于XML配置的AOP和基于注解配置的AOP

Spring 4 AOP与@Aspect注解深入解析 Aspect要定义一个切入点以匹配特定方法,您可以按照以下步骤操作:指定返回类型,如表示匹配返回类型为任意的方法。指定方法名称,如表示匹配所有名为method的方法。使用通配符来匹配任意类型的参数,如表示匹配任意方法。如果需要匹配特定类型的参数,可以指定参数类型,如表示匹配所有接受一个String和一个int类型参数的方法。代码示例:@Aspect@Component在这个例子中,切入点将匹配包中所有类的所有方法。@Before。 阅读详情

1. 第1章 AOP 的相关概念[理解]

demo代码

1.1案例分析

我们在昨天的day02_02account_xml_ioc 这个Module下进行分析其中的问题。首先我们创建一个Module命名为 day03_01account ,然后把 day02_02account_xml_ioc 这个Module里src/下的所有文件都复制到我们新建的Module的src下。然后我们参考 day02_02account_xml_ioc 这个Module里的pom.xml文件来修改我们新建的pom.xml文件。
下面我们修改一下 IAccountService.java 文件,增加一个新的接口。

public interface IAccountService {
    /**
     * 转账
     * @param sourceName 转出账户的名称
     * @param targetName 转入账户的名称
     * @param money      转账金额
     */
    void transfer(String sourceName, String targetName, Float money);
}

然后在 AccountServiceImpl.java这个类里实现一下这个方法。

public class AccountServiceImpl implements IAccountService {
	...
    public void transfer(String sourceName, String targetName, Float money) {
        //1.根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //3.转出账户的存款减去转账金额
        source.setMoney(source.getMoney() - money);
        //4.转入账户的存款加转账金额
        target.setMoney(target.getMoney() + money);
        //5.更新转出账户
        accountDao.updateAccount(source);
        //6.更新转入账户
        accountDao.updateAccount(target);
    }
}

这时你会发现我们的 IAccountDao.java 接口里少了一个 findAccountByName 方法,下面我们就把这个方法添加上。

public interface IAccountDao {
	...
    /**
     * 根据名称查询账户
     * @param accountName 账户名称
     * @return 如果有唯一的一个结果就返回,如果没有结果就返回null
     * 如果结果集超过一个就跑出异常
     */
    Account findAccountByName(String accountName);
}

下面我们在AccountDaoImpl.java里实现一下这个方法。

public class AccountDaoImpl implements IAccountDao {
	...
    public Account findAccountByName(String accountName) {
        try {
            List<Account> accounts = runner.query("select * from account where name = ?;", new BeanListHandler<Account>(Account.class), accountName);
            if (accounts == null || accounts.isEmpty()) {
                return null;
            }
            if (accounts.size() > 1) {
                throw new RuntimeException("结果集不唯一,数据有问题");
            }
            return accounts.get(0);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

然后修改一下测试类 AccountServiceTest.java 把无关的内容都删除掉。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
    @Autowired
    IAccountService as;

    @Test
    public void testTransfer() {
    	//aaa账户给bbb账户转账100元。
        as.transfer("aaa", "bbb", 100f);
    }
}

我们运行这个测试方法之前,数据库account表里的内容如下
在这里插入图片描述
我们来运行一下这个测试方法,运行之后数据库表的内容如下,可以看出正常运行了。
在这里插入图片描述
虽然我们的程序运行正常,但是里面存在一些问题,如果在AccountServiceImpl的transfer方法里运行了一半出错了,那数据库里的信息就不一致了。我们来修改一下AccountServiceImpl的transfer方法。添加 int i = 1 / 0; 语句模拟运行出错。

public class AccountServiceImpl implements IAccountService {
	...
    public void transfer(String sourceName, String targetName, Float money) {
        //1.根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //3.转出账户的存款减去转账金额
        source.setMoney(source.getMoney() - money);
        //4.转入账户的存款加转账金额
        target.setMoney(target.getMoney() + money);
        //5.更新转出账户
        accountDao.updateAccount(source);

        int i = 1 / 0;//模拟运行出错

        //6.更新转入账户
        accountDao.updateAccount(target);
    }
}

下面我们在来运行一下上面的测试方法。运行之后数据库表的内容如下
在这里插入图片描述
从上图中可以看出,aaa账户的存款减少了但是bbb账户的金额没有增加。这是一个很严重的问题。
在这里插入图片描述
究其原因就是在这个方法中存在多个连接,每和数据库交互一次就要获取一个连接,这些连接可能不是同一个(我们使用了连接池的技术),所以就没法保证数据的一致。当一个操作有多次和数据库交互获取多个连接,就没法进行事务控制。我们应该让这个几个和数据库交互的动作要发生就一起发生,要不发生就都不发生,才能保证数据的一致。我们知道每一个连接都可以保证事务的一致性,但是多个连接一起使用就不可以,所以我们应该在这一个方法中只有一个连接存在。这样我们才可以通过事务来保证数据一致。

添加事务管理的工具类并分析连接和线程绑定

为了解决上面的这个问题我们新建一个数据库的连接工具类 ConnectionUtils.java

/*
 * 连接的工具类,从数据源中获取一个连接并且实现和一个线程的绑定
 */
public class ConnectionUtils {
    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    private DataSource dataSource;
	// 这个方法用于spring的IOC依赖注入
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    /**
     * 获取当前线程上的连接
     * @return
     */
    public Connection getThreadConnection() {
        //1.先从ThreadLocal上获取
        Connection conn = tl.get();
        try {
            //2.判断当前线程上是否有连接
            if (conn == null) {
                //3.从数据源中获取一个连接,并且存入ThreadLocal中
                conn = dataSource.getConnection();
                tl.set(conn);
            }
            //4.返回当前线程上的连接
            return conn;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 把连接和线程解绑
     */
    public void removeConnection() {
        tl.remove();
    }
}

然后我们在添加一个事务管理的工具类 TransactionManager.java

/**
 * 和事务管理相关的工具类,包含了:开启事务、提交事务、回滚事务和释放连接
 */
public class TransactionManager {
    private ConnectionUtils connectionUtils;
	//这个方法用于spring的IOC依赖注入
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    /**
     * 开始事务
     */
    public void beginTransaction() {
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 提交事务
     */
    public void commit() {
        try {
            connectionUtils.getThreadConnection().commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    public void rollback() {
        try {
            connectionUtils.getThreadConnection().rollback();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 释放连接
     */
    public void release() {
        try {
            connectionUtils.getThreadConnection().close(); // 把连接返回连接池中
            connectionUtils.removeConnection();//把连接和线程进行解绑
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在工具类TransactionManager的 release()方法中在close()连接之后要把连接从线程中移除出去,因为,我们的服务器也会使用线程池技术,当我们对线程对象调用close()方法不是真正的把线程关闭,还是把线程还回了线程池中。所以在close()连接之后要把连接从线程中移除出去,避免下一个任务使用线程时会存在一个数据库连接在线程里。
下面来修改一下AccountDaoImpl.java,使我们在使用 QueryRunner 执行sql的时候,使用外面传给了Connection,而不是使用QueryRunner里的。

public class AccountDaoImpl implements IAccountDao {
    private QueryRunner runner;
    private ConnectionUtils connectionUtils;

	//这个方法为了进行数据注入
    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }
	//这个方法为了进行数据注入
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    public List<Account> findAllAccount() {
        try {
            return runner.query(connectionUtils.getThreadConnection(), "select * from account;", new BeanListHandler<Account>(Account.class));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Account findAccountById(Integer accountId) {
        try {
            return runner.query(connectionUtils.getThreadConnection(), "select * from account where id = ?;", new BeanHandler<Account>(Account.class), accountId);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void saveAccount(Account account) {
        try {
            runner.update(connectionUtils.getThreadConnection(), "insert into account (name, money) values(?,?)", account.getName(), account.getMoney());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void updateAccount(Account account) {
        try {
            runner.update(connectionUtils.getThreadConnection(), "update account set name = ?, money = ? where id = ?;", account.getName(), account.getMoney(), account.getId());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void deleteAccount(Integer accountId) {
        try {
            runner.update(connectionUtils.getThreadConnection(), "delete from account where id = ?;", accountId);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Account findAccountByName(String accountName) {
        try {
            List<Account> accounts = runner.query(connectionUtils.getThreadConnection(), "select * from account where name = ?;", new BeanListHandler<Account>(Account.class), accountName);
            if (accounts == null || accounts.isEmpty()) {
                return null;
            }
            if (accounts.size() > 1) {
                throw new RuntimeException("结果集不唯一,数据有问题");
            }
            return accounts.get(0);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

然后我们就来改造一下AccountServiceImpl.java,让这个类的方法都处在一个事务中,事务的控制就应该在业务层。

/**
 * 账户的业务层实现类
 * 事务控制应该都是在业务层的
 */
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;
    private TransactionManager txManager;
    //这个方法用于spring的IOC依赖注入
    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }
    //这个方法用于spring的IOC依赖注入
    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }

    public List<Account> findAllAccount() {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            List<Account> accounts = this.accountDao.findAllAccount();
            //3.提交事务
            txManager.commit();
            //4.返回结果
            return accounts;
        } catch (Exception e) {
            //5.回滚操作
            txManager.rollback();
            throw new RuntimeException(e);
        } finally {
            //6.释放事务
            txManager.release();
        }
    }

    public Account findAccountById(Integer accountId) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            Account account = this.accountDao.findAccountById(accountId);
            //3.提交事务
            txManager.commit();
            //4.返回结果
            return account;
        } catch (Exception e) {
            //5.回滚操作
            txManager.rollback();
            throw new RuntimeException(e);
        } finally {
            //6.释放事务
            txManager.release();
        }
    }

    public void saveAccount(Account account) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            this.accountDao.saveAccount(account);
            //3.提交事务
            txManager.commit();
            //4.返回结果
        } catch (Exception e) {
            //5.回滚操作
            txManager.rollback();
            throw new RuntimeException(e);
        } finally {
            //6.释放事务
            txManager.release();
        }
    }

    public void updateAccount(Account account) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            this.accountDao.updateAccount(account);
            //3.提交事务
            txManager.commit();
            //4.返回结果
        } catch (Exception e) {
            //5.回滚操作
            txManager.rollback();
            throw new RuntimeException(e);
        } finally {
            //6.释放事务
            txManager.release();
        }
    }

    public void deleteAccount(Integer accountId) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            this.accountDao.deleteAccount(accountId);
            //3.提交事务
            txManager.commit();
            //4.返回结果
        } catch (Exception e) {
            //5.回滚操作
            txManager.rollback();
            throw new RuntimeException(e);
        } finally {
            //6.释放事务
            txManager.release();
        }
    }

    public void transfer(String sourceName, String targetName, Float money) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            //2.1.根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //2.2.根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
            //2.3.转出账户的存款减去转账金额
            source.setMoney(source.getMoney() - money);
            //2.4.转入账户的存款加转账金额
            target.setMoney(target.getMoney() + money);
            //2.5.更新转出账户
            accountDao.updateAccount(source);

            int i = 1 / 0;

            //2.6.更新转入账户
            accountDao.updateAccount(target);
            //3.提交事务
            txManager.commit();
            //4.返回结果
        } catch (Exception e) {
            //5.回滚操作
            txManager.rollback();
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            //6.释放事务
            txManager.release();
        }
    }
}

然后我们来修改一下bean.xml,把需要数据的bean对象配置好

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

    <!-- 配置Service -->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <!-- 使用set方法注入 dao对象-->
        <property name="accountDao" ref="accountDao"></property>
        <!--注入事务管理器-->
        <property name="txManager" ref="transactionManager"></property>
    </bean>

    <!--配置Dao对象-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <!-- 使用set方法注入 QueryRunner -->
        <property name="runner" ref="runner"></property>
        <!-- 注入ConnectionUtils -->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

    <!--配置 QueryRunner 对象,因为 bean 对象 默认是单例对象,所以有可能发生线程安全问题。所以让scope设置为prototype-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!--配置数据源,使用数据库连接池-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--注入链接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!-- 配置 Connection 的工具类 ConnectionUtils ,这个工具类使用之后就不会在使用runner里的连接了-->
    <bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="com.itheima.utils.TransactionManager">
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
</beans>

修改好之后我们就来运行一下测试类AccountServiceTest.java里的测试方法testTransfer,控制台输出如下,出现了这个异常。
在这里插入图片描述
异常出现了,但是又会出现上面的那个数据不一致的问题吗,我们来看看数据库表里的内容,从下图可以看出,数据库里的内容没有发生变化,这正是我们想要的,事务控制成功了,说明我们成功解决了上面的那个问题。
在这里插入图片描述
经过上面的一顿操作已经解决了上面的问题,但是又有个问题出现了,就是这个写太复杂了看着太乱了,尤其是那个bean.xml依赖注入很乱。
下面就来解决这个问题。动态代理有来喽。

1.2 代理分析

1.2.1 代理模式的讲解

代理模式参考这里

1.2.2 使用动态代理实现事务的控制

首先我复制一份 AccountServiceImpl.java 复制到原来的位置命名为AccountServiceImpl_OLD。因为我们要使用动态代理来进行事务的控制,让 AccountServiceImpl来做被代理对象,所以AccountServiceImpl里就不需要在使用TransactionManager做事务控制了。AccountServiceImpl.java修改如下

public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;
    //这个方法用于spring的IOC依赖注入
    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public List<Account> findAllAccount() {
        return this.accountDao.findAllAccount();
    }

    public Account findAccountById(Integer accountId) {
        return this.accountDao.findAccountById(accountId);
    }

    public void saveAccount(Account account) {
        this.accountDao.saveAccount(account);
    }

    public void updateAccount(Account account) {
        this.accountDao.updateAccount(account);
    }

    public void deleteAccount(Integer accountId) {
        this.accountDao.deleteAccount(accountId);
    }

    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer....");
        //2.1.根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.2.根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //2.3.转出账户的存款减去转账金额
        source.setMoney(source.getMoney() - money);
        //2.4.转入账户的存款加转账金额
        target.setMoney(target.getMoney() + money);
        //2.5.更新转出账户
        accountDao.updateAccount(source);

        int i = 1 / 0;

        //2.6.更新转入账户
        accountDao.updateAccount(target);
    }
}

然后我们在com.itheima.factory里创建一个BeanFactory.java用于代理 AccountService。BeanFactory内容如下

/**
 * 用于创建Service的代理对象的工厂
 */
public class BeanFactory {
    private IAccountService accountService;
    private TransactionManager txManager;

    // 用于Spring的IOC依赖注入
    public void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }

    //这个方法用于spring的IOC依赖注入
    // 因为这个 txManager 对象要在匿名内部类里使用,所以要使用final修饰
    public final void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }

    //获取Service的代理对象
    public IAccountService getAccountService() {
        IAccountService service = (IAccountService) Proxy.newProxyInstance(this.accountService.getClass().getClassLoader(), this.accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    // 添加事务的支持,让所有和数据库交互的方法全部经过这里
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object returnValue = null;
                        try {
                            //1.开启事务
                            txManager.beginTransaction();
                            //2.执行操作
                            returnValue = method.invoke(accountService, args);
                            //3.提交事务
                            txManager.commit();
                            //4.返回结果
                            return returnValue;
                        } catch (Exception e) {
                            //5.回滚操作
                            txManager.rollback();
                            throw new RuntimeException(e);
                        } finally {
                            //6.释放事务
                            txManager.release();
                        }
                    }
                });
        return service;
    }
}

最后我们在来修改一下bean.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置代理的service对象-->
    <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>

    <!-- 配置beanFactory 这个得到的对象是代理的 accountService 的 service-->
    <bean id="beanFactory" class="com.itheima.factory.BeanFactory">
        <!--注入accountService-->
        <property name="accountService" ref="accountService"></property>
        <!--注入事务管理器-->
        <property name="txManager" ref="transactionManager"></property>
    </bean>

    <!-- 配置Service 这个是没有事务控制的service-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <!-- 使用set方法注入 dao对象-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!-- 配置Service 这个是原来在service里做事务控制的类-->
    <bean id="accountService_OLD" class="com.itheima.service.impl.AccountServiceImpl_OLD">
        <!-- 使用set方法注入 dao对象-->
        <property name="accountDao" ref="accountDao"></property>
        <!--注入事务管理器-->
        <property name="txManager" ref="transactionManager"></property>
    </bean>

    <!--配置Dao对象-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <!-- 使用set方法注入 QueryRunner -->
        <property name="runner" ref="runner"></property>
        <!-- 注入ConnectionUtils -->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

    <!--配置 QueryRunner 对象,因为 bean 对象 默认是单例对象,所以有可能发生线程安全问题。所以让scope设置为prototype-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!--配置数据源,使用数据库连接池-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--注入链接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!-- 配置 Connection 的工具类 ConnectionUtils -->
    <bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="com.itheima.utils.TransactionManager">
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
</beans>

我们还需要修改一下AccountServiceTest.java,因为项目IOC容器里有三个AccountService,我们这里要使用有动态代理的那个service来进行数据的注入。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    @Qualifier("proxyAccountService")//使用有动态代理的那个service来进行数据的注入
    IAccountService as;

    @Test
    public void testTransfer() {
        as.transfer("bbb", "aaa", 100f);
    }
}

我们运行一下这个测试方法,控制台输出如下,可以看出动态代理成功了。
在这里插入图片描述
我们在来查看一下数据库,说明事务控制也成功了。
在这里插入图片描述

1.3 AOP 概述

1.3.1 什么是 AOP

AOP: 全称是 Aspect Oriented Programming 即: 面向切面编程。 百度百科中的解释如下
在这里插入图片描述
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

  • AOP 的作用及优势
  • 作用:
    • 在程序运行期间,不修改源码对已有方法进行增强。
  • 优势:
    • 减少重复代码
    • 提高开发效率
    • 维护 方便
  • AOP 的实现方式
    • 使用动态代理技术

1.3.2 AOP 的具体应用

参考这里 Java中的代理模式和装饰者模式,还有我们上面提到的动态代理进行事务控制。

2. Spring 中的 AOP[掌握]

我们学习 spring 的 aop,就是通过配置的方式,实现上一章节的功能 使用动态代理来进行事务控制。

2.1 AOP 相关术语

  • Joinpoint(连接点):

    • 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。我所理解的连接点就是那些在调用时经过动态代理的 invoke 这个方法的方法。比如我上一章节最后使用动态代理来进行事务控制时,service里的所用的方法都叫连接点。
  • Pointcut(切入点):

    • 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。我们使用动态代理的目的就是对方法进行增强,但是我们使用动态代理时,并不是所用的方法都需要被增强,所以被增强的方法就叫切入点,
      比如,我们的 Iservice 接口中新定义了一个 test()方法,但是我们在使用动态代理时,遇到这个方法就直接结束掉,如下。
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         if ("test".equals(method.getName())) { // 这个方法没有增强就不叫切入点
              return method.invoke(args);
          }
          // 经过下面的方法都进行了增强,所以经过下面的方法都叫切入点
          Object returnValue = null;
          try { // 下面对方法增强(比如开启事务、提交事务、回滚事务、释放事务等)就是 Advice(通知/增强)
              //1.开启事务
              txManager.beginTransaction();
              //2.执行操作
              returnValue = method.invoke(accountService, args); // 之前的操作叫 前置通知,之后的叫后置通知
              //3.提交事务
              txManager.commit();
              //4.返回结果
              return returnValue;
          } catch (Exception e) { // 这里的叫异常通知
              //5.回滚操作
              txManager.rollback();
              throw new RuntimeException(e);
          } finally { // 这里的叫 最终通知
              //6.释放事务
              txManager.release();
          }
      }
    }
    

所以就有有一句话要记住,所有的切入点都是连接点,但不是所有的连接点都是切入点。

  • Advice(通知/增强):
    • 所谓通知是指拦截到 Joinpoint(连接点) 之后所要做的事情就是通知。
    • 通知的类型: 前置通知,后置通知,异常通知,最终通知,环绕通知。
      在这里插入图片描述
  • Introduction(引介):
    • 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
  • Target(目标对象):
    • 代理的目标对象。
  • Weaving(织入):
    • 是指把增强应用到目标对象来创建新的代理对象的过程。
    • spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
    • 就是使用动态代理对方法进行增强的过程叫织入
  • Proxy(代理) :
    • 一个类被 AOP 织入增强后,就产生一个结果代理类。
      Aspect(切面):
    • 是切入点和通知(引介)的结合。

学习 spring 中的 AOP 要明确的事

  • a、开发阶段(我们做的)
    • 编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。把公用代码抽取出来,制作成通知。(开发阶段最后再做): AOP 编程人员来做。在配置文件中,声明切入点与通知间的关系,即切面。
  • b、运行阶段( Spring 框架完成的)
    • Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

2.2 基于 XML 的 AOP 配置

2.2.1 基于 XML 的 AOP 配置入门案例

首先我们新建一个Module命名为 day03_03soringAOP这个Module用于演示基于XML的AOP的配置
首先在pom.xml添加中添加需要使用的坐标。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itheima</groupId>
    <artifactId>day03_03springAOP</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency> <!-- 支持切入点表达式 -->
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>
</project>

然后添加一个IAccountService的接口,这个接口里有三种方法,第一种无参无返回值、第二种有参无返回值、第三种无参有返回值。

/**
 * 账户的业务层接口
 */
public interface IAccountService {
    /**
     * 模拟保存账户
     */
    void saveAccount();

    /**
     * 模拟更新账户
     * @param i
     */
    void updateAccount(int i);

    /**
     * 模拟删除账户
     * @return
     */
    int deleteAccount();
}

然后在定义一个这个接口的实现类AccountServiceImpl

/**
 * 账户的业务层实现类,主要是模拟service的操作
 */
public class AccountServiceImpl implements IAccountService {
    public void saveAccount() {
        System.out.println("执行了保存");
    }

    public void updateAccount(int i) {
        System.out.println("执行了更新");
    }

    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}

然后在在utils包里定义一个公共类Logger

/**
 * 用于记录日志的工具类,它里面提共了公共的方法
 */
public class Logger {
    /**
     * 用于打印日志,计划让其在切入点方法执行之前执行(切入点方法就是我们的业务层方法)
     */
    public void printLog() {
        System.out.println("Logger类中的printLog方法开始记录日志了...");
    }
}

写好了公共类之后,下面我们就来创建一下bean.xml,并且配置aop,首先在spring的官方说明文档中找到带有aop的约束。

  • spring中基于xml的AOP配置步骤
  1. 把通知类也交给spring来管理
  2. 使用aop:config标签表明开始AOP的配置
  3. 使用aop:aspect标签表明配置切面
    • id属性:是给切面提供一个唯一的标识
    • ref属性:是指定通知类bean的id
  4. 在aop:aspect标签内部使用对应标签来配置通知的类型
    • 我们现在示例是让printLog方法在切入点方法执行之前执行;所以是前置通知
    • aop:before:表示配置前置通知
      • method属性:用于指定Logger类中的哪个方法是前置通知
      • pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
        • 切入点表达式的写法:
          • 关键字:execution(表达式)
          • 表达式:
            • 访问修饰f符 返回值 包名.包名…类名.方法名(参数列表)
            • 标准的表达式写法:
              public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
<?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">

    <!-- 配置spring的IOC,把service对象配置进来 -->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
    <!--spring中基于xml的AOP配置步骤
        1.把通知类也交给spring来管理
        2.使用aop:config标签表明开始AOP的配置
        3.使用aop:aspect标签表明配置切面
            id属性:是给切面提供一个唯一的标识
            ref属性:是指定通知类bean的id
        4.在aop:aspect标签内部使用对应标签来配置通知的类型
            我们现在示例是让printLog方法在切入点方法执行之前执行;所以是前置通知
            aop:before:表示配置前置通知
                method属性:用于指定Logger类中的哪个方法是前置通知
                pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强

            切入点表达式的写法:
                关键字:execution(表达式)
                表达式:
                    访问修饰f符  返回值 包名.包名...类名.方法名(参数列表)
                标准的表达式写法:
                    public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
      -->

    <!--配置Logger类-->
    <bean id="logger" class="com.itheima.utils.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知的类型,并且建立通知方法和切入点方法的关联-->
            <aop:before method="printLog" pointcut="execution(public void com.itheima.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

在这里插入图片描述
然后在test.java里的创建一个测试类com.itheima.test.AOPTest.java如下

/**
 * 测试AOP的配置
 */
public class AOPTest {
    public static void main(String[] args) {
        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        IAccountService as = (IAccountService) ac.getBean("accountService");
        //3.执行方法
        as.saveAccount();
    }
}

这个Module的结构如下
在这里插入图片描述

2.2.2切入点表达式

  • 关键字:execution(表达式)
  • 表达式:
    • 访问修饰f符 返回值 包名.包名…类名.方法名(参数列表)
  • 标准的表达式写法:
    public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
    
  • 访问修饰符可以省略:
    void com.itheima.service.impl.AccountServiceImpl.saveAccount()
    
  • 返回值可以使用通配符,表示任意返回值:
    * com.itheima.service.impl.AccountServiceImpl.saveAccount()
    
  • 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*:
    * com.itheima.service.impl.AccountServiceImpl.saveAccount()
    * *.*.*.*.AccountServiceImpl.saveAccount()
    
  • 包名可以使用 … 表示当前包及其子包
    * *..AccountServiceImpl.saveAccount()
    
  • 类名和方法名都可以使用*来实现统配
    * *..*.*()
    
  • 参数列表:
    • 可以直接写数据类型:
      • 基本类型直接写名称
      • 引用类型写包名.类名的方式 如 java.lang.String int
        * *..*.*() 表示没有参数的方法
        * *..*.*(int) 表示有一个int型的参数
        * *..*.*(java.lang.String) 表示有一个String型的参数
        * *..*.*(java.lang.String, int) 表示有一个int型的参数和一个int型的参数
        
    • 可以使用通配符表示任意参数类型,但是方法必须要有参数
    • 可以使用 … 表示有无参数均可,有参数可以是任意类型
  • 全统配写法(在实际开发中不推荐使用,因为这样会扫描所有的包下的所有的类和所有的方法,因为整个项目的所有方法都是匹配的)
    * *..*.*(..)
    
  • 实际开发中切入点表达式的通常写法:
    要切入到业务层实现类下的所有方法
    * com.itheima.service.impl.*.*(..)
    

2.2.3 AOP各种通知的XML配置

我们新建一个Module命名为day03_04adviceType,然后我们把上一个名为day03_springAOP的Module里的src下的所有内容都拷贝到当前的module的src下。然后我们修改一下Logger.java

/**
 * 用于记录日志的工具类,它里面提共了公共的方法
 */
public class Logger {
    /**
     * 前置通知
     */
    public void beforePrintLog() {
        System.out.println("前置通知:Logger类中的beforePrintLog方法开始记录日志了...");
    }

    /**
     * 后置通知
     */
    public void afterReturningPrintLog() {
        System.out.println("后置通知:Logger类中的afterReturningPrintLog方法开始记录日志了...");
    }

    /**
     * 异常通知
     */
    public void afterThrowingPrintLog() {
        System.out.println("异常通知:Logger类中的afterThrowingPrintLog方法开始记录日志了...");
    }

    /**
     * 最终通知
     */
    public void afterPrintLog() {
        System.out.println("最终通知:Logger类中的afterPrintLog方法开始记录日志了...");
    }
}

修改一下测试类里的main方法

/**
 * 测试AOP的配置
 */
public class AOPTest {
    public static void main(String[] args) {
        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        IAccountService as = (IAccountService) ac.getBean("accountService");
        //3.执行方法
        as.saveAccount();
    }
}

关于AOP各种通知的XML配置请见下面的这个bean.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">

    <!-- 配置spring的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>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--前置通知,在切入点方法执行之前执行-->
            <aop:before method="beforePrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
            <!--后置通知,在切入点方法正常执行之后执行。它和异常通知永远只能执行一个-->
            <aop:after-returning method="afterReturningPrintLog"
                                 pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-returning>
            <!--异常通知,在切入点方法执行产生异常之后执行,相当于 catch 块里的代码。它和后置通知永远只能执行一个-->
            <aop:after-throwing method="afterThrowingPrintLog"
                                pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-throwing>
            <!--最终通知,无论切入点方法是否正常执行它都会执行,就是在 finally 块里的代理-->
            <aop:after method="afterPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>

我们在编写通知处的代码时发现每个地方都要写切入点表达式,在这里的每个切入点表达式都一样,那能不能把这个切入点表达式只写一遍,然后都去引入这个呢?答案当然是可以的。在 aop:aspect 这个标签里存在一个 aop:pointcut 标签,我们可以把切入点表达式写在这个标签里面,然后在需要使用切入点表达式的地方直接引用这个标签即可。bean.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">

    <!-- 配置spring的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>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--前置通知,在切入点方法执行之前执行-->
            <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
            <!--后置通知,在切入点方法正常执行之后执行。它和异常通知永远只能执行一个-->
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
            <!--异常通知,在切入点方法执行产生异常之后执行,相当于 catch 块里的代码。它和后置通知永远只能执行一个-->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
            <!--最终通知,无论切入点方法是否正常执行它都会执行,就是在 finally 块里的代理-->
            <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>

            <!--配置切入点表达式,id属性用于指定表达式的唯一标识。expression属性用于指定表达式的内容
                注意:此标签写在 aop:aspect 标签内部只能在当前切面使用。
                它还可以写在 aop:aspect 标签外面,此时就变成了所有的切面可用
            -->
            <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
        </aop:aspect>
    </aop:config>
</beans>

但是这样写还有一个问题就是 aop:pointcut 标签只能写在 aop:aspect 标签内部只能在当前切面使用,在其他的切面里就不能使用了。还有一个办法,正如在上面的注释中写的, aop:pointcut 标签还可以写在 aop:aspect 标签外面,此时就变成了所有的切面可用了。这样bean.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">

    <!-- 配置spring的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:aspect id="logAdvice" ref="logger">
            <!--前置通知,在切入点方法执行之前执行-->
            <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
            <!--后置通知,在切入点方法正常执行之后执行。它和异常通知永远只能执行一个-->
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
            <!--异常通知,在切入点方法执行产生异常之后执行,相当于 catch 块里的代码。它和后置通知永远只能执行一个-->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
            <!--最终通知,无论切入点方法是否正常执行它都会执行,就是在 finally 块里的代理-->
            <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>

2.2.4 AOP的最后一种通知:环绕通知

在切面 aop:aspect 标签里除了上面我们讲到的前置通知、后置通知、异常通知、最终通知的标签之外,还有一个标签,就是 aop:around 标签标示环绕通知。我们首先修改一下bean.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">

    <!-- 配置spring的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>
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置环绕通知-->
            <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>

然后我们在 Logger 类中创建一个 aroundPrintLog方法如下。

package com.itheima.utils;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 用于记录日志的工具类,它里面提共了公共的方法
 */
public class Logger {
    ...
    public void aroundPrintLog() {
        System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...");
    }
}

下面我们来运行一下这个main方法,控制台输出如下,表明值调用了环绕通知的方法,但是没有调用我 业务方法。
在这里插入图片描述
这是为什么呢?还记得上面讲的什么是环绕通知吗啊?就是下图里的这个invoke这个方法,在动态代理里面这个就是环绕通知。
在这里插入图片描述
在来看一下我们使用Spring的AOP的环绕通知,这里少了很多东西,比如前置通知、对切入点方法的调用、后置通知、异常通知、最终通知等。

public void aroundPrintLog() {
   System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...");
}
  • 环绕通知
    • 问题:
      当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
    • 分析:
      通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点调用,而我们的代码中没有
    • 解决方法:
      Spring框架为我们提供的一个接口:ProceedingJoinPoint。该接口有一个proceed(),此方法就相当于明确调用切入点方法。
      该接口可以作为环绕通知的方法参数,在程序执行时,Spring框架会为我们提供该接口的实现类工我们使用。
    • Spring中的环绕通知:
      它是Spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。

下面就来改变一下这个方法。

public Object aroundPrintLog(ProceedingJoinPoint pjp) {
        Object rtValue;
        try {
            Object[] args = pjp.getArgs();
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了..."); // 写在这里表示前置通知
            rtValue = pjp.proceed(args);// 明确调用业务层方法
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了..."); // 写在这里表示后置通知
            return rtValue;
        } catch (Throwable throwable) { // 这里要使用Throwable来拦截异常
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了..."); // 写在这里表示异常通知
            throw new RuntimeException(throwable);
        } finally {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了..."); // 写在这里表示最终通知
        }
    }

2.3 基于注解的 AOP 配置

2.3.1 使用AOP注解来配置

基于注解的AOP实现起来非常的简单。首先我们来新创建一个Module命名为day03_05annotationAOP,然后配置好pom.xml导入相应的jar包,因为我们的代码和上面额基本一样,所以我们把day03_04adviceType这个Module里的src下的所有文件都拷贝到我们新建的Module的src下。
首先我们来修改一下bean.xml文件,因为我们要使用注解的方式来配置AOP,那我们也把ioc也改成使用注解的方式(注意bean.xml的约束内容的改变,因为需要扫描包,所以要引入 context约束)

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

    <!--配置Spring创建IOC容器时要扫描的包-->
    <context:component-scan base-package="com.itheima"/>

    <!--配置Spring开启注解AOP的支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

然后给class加上IOC的注解

/**
 * 账户的业务层实现类
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
	···
}
/**
 * 用于记录日志的工具类,它里面提共了公共的方法
 */
@Component("logger") // 不属于MVC三层架构里的任何一种,就使用Component注解
public class Logger {
	···
}

下面就来配置AOP的注解

/**
 * 用于记录日志的工具类,它里面提共了公共的方法
 */
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {
    /**
     * 这个方法用于表示切入点表达式
     */
    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1() {
    }

    /**
     * 前置通知
     */
//    @Before("pt1()") // 引用切入点表达式时必须要写pt1后面的()
    public void beforePrintLog() {
        System.out.println("前置通知:Logger类中的beforePrintLog方法开始记录日志了...");
    }

    /**
     * 后置通知
     */
//    @AfterReturning("pt1()")
    public void afterReturningPrintLog() {
        System.out.println("后置通知:Logger类中的afterReturningPrintLog方法开始记录日志了...");
    }

    /**
     * 异常通知
     */
//    @AfterThrowing("pt1()")
    public void afterThrowingPrintLog() {
        System.out.println("异常通知:Logger类中的afterThrowingPrintLog方法开始记录日志了...");
    }

    /**
     * 最终通知
     */
//    @After("pt1()")
    public void afterPrintLog() {
        System.out.println("最终通知:Logger类中的afterPrintLog方法开始记录日志了...");
    }

    @Around("pt1()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp) {
        Object rtValue;
        try {
            Object[] args = pjp.getArgs();
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...前置"); // 写在这里表示前置通知
            rtValue = pjp.proceed(args);// 明确调用业务层方法
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...后置"); // 写在这里表示后置通知
            return rtValue;
        } catch (Throwable throwable) { // 这里要使用Throwable来拦截异常
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...异常"); // 写在这里表示异常通知
            throw new RuntimeException(throwable);
        } finally {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...最终"); // 写在这里表示最终通知
        }
    }
}

我们首先把环绕通知的注解注释掉,打开其他通知的注解,运行一下我们的测试类AOPTest里的main方法,控制台输出如下,可以看出最终通知和后置通知的调用顺序颠倒了,和使用xml配置的不太一样。不符合我们的要求。
在这里插入图片描述
下面我们来打开环绕通知的注解,注释掉其他通知的注解。然后运行一下我们的测试类AOPTest里的main方法,控制台输出如下,可以看出这种才是我们需要的,因为环绕通知里的代码是我们自己写的,所以它的调用顺序我们可以自己控制。(使用注解配置AOP时推荐使用环绕通知)
在这里插入图片描述

2.3.2 基于注解的切入点

在上一节2.3.1中我们对想要进行代理的方法是使用切入点表达式的方式去匹配方法,这个方式不够灵活,如果我们只想对某一个类中的某一个方法进行aop的话,在使用上面的这种形式虽然可能达到我们的目的,但是会对其他的方法也进行代理不够优雅,怎么办呢?spring提供了另一个方式,就是使用注解的方式。
我们先创建一个新的Module命名为day03_06annotationAdvice,然后把day03_05annotationAOP这个Module中src下的所有文件都拷贝到我们新建的Module的src下。
下面我们就新建一个注解LoggerAnnotation用于代理标识,代码如下

package com.itheima.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 用于标识需要记录日志的注解,
 * ElementType.METHOD 表示这个注解可以放在方法上
 * ElementType.TYPE  表示这个注解可以放到 类、接口上
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoggerAnnotation {
}
2.3.2.1 切入点注解Pointcut中@annotation的使用

下面我们修改一下Logger,代码如下

package com.itheima.utils;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 用于记录日志的工具类,它里面提共了公共的方法
 */
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {
    /**
     * 这个方法用于表示切入点表达式,要代理的方法通过切入点表达式解析得到
     */
    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1() {
    }

    /**
     * 这里使用使用 @annotation 标识要使用哪个注解来进行代理
     * 这里 @annotation() 里填写注解的名字
     * 要代理的方法上必须要写该注解
     * 注意 这种方法只能对该注解写在方法起作用,只能对写该注解的方法进行代理
     * 注意 如果注解和当前的这个切面类不在同一个包里就要写全这个注解的路径
     *     如果注解和当前的这个切面类在同一个包里就不用写全这个注解的路径,只填注解名就行
     */
    @Pointcut("@annotation(com.itheima.annotation.LoggerAnnotation)")
    private void ptAnnotation() {
    }

    /*
     * 环绕通知
     */
    //@Around("pt1()")
    @Around("ptAnnotation()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp) {
        Object rtValue;
        try {
            Object[] args = pjp.getArgs();
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...前置"); // 写在这里表示前置通知
            rtValue = pjp.proceed(args);// 明确调用业务层方法
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...后置"); // 写在这里表示后置通知
            return rtValue;
        } catch (Throwable throwable) { // 这里要使用Throwable来拦截异常
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...异常"); // 写在这里表示异常通知
            throw new RuntimeException(throwable);
        } finally {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...最终"); // 写在这里表示最终通知
        }
    }
}

然后在修改一下IAccountService的实现类AccountServiceImpl,代码如下

package com.itheima.service.impl;

import com.itheima.annotation.LoggerAnnotation;
import com.itheima.service.IAccountService;
import org.springframework.stereotype.Service;

/**
 * 账户的业务层实现类
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Override
    @LoggerAnnotation // 增加这个注解用于代理的方法标识
    public void saveAccount() {
        System.out.println("执行了保存");
    }

    @Override
    public void updateAccount(int i) {
        System.out.println("执行了更新");
    }

    @Override
    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}

下面我们来修改下一测试类AOPTest,代码如下

package com.itheima.test;
import com.itheima.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * 测试AOP的配置
 */
public class AOPTest {
    public static void main(String[] args) {
        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        IAccountService as = (IAccountService) ac.getBean("accountService");
        //3.执行方法
        as.saveAccount();
        as.deleteAccount();
    }
}

执行一下测试类AOPTest中的main方法,结果如下,由此可见使用LoggerAnnotation注解也达到了代理方式。没有使用注解的deleteAccount方法就没有被代理。
在这里插入图片描述

2.3.2.2 切入点注解Pointcut中@within的使用

通过上面的测试可以看出要进行代理的方法都需要使用自定义的注解LoggerAnnotation,如果我们想要一个类中的所有方法都进行代理的话就要在该类中所有的方法都使用该注解,不怎么优雅。有什么方法可以在类上使用一个注解然后就可以对该类中的所有方法进行代理呢?答案当然是肯定的,spring已经考虑到这个需求了。下面我们就来介绍切入点注解Pointcut中@within的使用。
因为代码类似,所以我们还在day03_06annotationAdvice这个Module中进行测试。
下面我们再修改一下切面类Logger,代码如下

package com.itheima.utils;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 用于记录日志的工具类,它里面提共了公共的方法
 */
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {
    /**
     * 这个方法用于表示切入点表达式,要代理的方法通过切入点表达式解析得到
     */
    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1() {
    }

    /**
     * 这里使用使用 @annotation 标识要使用哪个注解来进行代理
     * 这里 @annotation() 里填写注解的名字
     * 要代理的方法上必须要写该注解
     * 注意 这种方法只能对该注解写在方法起作用,只能对写该注解的方法进行代理
     * 注意 如果注解和当前的这个切面类不在同一个包里就要写全这个注解的路径
     *     如果注解和当前的这个切面类在同一个包里就不用写全这个注解的路径,只填注解名就行
     */
    @Pointcut("@annotation(com.itheima.annotation.LoggerAnnotation)")
    private void ptAnnotation() {
    }

    /**
     * 这里 @within 与 @annotation 作用类似
     * 唯一不同的是作用在类上,对应的注解放到类上面,就可以把这个类里的所有的方法都代理
     * 要代理的类上必须要写该注解。
     * 如果注解放在方法上,是不会代理的。
     */
    @Pointcut("@within(com.itheima.annotation.LoggerAnnotation)")
    private void ptWithin() {
    }

    /*
     * 环绕通知
     */
    //@Around("pt1()")
    //@Around("ptAnnotation()")
    @Around("ptWithin()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp) {
        Object rtValue;
        try {
            Object[] args = pjp.getArgs();
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...前置"); // 写在这里表示前置通知
            rtValue = pjp.proceed(args);// 明确调用业务层方法
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...后置"); // 写在这里表示后置通知
            return rtValue;
        } catch (Throwable throwable) { // 这里要使用Throwable来拦截异常
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...异常"); // 写在这里表示异常通知
            throw new RuntimeException(throwable);
        } finally {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了...最终"); // 写在这里表示最终通知
        }
    }
}

下面我们再创建一个IAccountService的测试类AccountServiceImpl1用于测试,代码如下

package com.itheima.service.impl;

import com.itheima.annotation.LoggerAnnotation;
import com.itheima.service.IAccountService;
import org.springframework.stereotype.Service;
/**
 * 用与测试在类上添加一个注解,就对该类中所有方法进行代理
 */
@LoggerAnnotation
@Service("accountServiceImpl1")
public class AccountServiceImpl1 implements IAccountService {
    @Override
    public void saveAccount() {
        System.out.println("AccountServiceImpl1 执行了保存");
    }

    @Override
    public void updateAccount(int i) {
        System.out.println("AccountServiceImpl1 执行了更新");
    }

    @Override
    public int deleteAccount() {
        System.out.println("AccountServiceImpl1 执行了删除");
        return 0;
    }
}

我们再来修改一下测试类AOPTest,代码如下

package com.itheima.test;

import com.itheima.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 测试AOP的配置
 */
public class AOPTest {
    public static void main(String[] args) {
        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        IAccountService as = (IAccountService) ac.getBean("accountService");
        //3.执行方法
        as.saveAccount();
        as.deleteAccount();
        System.out.println();

        IAccountService as1 = (IAccountService) ac.getBean("accountServiceImpl1");
        as1.saveAccount();
        as1.deleteAccount();
    }
}

执行测试类AOPTest中的main方法结果如下,使用切入点注解Pointcut中@within的使用对配置指定注解的类里的所有方法都进行了代理,指定的注解只放在方法上,该方法就不会代理。
在这里插入图片描述

2.3.2.3 扩展切入点注解Pointcut中表达式标签

在实际开发中我们大部分都是使用上面介绍了这三种情况,但是spring提供了更多的使用方式,表达式标签共有10种,其中这么对哦表达式又可以组合使用组成更多的使用姿势。
使用表达式的形式进行匹配的标签

execution:用于匹配方法执行的连接点
within:用于匹配指定类型内的方法执行
this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配
args:用于匹配当前执行的方法传入的参数为指定类型的执行方法
bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法

使用自定义注解的形式进行匹配的标签

@within:用于匹配所以持有指定注解类型内的方法
@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
@args:用于匹配当前执行的方法传入的参数持有指定注解的执行
@annotation:用于匹配当前执行方法持有指定注解的方法

除了这里10种标签的使用方式外,pointcut还支持引用的使用
有时,我们可以将切入专门放在一个类中集中定义。其他地方可以通过引用的方式引入其他类中定义的切入点。
例如有如下切面类,该类中定义了两个切入点表达式方法

package com.javacode2018.aop.demo9.test14;
import org.aspectj.lang.annotation.Pointcut;
public class AspectPcDefine {
    @Pointcut("bean(bean1)")
    public void pc1() {
    }
    @Pointcut("bean(bean2)")
    public void pc2() {
    }
}

如果我们想在其他的切面类中也进行拦截代理同样的切入点表达式的方法,就可以使用引用的方式,如下

package com.javacode2018.aop.demo9.test14;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Aspect14 {
    @Pointcut("com.javacode2018.aop.demo9.test14.AspectPcDefine.pc1()")
    public void pointcut1() {
    }
}

除此之外,pointcut还可以组合使用,Pointcut定义时,还可以使用&&、||、!运算符。

  • &&:多个匹配都需要满足
  • ||:多个匹配中只需满足一个
  • !:匹配不满足的情况下

如下所示

@Pointcut("bean(bean1) || bean(bean2)") //匹配bean1或者bean2
@Pointcut("@target(Ann1) && @Annotation(Ann2)") //匹配目标类上有Ann1注解并且目标方法上有Ann2注解
@Pointcut("@target(Ann1) && !@target(Ann2)") // 匹配目标类上有Ann1注解但是没有Ann2注解

具体使用姿势可以参考这里

2.3.3 不使用 XML 的配置方式

配置 SpringConfiguration 类

@Configuration
@ComponentScan(basePackages="com.itheima")
@EnableAspectJAutoProxy // 配置Spring开启注解AOP的支持
public class SpringConfiguration {
}

参考
不同版本的spring说明文档
spring的官方说明文档
Java中的代理模式和装饰者模式
AOP百度百科
@Pointcut 的常用方式:execution,within,this,target,args,@within,@target,@args,@annotation等介绍

Spring AOP配置实战:XML与注解方式详解 Spring AOP(面向切面编程)是一种编程范式,旨在提高代码的模块化,通过将横切关注点(如日志记录、权限控制、事务管理)从业务逻辑中分离出来。其核心概念包括:切面(Aspect):一个模块化的组件,封装了横切逻辑,例如日志切面。连接点(Join Point):程序执行过程中的某个点,如方法调用或异常抛出。切入点(Pointcut):定义哪些连接点将被切面处理,通常通过表达式匹配方法。通知(Advice):切面在特定连接点上执行的动作,如前置通知、后置通知等。 阅读详情

相关推荐

别再瞎调参数了!用MATLAB fmincon优化傅里叶级数,为你的机器人设计“完美”激励轨迹

本文详细介绍了如何利用MATLAB的fmincon优化器设计机器人激励轨迹,通过优化傅里叶级数参数,解决传统试错法的低效问题。文章涵盖了目标函数设计、约束条件转化及优化算法选择,帮助工程师实现高精度的系统辨识和动力学参数优化。

weixin_42613501的博客 240

Spring高手之路19——Spring AOP注解指南

在本文中,我们将深入探索Spring AOP(面向切面编程)的核心概念及其在现代Spring应用中的实际应用。从基本的注解使用到复杂的切面配置,本文将一步步指导你如何利用Spring AOP提升代码的模块化,帮助你在Spring开发路上更进一步。

卓越无关环境,保持空杯心态——靡不有初,鲜克有终 1万+

Uniapp+Vue3+Monorepo:如何优化你的前端开发流程(附完整配置代码)

本文详细介绍了如何利用Uniapp+Vue3+Monorepo技术栈优化前端开发流程,提升代码可维护性和团队协作效率。通过Monorepo架构实现依赖管理智能化、代码复用最大化和构建流程统一化,结合完整配置代码和实战指南,帮助开发者快速构建高性能跨端应用。

weixin_29011993的博客 452

Spring AOP:注解配置与XML配置双实战

这篇文章系统性地讲解了Spring AOP(面向切面编程)。核心在于通过注解(如@Aspect)或XML配置,在不修改原始代码的情况下,为方法动态添加统一功能(如日志、事务、加密等)。文章详细介绍了如何定义​​切点​​来定位目标方法,以及五种​​通知​​类型(如@Before、@Around)来实现增强逻辑。此外,还深入剖析了其底层原理——基于JDK和Cglib的​​动态代理​​,并提供了完整的实战案例,帮助读者从概念到应用全面掌握Spring AOP。

先做到 再看见 2293

grafana 配置页面告警

Rate 下拉选择也可,保持默认也可。By label 选择一个比如选择filename。配置无数据或者错误数据的时候如何处理,这里选择 Normal (表示当做正常不会告警)单机 node_name 标签,选择一个主机,选好后单机 Show logs。单机 New floder,如果之前配置过,也可以选择。单机 Save contact point 保存即可。会跳出如下对话框(如果没有跳出就自己单机选择下)完成后会自动生成如下图,告警规则部分就配置好了。选择告警接收方式(这里选择企业微信 WeCon)

weixin_40548182的博客 3251

Linux Wireless之80211(nl80211, cfg80211, mac80211)

Linux Wireless之80211(nl80211, cfg80211, mac80211)前言在Linux无线子系统中,cfg80211、mac80211和nl80211是三个关键的组件,它们共同工作以实现对802.11设备的配置和管理。cfg80211负责内核空间的通用API,mac80211负责具体的MAC层实现,而nl80211则作为用户空间与内核空间之间的桥梁,用于配置管理和通信。Netlink 是一个 Linux 中的 socket 类型,用于在内核与用户空间之间传递事件。

DD'Notes 1479

Spring AOP开发 XML配置和注解配置

1.AOP开发中的相关术语Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只 支持方法类型的连接点. Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义. Advice(通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知.通知分为前置通知,后置 通知,异常通...

qq_35232663的博客 8845

Android 15.0 首次开机默认授予app运行时权限(去掉运行时授权弹窗)

在15.0的系统rom产品定制化开发中,在6.0以后对于权限的申请,都需要动态申请,所以会在系统首次启动后,在app的首次运行时, 会弹出授权窗口,会让用户手动授予app运行时权限,在由于系统产品开发需要要求默认授予app运行时权限,不需要用户默认授予运行时弹窗,所以需要在首次开机默认授予所有app运行时权限

安卓兼职framework和app工程师的博客 1204

【Spring aop】Spring aop的XML和注解的两种配置实现

一:XML形式配置 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context

flysun的博客 1万+

一次点击跑通:用 Postman 自动获取并携带 CSRF token,测试 SAP OData 写操作

SAP OData 写操作自动化测试方案 针对 SAP Gateway OData 服务写操作中常见的 CSRF token 验证问题,本文提出了一套基于 Postman 的自动化解决方案: 问题分析:解释了 CSRF token 的验证机制及其必要性,说明手工测试效率低下的原因 解决方案: 使用 Postman 环境变量存储 token 通过脚本自动获取并传递 token 利用 Collection Runner 实现一键执行 实施步骤: 创建环境变量存储 token 第一个请求获取 token 并存入变

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 301

Unreal5 从入门到精通之 学习Niagara特效系统

UE5 Niagara粒子系统解析 Niagara是UE5中的高性能粒子特效系统,相比传统粒子系统具有完全可编程性和可视化节点编辑两大特点。核心单元发射器(Emitter)分为模板发射器(预设效果如喷泉、火焰)和空白发射器(高度自定义)。系统提供13种预设模板,包括吹飞粒子、五彩纸屑爆发、动态光束等常见特效。 Niagara采用三级模块化设计: 系统级:全局参数设置与更新 发射器级:控制粒子生成规则与行为 粒子级:处理单个粒子的生命周期和渲染 关键模块包括初始化粒子属性、物理模拟、颜色渐变等,支持GPU加速

TxNet 1119

Spring AOP三种配置方式深度解析:XML、注解与纯注解实战指南

摘要:本文全面解析Spring AOP的三种配置方式。XML配置实现完全解耦但冗长;注解配置平衡灵活性,需结合XML启用;纯注解方式最为推荐,提供类型安全和重构支持。三者在类型安全、重构友好度等方面各具特点:XML适用于遗留系统,注解适合传统项目,纯注解是SpringBoot首选。文章通过代码示例对比分析,建议新项目优先采用纯注解,老项目逐步迁移,并提供了性能优化、切面组织等最佳实践。AOP的本质是通过关注点分离提升系统可维护性,合理运用可显著改善代码质量。

weixin_62938484的博客 1151

银河麒麟桌面系统批量交付:封装自动化ISO镜像,百台装机效率提升10倍

麒麟桌面系统自动化ISO镜像定制摘要 本文详细介绍麒麟桌面版ISO安装镜像的定制方法,通过修改系统文件实现自动化安装部署。主要内容包括:1) 挂载原版ISO并提取文件;2) 使用unsquashfs解压系统文件进行定制化修改,包括添加系统标记配置;3) 配置ky-installer.cfg自动应答文件,设置安装参数如用户名、密码、分区方案等;4) 修改grub引导配置启用自动安装模式。该流程支持磁盘加密、自定义分区、自动登录等功能,生成的定制镜像可满足批量部署需求,显著提升系统安装效率。

信创运维工程师 · 银河麒麟 & 容器实战 | 金融生产环境迁移·等保加固 | 142篇原创·17万+阅读 1502

Spring 配置AOP Spring注解配置AOP Spring XML 配置AOP Spring Aspect AOP

Spring 配置AOP Spring注解配置AOP Spring XML 配置AOP Spring Aspect AOP

HaHa_Sir的博客 1109

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Spring(三)AOP及基于XML和注解的AOP配置

文章目录1.AOP引入1.1.案例中问题1.2. 问题的解决1.3.新的问题1.4.新问题的解决动态代理回顾基于接口的动态代理基于子类的动态代理解决案例中的问题2.AOP相关概念2.1.什么是AOP2.1.AOP的作用和优势2.2.AOP 相关术语2.3.学习 spring 中的 AOP 要明确的事2.4.关于代理的选择3.基于XML的AOP配置3.1.环境搭建3.2.AOP 的配置步骤3.3.切入点表达式说明3.4.环绕通知4.基于注解的AOP配置4.1.环境搭建4.2.配置步骤环绕通知注解配置纯注解三级

weixin_45077780的博客 713

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

【Spring】AOP:基本概念及aop示例(包括xml、注解)

1.AOP简介 1.1 定义 面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。 1.2 优点 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 1.3 原理 aop底层将采用代理机制进行实现。 接口 + 实现类:spring 默认采用 jdk 的动态代理 Proxy。 实现类:spring 默认采用 cglib 字节码增强。 1.4 基本概念 target:目标类,需要被代理的类。例如:Use

A minor 2849

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

(38)spring(3)动态代理、AOP的相关概念、Spring中基于XML和基于注解的AOP配置

这里写目录标题一、动态代理1.基于接口的动态代理:2.基于子类的动态代理:3.动态代理的优点: 一、动态代理 动态代理的特点:字节码随用随创建,随用随加载。 作用:不修改源码的基础上,对方法进行加强。 分类:基于接口的动态代理;基于子类的动态代理。 1.基于接口的动态代理: 1)涉及的类:Proxy 2) 提供者:JDK官方 3)如何创建代理对象:使用Proxy类中的newProxyInstance方法。 4)创建代理对象的要求:被代理对象最少实现一个接口,如果没有则不能使用。 5) newProxyI

qq_44667083的博客 590

spring中基于xml和注解的AOP实现

所谓AOP, 即指的事面向切面编程.AOP的优势主要在于在程序运行期间,可以实现在不修改源码的情况下对已有方法进行增强.AOP的本质其实使用的动态代理技术. AOP的XML配置 在resources下新建一个bean.xml的文件, 然后导入aop配置. <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://w...

智障小鲁班 1146

Spring 基于注解与XML的AOP配置

通俗易懂!2025 年真实情况:S(一图看懂谁才是王道)

likuoelie的博客 790

Spring系统学习 - AOP之基于注解的AOP和XML的AOP

基于注解的 AOP 是一种使用注解来定义切面和通知的方式。在传统的基于 XML 配置的 AOP 中,切面和通知的定义通常是通过 XML 配置文件来完成的,而基于注解的 AOP 则使用注解来实现这些定义,使得配置更加简洁和直观。

好看的皮囊千篇一律,有趣的灵魂万里挑一。 867

Spring中AOP及配置(注解和xml)

AOP AOP:全称是 Aspect Oriented Programming 即:面向切面编程。 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 把我们程

小孩与狗的博客 591

Spring AOP的注解配置

Spring AOP的注解配置 (一)简述 上回我们说到了spring AOP的实现原理,我们发现其实通过自己编程来实现这个功能还是非常繁琐的。spring因此给我们提供了相应的配置方式,从而帮助我们简化开发过程,使我们的注意力能充分关注在业务逻辑上。 在了解AOP的注解配置之前,我们先要来关注一下AOP中的一些专业术语: Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。 Pointcut(切入点):所谓切入点是指我们

物是人非,结局始终不美 7206

Spring中的aop和基于XML以及注解的AOP配置

Spring中的aop和基于XML以及注解的AOP配置 完善account案例 E:\JAVAworkspace\spring_account 分析案例中的问题 各个数据库操作应该只获取一个connection连接,把这些此操作放到一个事务中,避免产生脏读、不可重复读、幻读等问题。 ConnectionUtils类:连接的工具类,用于从数据源中获取一个连接,并且实现和线程的绑定。使用ThreadLocal对象把Connection和当前线程绑定,从而使一个线程中只有一个能控制事务的对象 priv

weixin_37652146的博客 158

SPRING03_AOP的概述、动态代理、cglib代理、相关概念、基于xml配置、基于注解配置

文章目录①. Spring的AOP简介②. 动态代理③. cglib的动态代理④. AOP的相关概念⑤. 基于XML的AOP开发⑥. XML配置AOP详解⑦. 基于注解的AOP开发 ①. Spring的AOP简介 ①. AOP为 Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术 AOP是OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以

所得皆惊喜 504

Spring学习笔记:基于注解的Sprig AOP配置的深入学习

本文主要介绍了Spring AOP的注解配置方式,对比了XML与注解两种配置方案的优缺点。通过示例代码展示了如何通过@Aspect注解定义切面类,使用@Pointcut声明切入点,以及利用@Before等注解配置通知。文章还详细说明了如何通过XML或Java配置开启AOP注解支持,并分析了Spring AOP与AspectJ框架的区别。注解方式具有代码直观、模块化等优势,Spring团队也推荐使用基于注解的配置方案。

FeiChangWuRao的博客 824
上一篇: Java HashMap的put过程
下一篇: Java框架Spring学习教程从入门到精通
Code_Boy_Code
博客等级 码龄9年 650粉丝 28原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值