spring框架:简述AOP的使用(xml方式和注解方式)

本人小白一枚,欢迎大家一起讨论学习,如有错误,还望大家指教。

AOP概述

AOP的概念:
AOP,全称Apect Oriented Programming,译为面向切面编程,简单的说它可以帮我们把程序中重复的代码抽取出去,在需要执行的时候,使用动态代理技术,在不修改源码的基础上,对我们已有的方法进行增强。
在这里插入图片描述
AOP的作用及优势:
作用:在程序运行期间,不修改源码对已有的方法进行增强。
优势:减少重复代码,提高开发效率,维护方便。
AOP相关术语:
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
Advice(通知/增强):所谓通知是指拦截到Joinpoint之后要做的事情就是五种。通知的类型有五种:分别是前置通知,后置通知,异常通知,最终通知以及环绕通知。
Introduction(引介):引介是一种特殊的通知,是在不修改类代码的前提下,Introduction可以在运行期间为类动态地添加一些方法或Field。
Target(代理对象):代理的目标对象。
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程,spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy(代理):一个类被AOp植入增强后,就产生一个结果代理类。
Apect(切面):是切入点和通知(引介)的结合。

基于XML的AOP配置

  • <aop:config></aop:config>:用于声明开始aop的配置,aop的相关配置都在这个标签之内。
  • <aop:aspect></aop:aspect>:用于配置切面信息,属性id用于给切面提供一个唯一的表示,属性ref用于引用配置好的通知类bean的id。
  • <aop:pointcut></aop:pointcut>:用于配置切入点表达式,就是指定对哪些类的那些方法进行增强。属性expression用于定义切入点表达式,id用于给切入点表达式提供一个唯一标识。
  • <aop:xxx></aop:xxx>:用于配置对应的通知类型,这个标签在<aop:aspect></aop:aspect>标签中使用。
    • <aop:before></aop:before>:用于配置前置通知,在切入点方法之前执行。
      • method:用于指定通知类中的增强方法的名称。
      • pointcut-ref:用于指定切入点的表达式的引用。
      • pointcut:用于指定切入点表达式。
    • <aop:after-returning></aop:after-returning> :用于配置后置通知,在切入点方法正常执行后再执行,它和异常通知只能有一个执行,该标签具有的属性同上。
    • <aop:after-throwing></aop:after-throwing>:用于配置异常通知,在切入点方法发生异常后执行,它和后置通知只能执行一个,该标签具有的属性同上。
    • <aop:after></aop:after>:用于配置最终通知,无论切入点执行时是否发生异常,它都会在后面执行,该标签具有的属性同上。
    • <aop:around></aop:around>:用于配置环绕通知,它是spring框架为我们提供的一种可以手动控制增强代码什么时候执行的方式,通常情况下,环绕通知都是独立使用的,该标签具有的属性同上。
  • execution:对匹配到的方法进行增强,后接表达式,表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
    • public void com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account):对指定路径下的saveAccount方法且该方法有一个参数,类型为指定路径的Account。
    • * com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account):返回值可以使用*号,表示任意返回值。
    • * *.*.*.*.AccountServiceImpl.saveAccount(com.itheima.domain.Account):包名可以使用*号,表示任意包,但是有几级包,需要写几个* 。
    • * com..AccountServiceImpl.saveAccount(com.itheima.domain.Account) :使用…来表示当前包,及其子包 。
    • * com..*.saveAccount(com.itheima.domain.Account):类名可以使用*号,表示任意类 。
    • * com..*.*( com.itheima.domain.Account):方法名可以使用*号,表示任意方法。
    • * com..*.*(*):参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数 。
    • * com..*.*(..):参数列表可以使用…表示有无参数均可,有参数可以是任意类型。
    • * *..*.*(..):全通配方式,表示所有包下的所有方法,通常情况下我们只对业务层代码进行增强,所以切入点表达式都是指定业务层实现类。如按照上述路径,我们应该这么写execution(* com.itheima.service.impl.*.*(..))

使用演示:同样采用转账案例。

  • 创建maven工程,并在其中添加所需的坐标。
    <dependencies>
        <!--spring坐标-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--dbutils坐标-->
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>
        <!--数据库驱动坐标-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <!--连接池坐标-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.9</version>
        </dependency>
        <!--添加对aop相关的坐标-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>
  • 创建entity包,并添加Account实体类,下图展示数据表的结构以及记录数。
    在这里插入图片描述
    在这里插入图片描述
public class Account implements Serializable {
    private Integer id;
    private String name;
    private Float money;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Float getMoney() {
        return money;
    }

    public void setMoney(Float money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
  • 创建utils包并添加获取连接工具类以及事务管理类。
/**
 * 获取连接工具类,用于从连接池中获取一个连接,并且实现连接与线程绑定。
 */
public class ConnectionUtils {
    
    private ThreadLocal<Connection> tl;
    private DataSource dataSource;

    public void setTl(ThreadLocal<Connection> tl) {
        this.tl = tl;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Connection createConnection() {
        Connection connection = null;
        try {
            // 先从ThreadLocal获取连接
            connection = tl.get();
            // 如果当前线程没有连接,则从数据源中获取一个连接,并将连接与线程绑定
            if (connection == null) {
                connection = dataSource.getConnection();
                tl.set(connection);
            }
            return connection;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 将连接和线程解绑
     */
    public void removeConnection() {
        tl.remove();
    }
}
/**
 * 事务管理工具类,它包含了开始事务,提交事务,回滚事务和释放连接
 */
public class TransactionManager {
    private ConnectionUtils utils;

    public void setUtils(ConnectionUtils utils) {
        this.utils = utils;
    }

    /**
     * 关闭自动提交,开启手动事务
     */
    public void beginTransaction() {
        try {
            utils.createConnection().setAutoCommit(false);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 提交事务
     */
    public void commit() {
        try {
            utils.createConnection().commit();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 回滚事务
     */
    public void rollback() {
        try {
            utils.createConnection().rollback();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 释放连接,这里使用了连接池技术,所以这里的释放就是将连接归还到连接池,同时别忘了将线程与连接进行解绑
     */
    public void close() {
        try {
            utils.createConnection().close();
            utils.removeConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 创建dao包并添加对应接口以及实现类。
public interface AccountDao {
    /**
     * 通过姓名查找账户信息
     * @param name 账户姓名
     * @return
     */
    Account findByName(String name);

    /**
     * 修改账户信息
     * @param account
     */
    void updateAccount(Account account);
}
public class AccountDaoImpl implements AccountDao {
    private QueryRunner runner;
    private ConnectionUtils utils;

    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }

    public void setUtils(ConnectionUtils utils) {
        this.utils = utils;
    }

    @Override
    public Account findByName(String name) {
        List<Account> accounts = null;
        try {
            accounts = runner.query( utils.createConnection(), "SELECT * FROM account WHERE name = ?", new BeanListHandler<Account>(Account.class), name);
            if(accounts == null || accounts.size() == 0){
                return null;
            }
            if(accounts.size() > 1){
                throw new RuntimeException("结果集不唯一,数据有问题");
            }
            return accounts.get(0);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void updateAccount(Account account) {
        try {
            runner.update( utils.createConnection(), "UPDATE account SET name = ?, money = ? WHERE id = ? ", account.getName(), account.getMoney(), account.getId());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 创建service包并添加对应接口以及实现类。
public interface AccountService {

    /**
     * 更新用户
     */
    void updateAccount(Account account);

    /**
     * @param sourceName 转出账户名称
     * @param targetName 转入账户名称
     * @param money 转账金额
     */
    void transferAccount(String sourceName, String targetName, Float money);
}
public class AccountServiceImpl implements AccountService {

    private AccountDao dao;

    public AccountDao getDao() {
        return dao;
    }

    public void setDao(AccountDao dao) {
        this.dao = dao;
    }

    @Override
    public void updateAccount(Account account) {
        dao.updateAccount(account);
    }

    @Override
    public void transferAccount(String sourceName, String targetName, Float money) {
        Account sourceAccount = dao.findByName(sourceName);
        Account targetAccount = dao.findByName(targetName);
        sourceAccount.setMoney(sourceAccount.getMoney() - money);
        targetAccount.setMoney(targetAccount.getMoney() + money);
        dao.updateAccount(sourceAccount);
        // 手动添加异常
        int i = 10 / 0;
        dao.updateAccount(targetAccount);
    }
}
  • 在resource目录下创建xml配置文件,并在其中添加配置信息,这里要注意一下,以上类的属性要加上set方法,因为这里我们要都是用的是set方式进行注入的。
<?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">
    <!--配置持久层-->
    <bean id="accountDao" class="dao.impl.AccountDaoImpl">
        <property name="utils" ref="connect"/>
        <property name="runner" ref="runner"/>
    </bean>
    <!--配置事务层-->
    <bean id="accountService" class="service.impl.AccountServiceImpl">
        <property name="dao" ref="accountDao"/>
    </bean>
    <bean id="tl" class="java.lang.ThreadLocal"/>
    <!--配置连接工具类-->
    <bean id="connect" class="utils.ConnectionUtils">
        <property name="dataSource" ref="dataSource"/>
        <property name="tl" ref="tl"/>
    </bean>
    <!--配置事务管理类-->
    <bean id="transactionManager" class="utils.TransactionManager">
        <property name="utils" ref="connect"/>
    </bean>
    <!--配置数据库操作对象-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>
    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql:///frame?characterEncoding=utf-8"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    <!--声明事务-->
    <aop:config>
        <aop:pointcut id="pt1" expression="execution(* service.impl.*.*(..))"></aop:pointcut>
        <aop:aspect id="txAdvice" ref="transactionManager">
            <aop:before method="beginTransaction" pointcut="execution(* service.impl.*.*(..))"/>
            <aop:after-returning method="commit" pointcut-ref="pt1"/>
            <aop:after-throwing method="rollback" pointcut-ref="pt1"/>
            <aop:after method="close" pointcut-ref="pt1"/>
            <!--<aop:around method="transactionAround" pointcut-ref="pt1"/>-->
        </aop:aspect>
    </aop:config>
</beans>
  • 创建controller包并添加测试方法,因为我在业务层的转账操作手动添加了异常,看看转账操作是否成功回滚。
public class Demo {
    public static void main(String[] args) {
        try {
            ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
            AccountService service = context.getBean("accountService", AccountService.class);
            System.out.println("开始转账~~~");
            service.transferAccount("周星星", "刘灰灰", 500f);
            System.out.println("转账成功~~~");
        } catch (Exception e) {
            System.out.println("转账失败~~~");
        }
    }
}

在这里插入图片描述
在这里插入图片描述

  • 因为在业务层的转账操作去除异常并查看结果。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 对于xml方式配置aop的总结,通过上面的测试我们发现,我们成功的将事务增强到业务层的方法中,但是我们只使用了前置通知,后置通知,异常通知以及最终通知,并没有使用环绕通知,所以在这里补充下环绕通知。
    在这里插入图片描述
  • 在事务管理类中添加一个方法,用以表示环绕通知,环绕通知就是我们可以自定义增强方法的执行顺序。spring为我们提供了一个接口让我们可以更便利的定义该方法。
    在这里插入图片描述
  • 修改xml配置文件,添加环绕通知的配置信息,同时将其余四个通知注释,再次测试,成功运行。
    在这里插入图片描述

基于注解的AOP配置

  • @Aspect:用于声明当前类为切面类,在类上使用。
  • @Before:用于声明该方法为前置通知,在方法上使用。
    • value:用于指定切入点表达式,还可以指定切入点表达式的引用。
  • @AfterReturning:用于声明该方法为后置通知,在方法上使用,该注解的属性同上。
  • @AfterThrowing:用于声明该方法为异常通知,在方法上使用,该注解的属性同上。
  • @After:用于声明该方法为最终通知,在方法上使用,该注解的属性同上。
  • @Around:用于声明该方法为环绕通知,在方法上使用,该注解的属性同上。
  • @Pointcut:指定切入点表达式,在通知类的方法上使用。
    • value:指定表达式的内容。

使用演示:同样采用转账案例,这里使用纯注解方式。

  • 创建maven工程,并在其中添加所需的坐标,直接复制上一个案例的pom文件即可。

  • 创建entity包,并添加Account实体类,这里同上使用上一个案例的数据表,同时将记录恢复到演示前。
    在这里插入图片描述

  • 创建utils包并添加获取连接工具类以及事务管理类,因为这里使用了注解,我们就可以不用添加set方法。

/**
 * 获取连接工具类,用于从连接池中获取一个连接,并且实现连接与线程绑定。
 */
@Component
public class ConnectionUtils {
    @Autowired
    private ThreadLocal<Connection> tl;
    @Autowired
    private DataSource dataSource;
    
    public Connection createConnection() {
        Connection connection = null;
        try {
            // 先从ThreadLocal获取连接
            connection = tl.get();
            // 如果当前线程没有连接,则从数据源中获取一个连接,并将连接与线程绑定
            if (connection == null) {
                connection = dataSource.getConnection();
                tl.set(connection);
            }
            return connection;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 将连接和线程解绑
     */
    public void removeConnection() {
        tl.remove();
    }
}
@Component
// 使用该注解表明这是一个切面
@Aspect
public class TransactionManager {

    @Autowired
    private ConnectionUtils utils;
    
    @Pointcut("execution(* service.impl.*.*(..))")
    private void pt1() {}

    /**
     * 关闭自动提交,开启手动事务
     */
    @Before("pt1()")
    public void beginTransaction() {
        try {
            utils.createConnection().setAutoCommit(false);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 提交事务
     */
    @AfterReturning("execution(* service.impl.*.*(..)))")
    public void commit() {
        try {
            utils.createConnection().commit();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 回滚事务
     */
    @AfterThrowing("pt1()")
    public void rollback() {
        try {
            utils.createConnection().rollback();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 释放连接,这里使用了连接池技术,所以这里的释放就是将连接归还到连接池,同时别忘了将线程与连接进行解绑
     */
    @After("pt1()")
    public void close() {
        try {
            utils.createConnection().close();
            utils.removeConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 创建dao包并添加对应接口以及实现类。
public interface AccountDao {
    /**
     * 通过姓名查找账户信息
     * @param name 账户姓名
     * @return
     */
    Account findByName(String name);

    /**
     * 修改账户信息
     * @param account
     */
    void updateAccount(Account account);
}
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private QueryRunner runner;
    @Autowired
    private ConnectionUtils utils;

    @Override
    public Account findByName(String name) {
        List<Account> accounts = null;
        try {
            accounts = runner.query( utils.createConnection(), "SELECT * FROM account WHERE name = ?", new BeanListHandler<Account>(Account.class), name);
            if(accounts == null || accounts.size() == 0){
                return null;
            }
            if(accounts.size() > 1){
                throw new RuntimeException("结果集不唯一,数据有问题");
            }
            return accounts.get(0);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void updateAccount(Account account) {
        try {
            runner.update( utils.createConnection(), "UPDATE account SET name = ?, money = ? WHERE id = ? ", account.getName(), account.getMoney(), account.getId());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 创建service包并添加对应接口以及实现类。
public interface AccountService {

    /**
     * 更新用户
     */
    void updateAccount(Account account);

    /**
     * @param sourceName 转出账户名称
     * @param targetName 转入账户名称
     * @param money 转账金额
     */
    void transferAccount(String sourceName, String targetName, Float money);
}
@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao dao;

    @Override
    public void updateAccount(Account account) {
        dao.updateAccount(account);
    }

    @Override
    public void transferAccount(String sourceName, String targetName, Float money) {
        Account sourceAccount = dao.findByName(sourceName);
        Account targetAccount = dao.findByName(targetName);
        sourceAccount.setMoney(sourceAccount.getMoney() - money);
        targetAccount.setMoney(targetAccount.getMoney() + money);
        dao.updateAccount(sourceAccount);
        // 手动添加异常
        // int i = 10 / 0;
        dao.updateAccount(targetAccount);
    }
}
  • 创建config包,并添加配置类以及数据库配置类,同时数据库配置类引用了db.properties文件。
    在这里插入图片描述
@PropertySource("db.properties")
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean("dataSource")
    public DataSource createDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean("runner")
    public QueryRunner createQueryRunner(@Qualifier("dataSource")DataSource dataSource) {
        return new QueryRunner(dataSource);
    }
}
@Configuration
@ComponentScan({"dao", "service", "utils"})
// 该注解表示开启对AOP的支持
@EnableAspectJAutoProxy
@Import(JdbcConfig.class)
public class SpringConfig {
    @Bean("tl")
    public ThreadLocal createThreadLocal() {
        return new ThreadLocal();
    }
}
  • 创建controller包并添加测试方法,注意,此时的转账操作并没有异常,这里可以对比下使用xml方式以及注解方式,获取ApplicationContext的方式是不一样的,当我们执行测试时,在没有异常的情况下,我的猜想是顺利转账,但是结果却是报出了一个异常,根据异常信息显示,它提示我们不能进行commit,也就是事务提交操作,因为现在是自动提交事务即autocommit=true。原因就是当我们使用注解方式配置AOP时,spring有个bug,就是它会先执行最终通知,再回执行后置通知,也就是后置通知与最终通知的顺序是反的,想要解决这个问题,我们可以使用环绕通知来解决问题。
public class Demo {
    public static void main(String[] args) {
        try {
            ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
            AccountService service = context.getBean("accountService", AccountService.class);
            System.out.println("开始转账~~~");
            service.transferAccount("周星星", "刘灰灰", 500f);
            System.out.println("转账成功~~~");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("转账失败~~~");
        }
    }
}

在这里插入图片描述

  • 在通知类添加环绕通知,已解决上述的问题,这里注意一下,因为使用环绕通知,所以将以上四个通知的注解全部注释。
@Component
// 使用该注解表明这是一个切面
@Aspect
public class TransactionManager {

    @Autowired
    private ConnectionUtils utils;

    @Pointcut("execution(* service.impl.*.*(..))")
    private void pt1() {}

    /**
     * 关闭自动提交,开启手动事务
     */
//    @Before("pt1()")
    public void beginTransaction() {
        try {
            utils.createConnection().setAutoCommit(false);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 提交事务
     */
//    @AfterReturning("execution(* service.impl.*.*(..)))")
    public void commit() {
        try {
            utils.createConnection().commit();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 回滚事务
     */
//    @AfterThrowing("pt1()")
    public void rollback() {
        try {
            utils.createConnection().rollback();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 释放连接,这里使用了连接池技术,所以这里的释放就是将连接归还到连接池,同时别忘了将线程与连接进行解绑
     */
//    @After("pt1()")
    public void close() {
        try {
            utils.createConnection().close();
            utils.removeConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 使用环绕通知来解决后置通知与最终通知的顺序问题
     */
    @Around("pt1()")
    public Object transactionAround(ProceedingJoinPoint pjp) {
        Object rtValue = null;
        try {
            // 获取方法执行所需的参数
            Object[] args = pjp.getArgs();
            // 开启事务
            beginTransaction();
            // 执行方法
            rtValue = pjp.proceed(args);
            // 提交事务
            commit();
        } catch (Throwable e) {
            // 回滚事务
            rollback();
            e.printStackTrace();
        } finally {
            // 释放资源
            close();
        }
        return rtValue;
    }
}

在这里插入图片描述
总结:这里就不对纯注解方式的AOP测试进行详细的描述了,大家可以自行测试,并且上述代码我已经亲测没有问题。同时我还要在强调一个问题,就是咋们使用纯注解时,可以在主配置类上使用@EnableAspectJAutoProxy注解表示开始AOP支持,如果我们使用注解加xml混合配置时,我们如果在配置中开始对AOP的支持,结果肯定很简单地,就是在配置文件中添加以下注解,用以开启AOP的支持,在<beans></beans>标签中。

<!-- 开启 spring 对注解 AOP 的支持 --> 
<aop:aspectj-autoproxy/> 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值