spring 框架学习安排

1. 第三天

1. 完善我们的account基于xml开发的IOC案例

  1. 准备案例

    1. dao层及实现类
    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {	
        /**
         * 查询所有
         * @return
         */
        List<Account> findAllAccount();
    
        /**
         * 查询一个
         * @return
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 保存
         * @param account
         */
        void saveAccount(Account account);
    
        /**
         * 更新
         * @param account
         */
        void updateAccount(Account account);	
    }
    
    
    /**
     * 账户的持久层实现类
     */
    public class AccountDaoImpl implements IAccountDao {
    
        private QueryRunner runner;
        public void setRunner(QueryRunner runner) {
            this.runner = runner;
        }
    
    
        public List<Account> findAllAccount() {
            try{
                return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
            }catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
    
        public Account findAccountById(Integer accountId) {
            try{
                return runner.query("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("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("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
            }catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    
    1. service层及实现类
    /**
     * 账户的业务层接口
     */
    public interface IAccountService {
    
        /**
         * 查询所有
         * @return
         */
        List<Account> findAllAccount();
    
        /**
         * 查询一个
         * @return
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 保存
         * @param account
         */
        void saveAccount(Account account);
    
        /**
         * 更新
         * @param account
         */
        void updateAccount(Account account);
    }
    
    
    /**
     * 账户的业务层实现类
     */
    @Service("accountService")
    public class AccountServiceImpl implements IAccountService{
    
        @Autowired
        private IAccountDao accountDao;
    
    
        public List<Account> findAllAccount() {
            return accountDao.findAllAccount();
        }
    
    
        public Account findAccountById(Integer accountId) {
            return accountDao.findAccountById(accountId);
        }
    
    
        public void saveAccount(Account account) {
            accountDao.saveAccount(account);
        }
    
    
        public void updateAccount(Account account) {
            accountDao.updateAccount(account);
        }
    
    
        public void deleteAccount(Integer acccountId) {
            accountDao.deleteAccount(acccountId);
        }
    }
    
    
    1. 实体类
    /**
     * 账户的实体类
     */
    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 +
                    '}';
        }
    }
    
    
    1. 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="accountService" class="com.liuzeyu.service.impl.AccountServiceImpl">
            <!-- 注入dao -->
            <property name="accountDao" ref="accountDao"></property>
        </bean>
    
        <!--配置Dao对象-->
        <bean id="accountDao" class="com.liuzeyu.dao.impl.AccountDaoImpl">
            <!-- 注入QueryRunner -->
            <property name="runner" ref="runner"></property>
        </bean>
    
        <!--配置QueryRunner-->
        <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/eesy"></property>
            <property name="user" value="root"></property>
            <property name="password" value="809080"></property>
        </bean>
    </beans>
    
    1. 测试类
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:bean.xml")
    public class AnnotationTest {
    
        @Autowired
        private IAccountService service;
    }
    
  2. 为service添加转账方法,制造异常

      public void transfer(String sourceName, String targetName, Float money) {
                //2.执行操作
                //2.1根据sourceName获取转出账户
                Account sourceAccount = accountDao.findAccountByName(sourceName);
                //2.2根据targetName获取转入账户
                Account targetAccount = accountDao.findAccountByName(targetName);
                //2.3转出账户数据更新(减钱)
                Float smoney = sourceAccount.getMoney();
                smoney -=money;
                sourceAccount.setMoney(smoney);
                accountDao.updateAccount(sourceAccount);
                //2.4转入账户数据更新(加钱)
                Float tmoney = targetAccount.getMoney();
                tmoney +=money;
                targetAccount.setMoney(tmoney);
    ---->       int i = 1/0;
                accountDao.updateAccount(targetAccount);
        }
    
  3. 测试函数

        @Test
        public void testTransfer(){
            service.transfer("aaa","bbb",100f);
        }
    
  4. 发现问题:aaa的钱可以减少,但是bbb的钱不可以增加,这是不被允许

  5. 问题分析,如下图:

    在转账方法中,每一次连接数据库都获取新的Connection对象,从数据库连接池中获取了4次连接,前三次的每一次事务都正常提交,知道遇到除0异常,第四个无法提交。如何解决这一问题呢?

  6. 解决方案

    分析:上述四次与数据库建立连接应当由同一个Connection对象完成,要成功都成功,要失败全都要失败。并且这个Connection与当前线程进行绑定,从而使一个线程中只有一个控制事务的Connection对象。

  7. 实现

    1. 准备连接和线程绑定的工具类
    /**
     * Created by liuzeyu on 2020/4/22.
     *  * 工具类:作用是用于从数据库连接池获取一个连接并和当前线程绑定
     */
    public class ConnectionUtils {
        private ThreadLocal<Connection> t1 = new ThreadLocal<Connection>();
        private DataSource dataSource;
    
        public void setDataSource(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        /**
         * 获取当前线程上的连接
         */
        public Connection getLocalThreadConnection(){
            try{
                //1.先从ThreadLocal上获取
                Connection conn = t1.get();
                //2.判断当前线程是否有连接
                if( conn == null){
                    //3.从数据源获取一个连接,并存入ThreadLocal中
                    conn = dataSource.getConnection();
                    t1.set(conn);
                }
                //4.返回当前连接
                return conn;
            }catch (Exception e){
                throw new RuntimeException(e);
            }
        }
    
    
        /**
         * 处理连接已经返回到连接池中了,但是线程还是和连接绑定在一起
         */
        public void removeConnection(){
            t1.remove();  //线程和连接绑定
        }
    }
    
    
    1. 准备事务管理工具类
    /**
     * Created by liuzeyu on 2020/4/22.
      和事务管理相关的工具类,它包含了事务提交,事务开启,事务回滚
    */
    public class TransactionManger {
        private ConnectionUtils connectionUtils;
    
        public void setConnectionUtils(ConnectionUtils connectionUtils) {
            this.connectionUtils = connectionUtils;
        }
    
        //开启事务
        public void beginTransaction(){
            try {
                connectionUtils.getLocalThreadConnection().setAutoCommit(false);  //返回连接池中
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
        //提交事务
        public void commitTransaction(){
            try {
                connectionUtils.getLocalThreadConnection().commit();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
        //回滚事务
        public void rollbackTransaction(){
            try {
                connectionUtils.getLocalThreadConnection().rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
        //释放连接
        public void release(){
            try {
                connectionUtils.getLocalThreadConnection().close();
                connectionUtils.removeConnection();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
    }
    
    
    1. 修改Connection获取方式,修改为从的ThreadLocal中获取
    /**
     * 账户的持久层实现类
     */
    public class AccountDaoImpl implements IAccountDao {
    
        private QueryRunner runner;
        private ConnectionUtils connectionUtils;
    
        public void setConnectionUtils(ConnectionUtils connectionUtils) {
            this.connectionUtils = connectionUtils;
        }
    
        public void setRunner(QueryRunner runner) {
            this.runner = runner;
        }
    
    
        public List<Account> findAllAccount() {
            try{
                return runner.query(connectionUtils.getLocalThreadConnection(),"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.getLocalThreadConnection(),"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.getLocalThreadConnection(),"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.getLocalThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
            }catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    1. 修改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="accountService" class="com.liuzeyu.service.impl.AccountServiceImpl">
            <!-- 注入dao -->
            <property name="accountDao" ref="accountDao"></property>
            <!--注入事务管理器-->
            <property name="txManger" ref="txManger"></property>
        </bean>
    
        <!--配置Dao对象-->
        <bean id="accountDao" class="com.liuzeyu.dao.impl.AccountDaoImpl">
            <!-- 注入QueryRunner -->
            <property name="runner" ref="runner"></property>
            <!--注入connectionUtils-->
            <property name="connectionUtils" ref="connectionUtils"></property>
        </bean>
    
        <!--配置QueryRunner-->
        <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
            <!--&lt;!&ndash;注入数据源&ndash;&gt;-->        <!--连接不从连接池中获取-->
            <!--<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/eesy"></property>
            <property name="user" value="root"></property>
            <property name="password" value="809080"></property>
        </bean>
    
        <!--配置Connection工具类 ConnectionUtils-->
        <bean id="connectionUtils" class="com.liuzeyu.utils.ConnectionUtils">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!--配置事务-->
        <bean id="txManger" class="com.liuzeyu.utils.TransactionManger">
            <!--注入connectionUtils-->
            <property name="connectionUtils" ref="connectionUtils"></property>
        </bean>
    </beans>
    
    1. 修改service实现类
    
        public void transfer(String sourceName, String targetName, Float money) {
    
            try {
                //1.开启事务
                txManger.beginTransaction();
                //2.执行操作
                //2.1根据sourceName获取转出账户
                Account sourceAccount = accountDao.findAccountByName(sourceName);
                //2.2根据targetName获取转入账户
                Account targetAccount = accountDao.findAccountByName(targetName);
                //2.3转出账户数据更新(减钱)
                Float smoney = sourceAccount.getMoney();
                smoney -=money;
                sourceAccount.setMoney(smoney);
                accountDao.updateAccount(sourceAccount);
                //2.4转入账户数据更新(加钱)
                Float tmoney = targetAccount.getMoney();
                tmoney +=money;
                targetAccount.setMoney(tmoney);
                int i = 1/0;
                accountDao.updateAccount(targetAccount);
                //3.提交事务
                txManger.commitTransaction();
            }catch (Exception e){
                //4.回滚操作
    
                txManger.rollbackTransaction();
                e.printStackTrace();
            }finally {
                //5.释放资源
                txManger.release();
            }
        }
    
    1. 测试:可以解决上述问题!

2. 分析上述案例中存在的问题

问题:方法与方法之间的依赖,如事务控制器与service实现类之间产生了依赖关系
修改事务控制器方法名

service实现类不能使用

为什么会有这一问题的出现呢,原因很简单就是因为service实现类中使用了事务管理器这一工具类,如何做到不使用事务管理器又可以做到事务的控制呢?

3. 动态代理的回顾

设计模式-代理模式之动态代理

4. 解决案例中的问题

思路分析:
由IAccountService的代理对象来实现类中的方法,并且代理对象可以实现了事务的控制。

  1. 创建没有事务控制的service实现类

    /**
     * 账户的业务层实现类
     */
    public class AccountServiceImpl_OLD implements IAccountService {
    
        private IAccountDao accountDao;
    
        public void setAccountDao(IAccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        private TransactionManger txManger;
    
        public void setTxManger(TransactionManger txManger) {
            this.txManger = txManger;
        }
    
        public List<Account> findAllAccount() {
            return accountDao.findAllAccount();
        }
    
    
        public Account findAccountById(Integer accountId) {
            return accountDao.findAccountById(accountId);
        }
    
    
        public void saveAccount(Account account) {
            accountDao.saveAccount(account);
        }
    
    
        public void updateAccount(Account account) {
    
            accountDao.updateAccount(account);
        }
    
    
        public void deleteAccount(Integer acccountId) {
    
            accountDao.deleteAccount(acccountId);
        }
    
        public void transfer(String sourceName, String targetName, Float money) {
    
            //2.1根据sourceName获取转出账户
            Account sourceAccount = accountDao.findAccountByName(sourceName);
            //2.2根据targetName获取转入账户
            Account targetAccount = accountDao.findAccountByName(targetName);
            //2.3转出账户数据更新(减钱)
            Float smoney = sourceAccount.getMoney();
            smoney -= money;
            sourceAccount.setMoney(smoney);
            accountDao.updateAccount(sourceAccount);
            //2.4转入账户数据更新(加钱)
            Float tmoney = targetAccount.getMoney();
            tmoney += money;
            targetAccount.setMoney(tmoney);
            //int i = 1 / 0;
            accountDao.updateAccount(targetAccount);
        }
    }
    
    
  2. 创建代理类,实现的功能有事务的控制,service层代码的实现

    /**
     * Created by liuzeyu on 2020/4/23.
     * 创建代理service工厂
     *      增强方法:添加事务管理
     */
    public class BeanFactory {
        private  IAccountService accountService;
        private TransactionManger txManger;
    
        public final void setTxManger(TransactionManger txManger) {
            this.txManger = txManger;
        }
        public  void setAccountService(IAccountService accountService) {
            this.accountService = accountService;
        }
    
        /**
         * 获取service的代理对象
         * @return
         */
        public IAccountService getAccountService() {
            IAccountService iac = (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                    accountService.getClass().getInterfaces(),
                    new InvocationHandler() {
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            Object rstValue = null;
                            try {
                                //1.开启事务
                                txManger.beginTransaction();
                                //2.执行操作
                                rstValue = method.invoke(accountService, args);
                                //3.提交事务
                                txManger.commitTransaction();
                                //4.返回结果
                                return rstValue;
                            } catch (Exception e) {
                                //5.回滚操作
                                txManger.rollbackTransaction();
                                throw new RuntimeException(e);
                            } finally {
                                //6.释放资源
                                txManger.release();
                            }
                        }
                    });
            return iac;
        }
    
    }
    
    
  3. 修改bean.xml文件,使用的是没有事务控制器的service实现类

    <?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.liuzeyu.service.impl.AccountServiceImpl">-->
            <!--&lt;!&ndash; 注入dao &ndash;&gt;-->
            <!--<property name="accountDao" ref="accountDao"></property>-->
            <!--&lt;!&ndash;注入事务管理器&ndash;&gt;-->
            <!--<property name="txManger" ref="txManger"></property>-->
        <!--</bean>-->
    
        <!-- 配置代理Service -->
        <bean id="accountSerivce_OLD" class="com.liuzeyu.service.impl.AccountServiceImpl_OLD">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
        <bean id="beanFactory" class="com.liuzeyu.factory.BeanFactory">
            <property name="accountService" ref="accountSerivce_OLD"></property>
            <property name="txManger" ref="txManger"></property>
        </bean>
        <bean id="proxy_accpuntService" factory-bean="beanFactory" factory-method="getAccountService">
        </bean>
    
        <!--配置Dao对象-->
        <bean id="accountDao" class="com.liuzeyu.dao.impl.AccountDaoImpl">
            <!-- 注入QueryRunner -->
            <property name="runner" ref="runner"></property>
            <!--注入connectionUtils-->
            <property name="connectionUtils" ref="connectionUtils"></property>
        </bean>
    
        <!--配置QueryRunner-->
        <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
            <!--&lt;!&ndash;注入数据源&ndash;&gt;-->        <!--连接不从连接池中获取-->
            <!--<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/eesy"></property>
            <property name="user" value="root"></property>
            <property name="password" value="809080"></property>
        </bean>
    
        <!--配置Connection工具类 ConnectionUtils-->
        <bean id="connectionUtils" class="com.liuzeyu.utils.ConnectionUtils">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!--配置事务-->
        <bean id="txManger" class="com.liuzeyu.utils.TransactionManger">
            <!--注入connectionUtils-->
            <property name="connectionUtils" ref="connectionUtils"></property>
        </bean>
    </beans>
    

    重点难点在这些配置文件的修改

  4. 为测试类添加新注入的service

    小结:
    最后:经测试可以实现相同的效果,但是解决了我们刚刚存在的问题吗?
    肯定是解决了,因为如果我们在实现类中多处用到了事务控制,那么修改事务控制器的方法被修改了,实现类就要修改多次。
    但是如果使用了动态代理,那么只需要在invoke函数中修改一次即可,提高可维护性。因为业务层的每次函数执行都会经过invoke函数,所以会经常在里面编写一下可重用性的代码,提高开发效率。

5. AOP的概念

  1. 什么是AOP?
    AOP全称Aspect Oriented Programming,即面向切面编程。通过预编译和运行器动态代理机制实现程序功能的统一维护技术。AOP是OOP的延续,是软件开发的重点和热点,也是spring框架的一个重要的内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高了程序的可重用行和开发效率。
  2. AOP的作用及优势
    作用
    在程序期间,不修改源码,对已有方法进行增强
    优势:
    1. 减少重复代码
    2. 提高开发效率
    3. 提高可维护性
  3. AOP的实现方式
    <mark>动态代理技术</mark>

6. Spring中的AOP【掌握】

1. AOP相关术语(基于项目day03_spring_account)
  1. Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。例如项目中IAccountService接口中所有的方法。

  2. Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。例如如果在项目IAccountService接口中添加方法test,然后再再将其排除再增强方法之外,就是不增加事务的增强处理,此时的test方法就不是切入点(如下图),但它还是连接点。其余的IAccountService接口方法都是切入点。

  3. Advice(通知/增强):
    所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
    通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
    例如案例中的事务管理器就是一个通知,它起到的作用有提供了公共代码的部分

  4. Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方
    法或 Field。

  5. Target(目标对象):代理的目标对象,就是被代理对象。案例中的accountService

  6. weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。简单的说加入事务控制的过程叫做织入。如案例中事务管理的加入过程。

  7. Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。其实就是代理对象,如案例中的,Proxy.newProxyInstance的返回值。

  8. Aspect(切面):是切入点和通知(引介)的结合。

2. 学习spring中AOP要明确的事
  1. 开发阶段(我们做的)
    1. 编写核心业务代码(开发主线):大部分由程序员来做,要求熟悉业务需求。
    2. 把公共代码抽取出来,制作成通知,是开发阶段最后才做,AOP编程人员来做。
    3. 在配置文件中,声明切入点和通知点之间的关系,即切面,AOP编程人员来做。
  2. 运行阶段(spring框架做的)
    spring框架监控切入点方法的执行,一旦监控到切入点方法被执行了,使用代理机制,动态创建目标对象的代理对象。根据通知类别,在代理对象的相应位置,将通知对象的功能织入,完成完整的代理逻辑运行。
3. 关于代理的选择

在spring中,框架会根据目标类是否实现了接口来决定采用哪一种的动态代理方式。

4. 基于xml的AOP配置
  1. 编写service层代码
/**
 * Created by liuzeyu on 2020/4/24.
 * 模拟业务层接口
 */
public interface IAccountService {

    //模拟保存方法
    public void save();

    //模拟更新账户
    public void updateAccount(int i);

    //模拟删除账户
    public int deleteAccount();
}
package com.liuzeyu.service.impl;

import com.liuzeyu.service.IAccountService;

/**
 * Created by liuzeyu on 2020/4/24.
 */
public class IAccountServiceImpl implements IAccountService {
    public void save() {
        System.out.println("执行了保存...");
    }

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

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

  1. 编写utils代码
/**
 * Created by liuzeyu on 2020/4/24.
 * 用于记录日志的工具类
 */
public class Logger {
    /**
     * 用于打印日志:
     * 计划让其在切入点方法执行之前(切入点方法就是业务层方法)
     */
    public void printLog(){
        System.out.println("Logger类的printLog()开始打印日志....");
    }
}
  1. 准备bean.xml
    1. 把service和通知Bean都交给spring容器管理
    2. 使用aop:config标签表示AOP的开始配置
    3. 使用aop:aspect表示开始配置切面
      属性:
      id:给切面提供一个唯一标识。
      ref:是指定通知类的bean的id
    4. aop:aspect标签内部使用对应标签来配置通知的类型
      我们示例让printLog在切入点方法执行之前被执行,所以是前置通知
      aop:before:表示配置前置通知
      method属性:用于执行Logger类中哪个方法是前置通知
      pointcut属性:用于指定切入点表达式,该表达式的含义是对业务层中哪些方法的增强
      切入点表达式的写法:
<?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">

    <!--将service和logegr交给spring的IOC容器管理-->
    <bean id="accountService" class="com.liuzeyu.service.impl.IAccountServiceImpl"></bean>
    <bean id="logegr" class="com.liuzeyu.utlis.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logegr">
            <aop:before method="printLog" pointcut="execution(public void
            com.liuzeyu.service.impl.IAccountServiceImpl.save())"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

其中切入点表达式的写法

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

            <!--去除访问修饰符-->
            <!--<aop:before method="printLog" pointcut="execution( void-->
            <!--com.liuzeyu.service.impl.IAccountServiceImpl.save())"></aop:before>-->

            <!--可以将返回值改为任意-->
            <!--<aop:before method="printLog" pointcut="execution( *-->
            <!--com.liuzeyu.service.impl.IAccountServiceImpl.save())"></aop:before>-->

            <!--包名可以写* 表示任意-->
            <!--<aop:before method="printLog" pointcut="execution( *-->
            <!--*.*.*.*.*.save())"></aop:before>-->
            <!--包名可以写* 表示任意(多层包改进)-->
            <!--<aop:before method="printLog" pointcut="execution( *-->
            <!--*..*.save())"></aop:before>-->
            <!--全通配写法-->
            <!--<aop:before method="printLog" pointcut="execution(* *..*.*(..))"></aop:before>-->

            <!--方法参数(..)表示可以0或多个任意类型的参数-->
            <!--<aop:before method="printLog" pointcut="execution( *-->
            <!--*..*.*(..))"></aop:before>-->
            <!--方法参数(int)表示只可以是int类型的参数-->
            <!--<aop:before method="printLog" pointcut="execution( *-->
            <!--*..*.*(int))"></aop:before>-->
            <!--方法参数(引用类型)引用类型写包名+类型的全称-->
            <aop:before method="printLog" pointcut="execution( *
            *..*.*(java.lang.String))"></aop:before>
        </aop:aspect>
    </aop:config>
  1. 测试方法
package com.liuzeyu;

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

/**
 * Created by liuzeyu on 2020/4/24.
 */
public class AopTest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService accountService =(IAccountService)ac.getBean("accountService");
        accountService.save();
    }
}

5. 四种常用的通知类型
  1. 在上述的案例中,为通知类添加通知方法
/**
 * Created by liuzeyu on 2020/4/24.
 * 用于记录日志的工具类
 */
public class Logger {
    /**
     * 用于打印日志:
     * 计划让其在切入点方法执行之前(切入点方法就是业务层方法)
     */
    public void beforePrintLog(){
        System.out.println("前置通知...Logger类的beforePrintLog开始打印日志....");
    }

    /**
     * 用于打印日志:
     * 计划让其在切入点方法执行后(切入点方法就是业务层方法)
     */
    public void afterReturingPrintLog(){
        System.out.println("后置通知...Logger类的AfterReturingPrintLog()开始打印日志....");
    }

    /**
     * 用于打印日志:
     * 计划让其在切入点方法执行出现异常(切入点方法就是业务层方法)
     */
    public void afterThrowingPrintLog(){
        System.out.println("异常通知...Logger类的afterThrowingPrintLog()开始打印日志....");
    }

    /**
     * 用于打印日志:
     * 计划让其在切入点方法最终之前(切入点方法就是业务层方法)
     */
    public void afterPrintLog(){
        System.out.println("最终通知...Logger类的AfterPrintLog()开始打印日志....");
    }
}

  1. 配置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">
    <!--将service和logegr交给spring的IOC容器管理-->
    <bean id="accountService" class="com.liuzeyu.service.impl.IAccountServiceImpl"></bean>
    <bean id="logegr" class="com.liuzeyu.utlis.Logger"></bean>
    <!--配置AOP-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logegr">
            <!--配置前置通知类型,并且建立通知方法和切入点相关联-->
            <aop:before method="beforePrintLog"
                        pointcut="execution(* *..*.*(..))"
            ></aop:before>
            <!--配置后通知类型,并且建立通知方法和切入点相关联-->
            <aop:after-returning method="afterReturingPrintLog"
                        pointcut="execution(* *..*.*(..))"
            ></aop:after-returning>
            <!--配置异常通知类型,并且建立通知方法和切入点相关联-->
            <aop:after-throwing method="afterThrowingPrintLog"
                        pointcut="execution(* *..*.*(..))"
            ></aop:after-throwing>
            <!--配置最终通知类型,并且建立通知方法和切入点相关联-->
            <aop:after method="afterPrintLog"
                        pointcut="execution(* *..*.*(..))"
            ></aop:after>
        </aop:aspect>
    </aop:config>
</beans>
  1. 测试函数
public class AopTest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService accountService =(IAccountService)ac.getBean("accountService");
        accountService.save();
    }
}
  1. 执行结果

    为save方法制造异常

    测试结果

    可见后置通知和异常通知只能其中的一个被执行
    发现问题:bean.xml 切入点表达式每处都在写,而且写的一样,出现冗余,可以对其改造

    做到一处写好,处处引用。但是写在标签<aop:aspect 内部只能这个切面引用,如果想让其它切面引用。也可以写在外部,当必须写在<aop:aspect上面,如:
6.环绕通知
  1. 配置bean.xml
 <!--配置环绕通知-->
 <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
  1. 添加通知方法
    /**
     * 用于打印日志:
     * 环绕通知
     */
   public void aroundPrintLog(){
           System.out.println("环绕通知...Logger类的aroundPrintLog开始打印日志....");

   }
  1. 测试方法
/**
 * Created by liuzeyu on 2020/4/24.
 */
public class AopTest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService accountService =(IAccountService)ac.getBean("accountService");
        accountService.save();
    }
}

问题:当我们配置完环绕通知后,环绕通知的方法被执行了,但是切入点的方法却没有被执行。
分析:
发现环绕通知中缺少明确的切入点方法调用。
所以可以在环绕通知中添加切入点方法的执行,如下
解决:spring框架中为我们提供了一个接口ProceedingJoinPoint。该接口有一个proceed()方法,调用此方法就是明确调用切入点方法。
该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供接口的实现类供我们使用。

上述的异常必须由Throwable来抓,否则抓不住的。
重新测试结果:

7. 基于注解的AOP配置
  1. 将bean.xml的IOC配置拿掉,使用注解配置

    替换:
<!--扫描带有注解的类加入IOC容器-->
    <context:component-scan base-package="com.liuzeyu"></context:component-scan>



2. 将bean.xml的AOP配置拿掉,使用注解配置

替换为

   <!--配置AOP开启注解-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
/**
 * Created by liuzeyu on 2020/4/24.
 * 用于记录日志的工具类
 */
@Component("logegr")
@Aspect
public class Logger {
    @Pointcut("execution(* com.liuzeyu.service.impl.*.*(..))")
    private void pt1(){}

    /**
     * 用于打印日志:
     * 计划让其在切入点方法执行之前(切入点方法就是业务层方法)
     */
    //@Before("pt1()")
    public void beforePrintLog(){
        System.out.println("前置通知...Logger类的beforePrintLog开始打印日志....");
    }

    /**
     * 用于打印日志:
     * 计划让其在切入点方法执行后(切入点方法就是业务层方法)
     */
    @AfterReturning("pt1()")
    public void afterReturingPrintLog(){
        System.out.println("后置通知...Logger类的AfterReturingPrintLog()开始打印日志....");
    }

    /**
     * 用于打印日志:
     * 计划让其在切入点方法执行出现异常(切入点方法就是业务层方法)
     */
    @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 rstValue = null;
        try{
            Object[] args = pjp.getArgs();
            System.out.println("前置通知...Logger类的aroundPrintLog开始打印日志....");
            rstValue = pjp.proceed(args);
            System.out.println("后置通知...Logger类的aroundPrintLog开始打印日志....");
            return rstValue;
        }catch (Throwable t){
            System.out.println("异常通知...Logger类的aroundPrintLog开始打印日志....");
            throw  new RuntimeException(t);
        }finally {
            System.out.println("最终通知...Logger类的aroundPrintLog开始打印日志....");
        }
    }
}

测试前置,后置,最终,异常通知的时候发现并没有按照实际顺序执行:

但是测试环绕通知时,有按照实际顺序执行,所以当出现有严格的执行顺序时,我们一般选择环绕通知

最后如若需要纯注解配置,只需要添加配置类:

/**
 * Created by liuzeyu on 2020/4/25.
 */
@Configuration
@ComponentScan(basePackages = "com.liuzeyu")
@EnableAspectJAutoProxy
public class SpringConfig {
}

并且在创建容器时执行配置类的字节码文件

public class AopTest {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
        IAccountService accountService =(IAccountService)ac.getBean("accountService");
        accountService.save();
    }
}