20201125黄春跃

20201125黄春跃

知识点

什么是 AOP

AOP:全称是 Aspect Oriented Programming 即:面向切面编程。

1606304126935

简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改
源码的基础上,对我们的已有方法进行增强

AOP 的作用及优势

作用:
在程序运行期间,不修改源码对已有方法进行增强。
优势:
减少重复代码
提高开发效率
维护方便

AOP 的实现方式

使用动态代理技术

AOP的具体应用

案例中问题

这是我们之前中做的增删改查例子。下面是客户的业务层实现类。我们能看出什么问题吗?

public class AccountServiceImpl implements IAccountService {
  private IAccountDao accountDao;
  //省略了部分代码
  @Override
  public void saveAccount(Account account) {
    accountDao.save(account);
 }
  @Override
  public void updateAccount(Account account) {
    accountDao.update(account);
      }
  @Override
  public void deleteAccount(Integer accountId) {
    accountDao.delete(accountId);
 }
  @Override
  public Account findAccountById(Integer accountId) {
    return accountDao.findById(accountId);
 }
  @Override
  public List<Account> findAllAccount() {
    return accountDao.findAll();
 }
}

问题就是:
事务被自动控制了。换言之,我们使用了 connection 对象的 setAutoCommit(true)
此方式控制事务,如果我们每次都执行一条 sql 语句,没有问题,但是如果业务方法一次要执行多条
sql语句,这
种方式就无法实现功能了。
请看下面的示例:
我们在业务层中多加入一个方法。

public void transfer(Integer sourceId, Integer targetId, Float money) {
    //根据名称查询两个账户信息
    Account source = accountDao.findById(sourceId);
    Account target = accountDao.findById(targetId);
    //转出账户减钱,转入账户加钱
    source.setMoney(source.getMoney()-money);
    target.setMoney(target.getMoney()+money);
    //更新两个账户
    accountDao.update(source);
    int i=1/0; //模拟转账异常
    accountDao.update(target);
 }

当我们执行时,由于执行有异常,转账失败。但是因为我们是每次执行持久层方法都是独立事务,导致
无法实现事务控制(不符合事务的一致性)

问题的解决

解决办法:让业务层来控制事务的提交和回滚。
改造后的业务层实现类:
注:此处没有使用 spring 的 IoC.

package com.itlaobing.service.impl;
import com.itlaobing.config.JdbcUtils;
import com.itlaobing.dao.IAccountDao;
import com.itlaobing.dao.impl.AccountDaoImpl;
import com.itlaobing.model.Account;
import com.itlaobing.service.IAccountService;
import org.springframework.stereotype.Component;
import java.sql.SQLException;
import java.util.List;
/**
* @Classname AccountServiceImpl
* @Description TODO()
* @Date 2019/12/13 20:37
* @Author by Administrator
* @Version v1.0
*/
@Component("accountService")
public class AccountServiceImpl implements IAccountService {
//  @Resource
  private IAccountDao accountDao = new AccountDaoImpl();
  @Override
  public void saveAccount(Account account) {
    try {
      JdbcUtils.beginTransaction();
      accountDao.save(account);
      JdbcUtils.commitTransaction();
   } catch (SQLException e) {
      e.printStackTrace();
      try {
        JdbcUtils.rollbackTransaction();
     } catch (SQLException ex) {
        ex.printStackTrace();
     }
   }
 }
  @Override
  public void updateAccount(Account account) {
    try {
      JdbcUtils.beginTransaction();
      accountDao.update(account);
      JdbcUtils.commitTransaction();
   } catch (SQLException e) {
      e.printStackTrace();
      try {
        JdbcUtils.rollbackTransaction();
     } catch (SQLException ex) {
        ex.printStackTrace();
     }
   }
 }
  @Override
  public void deleteAccount(Integer accountId) {
    try {
      JdbcUtils.beginTransaction();
      accountDao.delete(accountId);
      JdbcUtils.commitTransaction();
   } catch (SQLException e) {
      e.printStackTrace();
      try {        JdbcUtils.rollbackTransaction();
     } catch (SQLException ex) {
        ex.printStackTrace();
     }
   }
 }
  @Override
  public Account findAccountById(Integer accountId) {
    return accountDao.findById(accountId);
 }
  @Override
  public List<Account> findAllAccount() {
    return accountDao.findAll();
 }
  @Override
  public void transfer(Integer sourceId, Integer targetId, Float money)
{
    try {
      JdbcUtils.beginTransaction();
      //根据名称查询两个账户信息
      Account source = accountDao.findById(sourceId);
      Account target = accountDao.findById(targetId);
      //转出账户减钱,转入账户加钱
      source.setMoney(source.getMoney()-money);
      target.setMoney(target.getMoney()+money);
      //更新两个账户
      accountDao.update(source);
      int i=1/0; //模拟转账异常
      accountDao.update(target);
      JdbcUtils.commitTransaction();
   } catch (Exception e) {
      e.printStackTrace();
      try {
        JdbcUtils.rollbackTransaction();
     } catch (SQLException ex) {
        ex.printStackTrace();
     }
   }
 }
}

JdbcUtils

package com.itlaobing.util;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/*** @Classname JdbcUtils
* @Description TODO()
* @Date 2019/12/15 20:02
* @Author by Administrator
* @Version v1.0
*/
public class JdbcUtils {
  //饿汉模式
  private static final ComboPooledDataSource ds = new
ComboPooledDataSource();
  static{
    Properties prop = new Properties();
    InputStream in =
JdbcUtils.class.getResourceAsStream("db.properties");
    try {
      prop.load(in);
      ds.setDriverClass(prop.getProperty("jdbc.driver"));
      ds.setJdbcUrl(prop.getProperty("jdbc.url"));
      ds.setUser(prop.getProperty("jdbc.username"));
      ds.setPassword(prop.getProperty("jdbc.password"));
   } catch (IOException e) {
      e.printStackTrace();
   } catch (PropertyVetoException e) {
      e.printStackTrace();
   }
 }
  /**
  * 创建一个数据源,并存入 spring 容器中
  * @return
  */
  public static DataSource getDataSource() {
    return ds;
 }
  /**
  * 它为null表示没有事务
  * 它不为null表示有事务
  * 当开启事务时,需要给它赋值
  * 当结束事务时,需要给它赋值为null
  * 并且在开启事务时,让dao的多个方法共享这个Connection
  */
  private static ThreadLocal<Connection> tl = new
ThreadLocal<Connection>();
  /**
  * dao使用本方法来获取连接
  */
  public static Connection getConnection() throws SQLException {
    /*
    * 如果有事务,返回当前事务的con
    * 如果没有事务,通过连接池返回新的con
    */Connection con = tl.get();//获取当前线程的事务连接
    if (con != null) {
      //有事务
      return con;
   }
    return ds.getConnection();
 }
  /**
  * 开启事务
  */
  public static void beginTransaction() throws SQLException {
    Connection con = tl.get();//获取当前线程的事务连接
    if (con != null){
      throw new SQLException("已经开启了事务,不能重复开启!");
   }
    con = ds.getConnection();//给con赋值,表示开启了事务
    con.setAutoCommit(false);//设置为手动提交
    tl.set(con);//把当前事务连接放到tl中
 }
  /**
  * 提交事务
  */
  public static void commitTransaction() throws SQLException {
    Connection con = tl.get();//获取当前线程的事务连接
    if (con == null) {
      throw new SQLException("没有事务不能提交!");
   }
    con.commit();//提交事务
    con.close();//关闭连接
    con = null;//表示事务结束!
    tl.remove();
 }
  /**
  * 回滚事务
  */
  public static void rollbackTransaction() throws SQLException {
    Connection con = tl.get();//获取当前线程的事务连接
    if (con == null) {
      throw new SQLException("没有事务不能回滚!");
   }
    con.rollback();
    con.close();
    con = null;
    tl.remove();
 }
  /**
  * 释放Connection
  */
  public static void releaseConnection() throws SQLException {
    System.out.println("释放Connection");
 }
}

动态代理常用的有两种方式

基于接口的动态代理
提供者:JDK 官方的 Proxy 类。
要求:被代理类最少实现一个接口。
基于子类的动态代理
提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。
要求:被代理类不能用 final 修饰的类(最终类)。

使用 JDK 官方的 Proxy 类创建代理对象

此处我们使用的是一个演员的例子:
在很久以前,演员和剧组都是直接见面联系的。没有中间人环节。
而随着时间的推移,产生了一个新兴职业:经纪人(中间人),这个时候剧组再想找演员就需要通过经
纪人来找了。下面我们就用代码演示出来

package com.itlaobing;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Actor implements  IActor{
    @Override
    public void basicAct(double money) {
        System.out.println("拿到钱,开始基本表演" + money);
    }

    @Override
    public void dangerAct(double money) {
        System.out.println("拿到钱,开始危险表演" + money);
    }

    public static void main(String[] args) {
        //需要找一个演员,没有经纪人的模式:
        final Actor actor = new Actor(); //直接创建
        IActor proxyActor = (IActor)Proxy.newProxyInstance(actor.getClass().getClassLoader(), actor.getClass().getInterfaces(), new InvocationHandler() {
            @Override

            /*
             *  method 当前执行的方法对象 public abstract void com.itlaobing.IActor.basicAct/dangerAct(double)
             *  args 执行方法所需的参数 money
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //拿到方法名
                String name = method.getName();
                Double money = (Double) args[0];
                Object rtValue = null;
                if("basicAct".equalsIgnoreCase(name)){
                    if(money > 2000){
                        rtValue = method.invoke(actor , money/2);
                    }
                }
                if("dangerAct".equalsIgnoreCase(name)){
                    if(money > 5000){
                        rtValue = method.invoke(actor , money/2);
                    }
                }
                return null;
            }
        });
        proxyActor.basicAct(8000);
        proxyActor.dangerAct(50000);

    }
}


package com.itlaobing;

public interface IActor {
    //基本演出
    public void basicAct(double money);
    //危险演出
    public void dangerAct(double money);
}

使用 CGLib 的 Enhancer 类创建代理对象

导入CGLib依赖

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>2.1_3</version>
</dependency>
package com.itlaobing;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class Actor2 {
    public void basicAct(double money) {
        System.out.println("拿到钱,开始基本表演,挣了" + money + "元");
    }

    public void dangerAct(double money) {
        System.out.println("拿到钱,开始危险表演,挣了" + money + "元");
    }

    public static void main(String[] args) {
        /**
         * 基于子类的动态代理
         * 要求:被代理对象不能是最终类
         * 用到的类:Enhancer
         * 用到的方法:reate(Class, Callback)
         * 方法的参数:
         * Class:被代理对象的字节码
         * Callback:如何代理
         * @param args
         */
        Actor2 actor2 = new Actor2();
        Actor2 proxyActor = (Actor2) Enhancer.create(actor2.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                /**
           * 执行被代理对象的任何方法,都会经过该方法。
           * 在此方法内部就可以对被代理对象的任何方法进行增强。
           *
           * 参数:前三个和基于接口的动态代理是一样的。
           * MethodProxy:当前执行方法的代理对象。
           * 返回值:
           * 当前执行方法的返回值
           */
                String name = method.getName();
                Double money = (Double) args[0];
                Object rtValue = null;
                if("basicAct".equalsIgnoreCase(name)){
                    if(money > 2000){
                        rtValue = method.invoke(actor2, money/2);
                    }
                }
                if("dangerAct".equalsIgnoreCase(name)){
                    if(money > 5000){
                        rtValue = method.invoke(actor2, money/2);
                    }
                }
                return rtValue;
            }
        });
        proxyActor.basicAct(10000);
        proxyActor.dangerAct(100000);
    }
}
class ActorChild extends Actor2 {
  @Override
  public void basicAct(double money) {
    super.basicAct(money / 2);
 }
  @Override
  public void dangerAct(double money) {
    super.dangerAct(money / 2);
 }
}

Spring 中的 AOP

AOP 相关术语

Aspect(切面):
是切入点和通知(引介)的结合。
Joinpoint(连接点):
所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连
接点
Advice(通知/增强):
在特定的连接点处采取的操作就是通知。

通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Pointcut(切入点):
匹配连接点的谓词。建议与切入点表达式关联,并在与该切入点匹配的任何连接点处运行(例如,执行
具有特定名称的方法)。切入点表达式匹配的连接点的概念是AOP的核心,并且Spring默认使用
AspectJ切入点表达语言。
Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法
或 Field。
Target(目标对象):
代理的目标对象。一个或多个方面通知的对象。由于Spring AOP是使用运行时代理实现的,因此该对象
将始终是代理对象。
Proxy(代理):
一个类被 AOP 织入增强后,就产生一个结果代理类。由AOP框架创建的一个对象,用于实现方法执行
等。在Spring框架中,AOP代理将是JDK动态代理或CGLIB代理。
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。
spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。

学习 spring 中的 AOP 要明确的事

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

关于代理的选择

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

基于 XML 的 AOP 配置

按照pdf一步一步的来

总结

晚自习后面把Spring从头开始看了看。

标签

评论


© 2021 成都云创动力科技有限公司 蜀ICP备20006351号-1