• AOP面向切面编程笔记


    1.AOP概念:Aspect Oriented Programming 面向切面编程

    2.作用:本质上来说是一种简化代码的方式
    继承机制
    封装方法
    动态代理
    ……


    3.情景举例
    ①数学计算器接口[MathCalculator]
    int add(int i,int j);
    int sub(int i,int j);
    int mul(int i, int j);
    int div(int i,int j);
    ②提供简单实现[EasyImpl]
    ③在简单实现的基础上让每一个计算方法都能够打印日志[LoginImpl]

    ④缺陷
    [1]手动添加日志繁琐,重复
    [2]统一修改不便
    [3]对目标方法本来要实现的核心功能有干扰,使程序代码很臃肿,不易于开发维护

    ⑤使用动态代理实现
    [1]创建一个类,让这个类能够提供一个目标对象的代理对象
    [2]在代理对象中打印日志



    4.AOP术语![参见图例和doc文档]
    AOP概述
    ●AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,
    是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。
    ●参见图例和doc文档解释AOP的各个术语!
    ●Spring的AOP既可以使用xml配置的方式实现,也可以使用注解的方式来实现!

    5.在Spring中使用AOP实现日志功能
    ①Spring中可以使用注解或XML文件配置的方式实现AOP。
    ②导入jar包
    com.springsource.net.sf.cglib -2.2.0.jar
    com.springsource.org.aopalliance-1.0.0 .jar
    com.springsource.org.aspectj.weaver-1.6.8 .RELEASE.jar
    commons-logging-1.1.3. jar
    spring-aop-4.0.0.RELEASE.jar
    spring-aspects-4.0.0.RELEASE.jar
    spring-beans-4.0.0.RELEASE.jar
    spring-context-4.0.0.RELEASE.jar
    spring-core-4.0.0.RELEASE.jar
    spring-expression-4.0.0.RELEASE. jar

    ③开启基于注解的AOP功能
    < aop:aspectj-autoproxy />
    ④声明一个切面类,并把这个切面类加入到IOC容器中
    @Aspect//表示这是一个切面类
    @Component//加入IOC容器
    public class LogAspect {}

    ⑤在切面类中声明通知方法
    [1]前置通知:@Before
    [2]返回通知:@AfterReturning
    [3]异常通知:@AfterThrowing
    [4]后置通知:@After
    [5]环绕通知:@Around :环绕通知是前面四个通知的集合体!

    @Aspect//表示这是一个切面类
    @Component//将本类对象加入到IOC容器中!
    public class LogAspect {
    @Before(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
    public void showBeginLog(){
    System.out.println("AOP日志开始");
    }
    @After(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
    public void showReturnLog(){
    System.out.println("AOP方法返回");
    }
    @AfterThrowing(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
    public void showExceptionLog(){
    System.out.println("AOP方法异常");
    }
    @AfterReturning(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
    public void showAfterLog(){
    System.out.println("AOP方法结束");
    }
    }

    ⑥被代理的对象也需要加入IOC容器
    @Component//加入IOC容器
    public class MathCalculatorImpl {

    public int add(int i,int j){
    int result = i+j;
    return result;
    }
    public int sub(int i,int j){
    int result = i-j;
    return result;
    }
    public int multi(int i,int j){
    int result = i*j;
    return result;
    }
    public int divide(int i,int j){
    int result = i/j;
    return result;
    }
    }

    6.切入点表达式:
    1.上述案例通过junit测试,会发现,我们调用目标类的四个方法只有add方法被加入了4个通知,如果想所有的方法都加上这些通知,可以
    在切入点表达式处,将execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int)) 换成:
    execution(public int com.neuedu.aop.target.MathCalculatorImpl.*(int, int))这样只要是有两个参数,且
    参数类型为int的方法在执行的时候都会执行其相应的通知方法!

    2.①切入点表达式的语法格式[参见第5章AOP细节]
    execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))

    参见第5章AOP细节:演示验证
    1.任意参数,任意类型
    2.任意返回值
    3.用@PointCut注解统一声明,然后在其它通知中引用该统一声明即可!
    需要注意的是:权限是不支持写通配符的,当然你可以写一个*表示所有权限所有返回值!


    最详细的切入点表达式:
    execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))
    最模糊的切入点表达式:
    execution (* *.*(..))

    7.统一声明切入点表达式
    @Pointcut(value= "execution(public int com.atguigu.aop.target.EazyImpl.add(int,int))")
    public void myPointCut(){}


    8.通知方法的细节
    ①在通知中获取目标方法的方法名和参数列表
    [1]在通知方法中声明一个JoinPoint类型的形参
    [2]调用JoinPoint对象的getSignature()方法获取目标方法的签名
    [3]调用JoinPoint对象的getArgs()方法获取目标方法的实际参数列表

    ②在返回通知中获取方法的返回值
    [1]在@AfterReturning注解中添加returning属性
    @AfterReturning (value="myPointCut()", returning= "result")
    [2]在返回通知的通知方法中声明一个形参,形参名和returning属性的值一致
    showReturnLog(JoinPoint joinPoint, Object result)
    ③在异常通知中获取异常对象
    [1]在@ AfterThrowing注解中添加throwing属性
    @AfterThrowing (value="myPointCut()",throwing= "throwable" )
    [2]在异常通知的通知方法中声明一个形参,形参名和throwing属性值一致
    showExceptinLog(JoinPoint joinPoint, Throwable throwable)


    9.根据接口类型获取target对象时,实际上真正放在IOC容器中的对象是代理对象,而并不是目标对象本身!


    10.环绕通知:@Around
    1.环绕通知需要在方法的参数中指定JoinPoint的子接口类型ProceedingJoinPoint为参数
    @Around(value="pointCut()")
    public void around(ProceedingJoinPoint joinPoint){
    }
    2.环绕通知会将其他4个通知能干的,自己都给干了!
    注意:@Around修饰的方法一定要将方法的返回值返回!本身相当于代理!

    @Around(value="pointCut()")
    public Object around(ProceedingJoinPoint joinPoint){
    Object[] args = joinPoint.getArgs();
    Signature signature = joinPoint.getSignature();
    String methodName = signature.getName();
    List<Object> list = Arrays.asList(args);
    Object result = null;
    try {
    //目标方法之前要执行的操作
    System.out.println("[环绕日志]"+methodName+"开始了,参数为:"+list);
    //调用目标方法
    result = joinPoint.proceed(args);

    //目标方法正常执行之后的操作
    System.out.println("[环绕日志]"+methodName+"返回了,返回值为:"+result);
    } catch (Throwable e) {
    //目标方法抛出异常信息之后的操作
    System.out.println("[环绕日志]"+methodName+"出异常了,异常对象为:"+e);
    throw new RuntimeException(e.getMessage());
    }finally{
    //方法最终结束时执行的操作!
    System.out.println("[环绕日志]"+methodName+"结束了!");
    }
    return result;
    }

    11.切面的优先级
    对于同一个代理对象,可以同时有多个切面共同对它进行代理。
    可以在切面类上通过@Order (value=50)注解来进行设置,值越小优先级越高!
    @Aspect
    @Component
    @Order(value=40)
    public class TxAspect {
    @Around(value="execution(public * com.neuedu.aop.target.MathCalculatorImpl.*(..))")
    public Object around(ProceedingJoinPoint joinPoint){
    Object[] args = joinPoint.getArgs();
    Signature signature = joinPoint.getSignature();
    String methodName = signature.getName();
    List<Object> list = Arrays.asList(args);
    Object result = null;
    try {
    //目标方法之前要执行的操作
    System.out.println("[事务日志]"+methodName+"开始了,参数为:"+list);
    //调用目标方法
    result = joinPoint.proceed(args);

    //目标方法正常执行之后的操作
    System.out.println("[事务日志]"+methodName+"返回了,返回值为:"+result);
    } catch (Throwable e) {
    //目标方法抛出异常信息之后的操作
    System.out.println("[事务日志]"+methodName+"出异常了,异常对象为:"+e);
    throw new RuntimeException(e.getMessage());
    }finally{
    //方法最终结束时执行的操作!
    System.out.println("[事务日志]"+methodName+"结束了!");
    }
    return result;
    }
    }

    12.注意:上面的AOP都是通过注解实现的,AOP实际上也可以通过xml配置的方式实现!

    <!-- 1.将需要加载到IOC容器中的bean配置好 -->
    <bean id="logAspect" class="com.neuedu.aop.proxy.LogAspect"></bean>
    <bean id="txAspect" class="com.neuedu.aop.target.TxAspect"></bean>
    <bean id="calculator" class="com.neuedu.aop.target.MathCalculatorImpl"></bean>

    <!-- 2.配置AOP,需要导入AOP名称空间 -->
    <aop:config>
    <!-- 声明切入点表达式 -->
    <aop:pointcut expression="execution(* com.neuedu.aop.target.MathCalculatorImpl.*(..))" id="myPointCut"/>
    <!-- 配置日志切面类,引用前面的类 ,通过order属性控制优先级-->
    <aop:aspect ref="logAspect" order="25">
    <!-- 通过method属性指定切面类的切面方法,通过pointcut-ref指定切入点表达式 -->
    <aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
    <aop:after method="showAfterLog" pointcut-ref="myPointCut"/>
    <aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="ex"/>
    <aop:after-returning method="showReturnLog" pointcut-ref="myPointCut" returning="result"/>
    <aop:around method="around" pointcut-ref="myPointCut"/>
    </aop:aspect>

    <!-- 配置事务切面类,引用前面的类 -->
    <aop:aspect ref="txAspect" order="20">
    <aop:around method="around" pointcut-ref="myPointCut"/>
    </aop:aspect>
    </aop:config>

    需要知道的是:事务的管理是和AOP是有很大关系的,即声明式事务的底层是用事务实现的!


    13.批处理(batch)------------>好比快递员【不能一件一件的送快递】
    - 批处理指的是一次操作中执行多条SQL语句
    - 批处理相比于一次一次执行效率会提高很多

    - 批处理主要是分两步:
    1.将要执行的SQL语句保存
    2.执行SQL语句

    - Statement和PreparedStatement都支持批处理操作,这里我们只需要掌握PreparedStatement的批处理方式:
    - 方法:
    void addBatch()
    - 将要执行的SQL先保存起来,先不执行
    - 这个方法在设置完所有的占位符之后调用
    int[] executeBatch()
    - 这个方法用来执行SQL语句,这个方法会将批处理中所有SQL语句执行

    - mysql默认批处理是关闭的,所以我们还需要去打开mysql的批处理:
    rewriteBatchedStatements=true
    我们需要将以上的参数添加到mysql的url地址中

    - 注意:低版本的mysql-jdbc驱动也不支持批处理,一般都是在修改的时候使用批处理,查询的时候不使用!



    案例演示:
    1.创建一张新的数据表
    CREATE TABLE t_emp(
    id INT PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(50)
    )
    2.反复打开数据库客户端,插入语句【相当于每次获取一个connection连接,执行executeUpdate语句】
    INSERT INTO t_emp(NAME) VALUES('张三');
    SELECT * FROM t_emp;

    3.引出批处理--->执行效率高,资源利用率好!

    @Test//测试批处理
    public void testBatch(){
    //向t_emp表中插入10000条数据

    //准备两个变量
    Connection connection = null;
    PreparedStatement ps = null;

    try {
    //获取数据库连接
    connection=JDBCUtil.getConnection();
    //准备SQL模板
    String sql = "INSERT INTO t_emp(NAME) VALUES(?)";
    //获取PrepareStatement
    ps = connection.prepareStatement(sql);
    //创建一个for循环,来设置占位符
    for(int i = 0; i < 10000 ;i++){
    //填充占位符
    ps.setString(1,"emp"+i);
    //添加到批处理方法中,调用无参的,有参的是Statement来调用的!
    ps.addBatch();
    }

    //获取一个时间戳
    long start = System.currentTimeMillis();
    //执行批处理
    ps.executeBatch();
    //获取一个时间戳
    long end = System.currentTimeMillis();
    System.out.println("共花费了:"+(end-start));
    } catch (SQLException e) {
    e.printStackTrace();
    }
    }



    5) 事务(Transaction)


    演示银行转账的功能:
    1.创建一张表示账号的表
    CREATE TABLE t_account(
    id INT PRIMARY KEY AUTO_INCREMENT,
    a_name VARCHAR(50),
    balance DECIMAL(11,2)
    )

    2.向表中插入几个用户
    INSERT INTO t_account(id,a_name,balance) VALUES(NULL,'sunwukong',1000);
    INSERT INTO t_account(id,a_name,balance) VALUES(NULL,'zhubajie',1000);
    INSERT INTO t_account(id,a_name,balance) VALUES(NULL,'shaheshang',1000);
    SELECT * FROM t_account;


    3.#sunwukong向shaheshang转账100元
    #从sunwukong的账号减去100元
    UPDATE t_account SET balance = balance - 100 WHERE a_name='sunwukong';

    #给shaheshang的账号加上100元
    UPDATE t_account SET balance = balance +100 WHERE a_name = 'shaheshang';


    重新设置为1000元:
    UPDATE t_account SET balance =1000;

    4.从java代码中演示上面的案例:
    1.创建Dao类
    public class AcountDao {
    public void update(String name,double money){
    //准备两个变量
    Connection conn = null;
    PreparedStatement ps = null;
    //准备SQL模板
    String sql = "UPDATE t_account SET balance = balance + ? WHERE a_name = ?";

    try {
    conn = JDBCUtil.getConnection();
    //获取PreparedStatement
    ps = conn.prepareStatement(sql);
    //填充占位符
    ps.setDouble(1, money);
    ps.setString(2, name);

    //执行SQL语句
    ps.executeUpdate();

    } catch (SQLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }finally{
    JDBCUtil.close(conn, ps, null);
    }
    }
    }

    2.测试该DAO
    public class TestTransaction {
    private AcountDao accountDao = new AcountDao();

    @Test
    public void test() {
    //从sunwukong账户向shaheshang账户转账100元!
    //1.从sunwukong账户扣除100元
    accountDao.update("sunwukong", -100);
    //2.向shaheshang账户添加100元
    accountDao.update("shaheshang", 100);
    }
    }
    //显然上面是可以正常执行的!
    但是如果上面的程序在suwukong减去100元之后,shaheshang加钱之前,出现了异常,如下所示:

    //从sunwukong账户向shaheshang账户转账100元!
    //1.从sunwukong账户扣除100元
    accountDao.update("sunwukong", -100);
    int i =10/0;//添加一个异常
    //2.向shaheshang账户添加100元
    accountDao.update("shaheshang", 100);


    - 在开发中我们的一个业务往往需要同时操作多个表,这些操作往往是不可分割,业务中的对数据库的多次操作,
    要么同时成功,要么全都失败。

    - 事务的特性(ACID):
    原子性(atomicity)
    一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。

    一致性(consistency)
    事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

    隔离性(isolation)
    一个事务的执行不能被其他事务干扰。
    即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

    持久性(durability)
    持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。
    接下来的其他操作或故障不应该对其有任何影响。

    - 操作事务的基本步骤:
    1.开启事务
    - 开启事务以后,我们只后的所有操作将都会在同一个事务当中
    2.操作数据库
    - 开启事务以后再去操作数据库,所有操作将不会直接提交到数据库中
    3.提交事务
    - 将修改应用到数据库
    4.回滚事务
    - 数据库操作过程中出现异常了,回滚事务,回滚事务以后,数据库变成开启事务之前的状态

    - mysql中的事务控制
    #开启事务
    START TRANSACTION
    #回滚事务
    ROLLBACK
    #提交事务
    COMMIT

    - JDBC中的事务主要通过Connection对象来控制的
    1.开启事务
    void setAutoCommit(boolean autoCommit) throws SQLException;
    - 设置事务是否自动提交,默认是自动提交
    - 设置事务手动提交
    conn.setAutoCommit(false);

    2.提交事务
    void commit() throws SQLException;
    - 提交事务
    conn.commit()

    3.回滚事务
    void rollback() throws SQLException;
    - 回滚事务
    conn.rollback()

    - 事务控制的格式:
    //创建一个Connection
    Connection conn = null;

    try{

    //获取Connection
    conn = JDBCUtils.getConnection();

    //开启事务
    conn.setAutoCommit(false);

    //对数据库进行操作

    //操作成功,提交事务
    conn.commit();

    }catch(Exception e){
    e.printStackTrace();

    //回滚事务
    try {
    conn.rollback();
    } catch (SQLException e1) {
    e1.printStackTrace();
    }

    }finally{
    JDBCUtils.close(conn, null, null);
    }


    - 注意:我们在同一个事务中使用的数据库连接(Connection)必须是同一个,否则事务还是不作用!
    所以此时原来的AcountDAO中的update方法要改为如下所示:
    public class AcountDao {
    public void update(Connection conn,String name,double money){
    //准备两个变量
    PreparedStatement ps = null;
    //准备SQL模板
    String sql = "UPDATE t_account SET balance = balance + ? WHERE a_name = ?";

    try {
    //获取PreparedStatement
    ps = conn.prepareStatement(sql);
    //填充占位符
    ps.setDouble(1, money);
    ps.setString(2, name);

    //执行SQL语句
    ps.executeUpdate();

    } catch (SQLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }finally{
    //此时也不能在这里关闭数据库连接了,而是在外边统一关闭
    JDBCUtil.close(null, ps, null);
    }
    }
    }














  • 相关阅读:
    讲透学烂二叉树(五):分支平衡—AVL树与红黑树伸展树自平衡
    讲透学烂二叉树(四):二叉树的存储结构—建堆-搜索-排序
    讲透学烂二叉树(三):二叉树的遍历图解算法步骤及JS代码
    instanceof运算符的实质:Java继承链与JavaScript原型链
    JavaScript new 关键词解析及原生实现 new
    JavaScript继承的实现方式:原型语言对象继承对象原理剖析
    GitHub不再支持密码验证解决方案:SSH免密与Token登录配置
    PNG文件解读(2):PNG格式文件结构与数据结构解读—解码PNG数据
    PNG文件解读(1):PNG/APNG格式的前世今生
    JIT-动态编译与AOT-静态编译:java/ java/ JavaScript/Dart乱谈
  • 原文地址:https://www.cnblogs.com/lc-java/p/7454768.html
Copyright © 2020-2023  润新知