Java框架之Spring02-AOP-动态代理-AspectJ-JdbcTemplate-事务

时间:2022-05-13 03:46:59

AOP

动态代理

  代理设计模式的原理:使用一个代理将原本对象包装起来,然后用该代理对象”取代”原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

代理模式的三要素:

  • 代理主题接口

  • 代理者

  • 被代理者

代理模式的主要优点

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;

  • 代理对象可以扩展目标对象的功能;

  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

其主要缺点

  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;

  • 增加了系统的复杂度;

动态代理的方式

   静态代理类只能替一个主题接口进行代理工作

       基于接口实现动态代理: JDK动态代理

       基于继承实现动态代理: Cglib、Javassist动态代理

JDK动态代理步骤:
* 1、编写主题接口
* 2、编写被代理类
* 3、编写代理工作处理器:即代理类要替被代理类做什么事情(有参构造器)
* 要求:必须实现InvocationHandler,重写
* Object invoke(Object proxy, Method method, Object[] args)
* 第一个参数:代理类对象
* 第二个参数:被代理类和代理类 要执行的方法
* 第三个参数:要执行方法的实参列表
* 这个invoke方法不是程序员调用,当代理类对象执行对应的代理方法时,自动调用的
* 4、创建代理类及其对象
* 需要:Proxy:提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
* static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
* 第一个参数:被代理类的类加载器,我们希望被代理和代理类使用同一个类加载器
* 第二个参数:被代理类实现的接口们
* 第三个参数:代理工作处理器对象
* 5、调用被代理的方法

  注意:代理对象和实现类对象,都实现了相同的接口。属于兄弟关系。(不能强制转换为,实现类对象)

AOP概述

  1) AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统 OOP(ObjectOrientedProgramming),面向对象编程)的补充。

  面向对象  纵向继承机制

  面向切面  横向抽取机制

  2) AOP编程操作的主要对象是切面(aspect),而切面用于横切关注点。
  3) 在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的类里——这样的类我们通常称之为“切面”。

    4) AOP的好处

    ① 每个事物逻辑位于一个位置,代码不分散,便于维护和升级

    ② 业务模块更简洁,只包含核心业务代码

AOP术语

1.横切关注点
  从每个方法中抽取出来的同一类非核心业务。
2.切面(Aspect)
  封装横切关注点信息的类,每个关注点体现为一个通知方法。
3.通知(Advice)
  切面必须要完成的各个具体工作
4.目标(Target)
  被通知的对象
5.代理(Proxy)
  向目标对象应用通知之后创建的代理对象
6. 连接点(Joinpoint)
  横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置。例如:类某个方法调用前、调用后、方法捕获到异常后等。
7. 切入点(pointcut):

  定位连接点的方式。每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物。
  如果把连接点看作数据库中的记录,那么切入点就是查询条件——AOP可以通过切入点定位到特定的连接点。
  切点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

AspectJ

 启用AspectJ注解支持

1、导入JAR包

2、引入aop名称空间

3、配置:<aop:aspectj-autoproxy> 

  当Spring IOC容器侦测到bean配置文件中的<aop:aspectj-autoproxy>元素时,会自动为与AspectJ切面匹配的bean创建代理

 用AspectJ注解声明切面

     在Spring中声明AspectJ切面为bean实例

  初始化AspectJ切面之后,容器就会为那些与 AspectJ切面相匹配的bean创建代理

  在AspectJ注解中,切面只是一个带有@Aspect注解的Java类

  通知是标注有某种注解的Java方法

  5种类型的通知注解:@Before(value="切入点表达式")

    ① @Before:前置通知,在方法执行之前执行

    ② @After:后置通知,在方法执行之后执行,即无论连接点是正常返回还是抛出异常,后置通知都会执行

    ③ @AfterRunning:返回通知,在方法返回结果之后执行   (如果异常,不执行 )

      在返回通知中访问连接点的返回值,如果只想在连接点返回的时候记录日志,应使用返回通知代替后置通知

      ①在返回通知中,只要将returning属性添加到@AfterReturning注解中,就可以访问连接点的返回值。

      ②必须在通知方法的签名中添加一个同名参数。在运行时Spring AOP会通过这个参数传递返回值

      ③原始的切点表达式需要出现在pointcut属性中

    ④ @AfterThrowing:异常通知,在方法抛出异常之后执行  (如果无异常,不执行)

      将throwing属性添加到@AfterThrowing注解中,在异常通知方法可以捕获到任何错误和异常。

      也可以将参数声明为其他异常的参数类型。然后通知就只在抛出这个类型及其子类的异常时才被执行

    ⑤ @Around:环绕通知,围绕着方法执行

      能够全面地控制连接点,甚至可以控制是否执行连接点。

      连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。

      需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。

      注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常

    @Around(value = "rePointCut()")
    public Object aroundMethod(ProceedingJoinPoint pjp) {
        Object obj = null;
        try {
            //前置通知
            System.out.println("前置通知");
            obj = pjp.proceed();    //调用目标对象的方法
            //返回通知
            System.out.println("返回通知,结果:"   obj);
        } catch (Throwable e) {
            //异常通知
            System.out.println("异常通知,ex:"   e);
            e.printStackTrace();
        } finally {
            //后置通知
            System.out.println("后置通知");
        }
        return obj;
    }

 

切入点表达式

通过表达式的方式定位一个或多个具体的连接点。

语法格式

execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))

 

表达式:    @Pointcut(value="execution(* com.spring.*.*(..))")
含义:       ArithmeticCalculator接口中声明的所有方法。
    第一个“*”代表任意修饰符及任意返回值。
   第二个“*”代表,任意类的全类名称|任意类名 第三个“
*”代表任意方法。 “..”匹配任意数量、任意类型的参数。 若目标类、接口与该切面类在同一个包中可以省略包名。

 

切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。

重用切入点

  在AspectJ切面中,可以通过@Pointcut注解将一个切入点声明成简单的方法。切入点的方法体通常是空的

  切入点方法的访问控制符同时也控制着这个切入点的可见性。

  在引入这个切入点时,必须将类名也包括在内。如果类没有与这个切面放在同一个包中,还必须包含包名。

  其他通知可以通过方法名称引入该切入点

//提取表达式
@Pointcut(value="execution(* com.spring.aspectj.*.*(..))")
public void rePointCut() {}
@Before(value="rePointCut()"):当前类中重用切入点表达式

 

指定切面的优先级

在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的

使用@Order注解,序号出现在注解中

 

@Aspect
@Order(0) //int类型,数值越小,优先级越高。     
public class TestAspect{}

 

XML方式配置切面

  基于注解的声明要优先于基于XML的声明,通过AspectJ注解,切面可以与AspectJ兼容,而基于XML的配置则是Spring专有的,所以不推荐

  在bean配置文件中,所有的Spring AOP配置都必须定义在<aop:config>元素内部。对于每个切面而言,都要创建一个<aop:aspect>元素来为具体的切面实现引用后端bean实例。

  切面bean必须有一个标识符,供<aop:aspect>元素引用。

1)声明切入点

    切入点使用<aop:pointcut>元素声明。

     ① 定义在<aop:aspect>元素下:只对当前切面有效

         ② 定义在<aop:config>元素下:对所有切面都有效

  基于XML的AOP配置不允许在切入点表达式中用名称引用其他切入点

2)声明通知

  通知元素需要使用<pointcut-ref>来引用切入点

     method属性指定切面类中通知方法的名称

    <aop:config>
        <aop:pointcut id="myPointcut" expression="execution(* com.spring.aspectj_xml.*.*(..))" />
        <!-- 定义日志切面 -->
        <aop:aspect id="loggingAspect" ref="loggingAspect" order="1">
            <aop:before method="beforeMethod" pointcut-ref="myPointcut"/>
            <aop:after method="afterMethod" pointcut-ref="myPointcut"/>
            <aop:after-returning method="afterReturnMethod" returning="rs" pointcut-ref="myPointcut"/>
            <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="myPointcut"/>
        </aop:aspect>
    </aop:config>

 

JdbcTemplate

  可以将Spring的JdbcTemplate看作是一个小型的轻量级持久化层框架,JdbcTemplate类是线程安全的

  JdbcTemplate所需要的JAR包

    spring-jdbc-4.0.0.RELEASE.jar

    spring-orm-4.0.0.RELEASE.jar

    spring-tx-4.0.0.RELEASE.jar

   数据库驱动和数据源

    druid-1.1.9.jar

             mysql-connector-java-5.1.7-bin.jar

配置文件中配置相关的bean

    <!--    引入jdbc配置文件-->
        <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!--    装配Druid数据源conn-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          p:url="${jdbc.url}"
          p:username="${jdbc.username}"
          p:password="${jdbc.password}"
          p:driverClassName="${jdbc.driverClass}"
    ></bean>
    <!--    通过数据源装配JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--    通过数据源装配事务管理器-->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--    启用事务管理器-->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>

 

持久化操作

  1) 增删改

    update(String sql,Object... args)

  2) 批处理增删改

    batchUpdate(String sql,List<Object[]> batchArgs)  

    Object[]封装了SQL语句每一次执行时所需要的参数

               List集合封装了SQL语句多次执行时的所有参数

    3)获取单个数值型

    queryForObject(String sql,Class<T> requiredType,Object... args)

  4)获取单个对象类型

    queryForObject(String sql,RowMapper rowMapper,Object... args)

  5)获取多个JavaBean类型

    query(String sql,RowMapper rowMapper,Object... args)

RowMapper对象可以使用BeanPropertyRowMapper实现类:注意new对象时指定类型

事务管理

   事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行要么都不执行

   事务的四个属性(ACID)

     ①原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。

     ②一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态

     ③隔离性(isolation):隔离性原则要求多个事务在并发执行过程中不会互相干扰

     ④持久性(durability):通常情况下,事务对数据的修改应该被写入到持久化存储器中。

编程式事务

使用原生的JDBC API实现事务管理是所有事务管理方式的基石,但是需要将事务管理代码嵌入到业务方法中,事务与业务代码相耦合,代码相对分散且混乱。所以:建议使用声明式事务。

         ①获取数据库连接Connection对象

         ②取消事务的自动提交

         ③执行操作

         ④正常完成操作时手动提交事务

         ⑤执行失败时回滚事务

         ⑥关闭相关资源

声明式事务

  事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。它将事务管理代码从业务方法中分离出来

  Spring的核心事务管理抽象是它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。开发人员可以通过配置的方式进行事务管理。

         事务管理器可以以普通的bean的形式声明在Spring IOC容器中。

事务管理器的主要实现

  1) DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。

  2) JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理

  3) HibernateTransactionManager:用Hibernate框架存取数据库

实现

  1) 配置文件:如上图

  2) 在需要进行事务控制的方法上加注解 @Transactional

    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED,
            timeout=3,
            readOnly=false,
            noRollbackFor=RuntimeException.class)
    public void purchase(String username, String isbn) {}

 

propagation属性详解

事务的传播行为

  当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。

  事务传播属性通过在@Transactional注解的propagation属性中定义。

Java框架之Spring02-AOP-动态代理-AspectJ-JdbcTemplate-事务

  ①REQUIRED传播行为

    当一个事务方法调用另一个事务方法时,它默认会在现有的事务内运行。因此在整个事务方法的开始和终止边界内只有一个事务。即:如果当前存在事务,就使用当前事务。如果当前没事务,就创建一个新的事务,去使用。

  ②. REQUIRES_NEW传播行为

    表示该事务方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。即:无论当前是否存在事务,都必须创建新事务,去使用。等新建事务运行结束后,继续执行被挂起事务

事务的隔离级别

  • 一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。主要为避免各种并发问题。

  • 数据库提供的 4 种事务隔离级别:

Java框架之Spring02-AOP-动态代理-AspectJ-JdbcTemplate-事务

  读未提交(1),存在问题:脏读
  读已提交(2),存在问题:不可重复读(建议使用)
  可重复读(4),存在问题:幻读(建议使用)
  串行化 (8),存在问题:效率低  

  用@Transactional注解声明式地管理事务时可以在@Transactional的isolation属性中设置隔离级别

触发事务回滚的异常

   捕获到RuntimeException或Error时回滚,而捕获到编译时异常不回滚。

       通过@Transactional 注解

         ① rollbackFor属性:指定遇到时必须进行回滚的异常类型,可以为多个

         ② noRollbackFor属性:指定遇到时不回滚的异常类型,可以为多个

事务的超时和只读属性

   超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。

       只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。

  readOnly=true,
    true:事务只读,一旦设置只读属性,该事务就不能进行:增删改操作。
    false:设置事务为,不只读。
  timeout=3,  设置事务的“强制回滚”时间秒。 

基于xml方式配置声明式事务

    <!-- 配置事务切面 -->
    <aop:config>
        <aop:pointcut expression="execution(* com.tx.component.service.BookShopServiceImpl.purchase(..))"
                      id="txPointCut"/>
        <!-- 将切入点表达式和事务属性配置关联到一起 -->
        <aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/>
    </aop:config>
    <!-- 配置基于XML的声明式事务  -->
    <tx:advice id="myTx" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 设置具体方法的事务属性 -->
            <tx:method name="find*" read-only="true"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="purchase"
                       isolation="READ_COMMITTED"
                       no-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException"
                       propagation="REQUIRES_NEW"
                       read-only="false"
                       timeout="10"/>
        </tx:attributes>
    </tx:advice>