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 的好处
1 每个事物逻辑位于一个位置, 代码不分散, 便于维护和升级
2 业务模块更简洁, 只包含核心业务代码
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="切入点表达式")
1 @Before: 前置通知, 在方法执行之前执行
2 @After: 后置通知, 在方法执行之后执行, 即无论连接点是正常返回还是抛出异常, 后置通知都会执行
3 @AfterRunning: 返回通知, 在方法返回结果之后执行 (如果异常, 不执行 )
在返回通知中访问连接点的返回值, 如果只想在连接点返回的时候记录日志, 应使用返回通知代替后置通知
1在返回通知中, 只要将 returning 属性添加到 @AfterReturning 注解中, 就可以访问连接点的返回值.
2必须在通知方法的签名中添加一个同名参数. 在运行时 Spring AOP 会通过这个参数传递返回值
3原始的切点表达式需要出现在 pointcut 属性中
4 @AfterThrowing: 异常通知, 在方法抛出异常之后执行 (如果无异常, 不执行)
将 throwing 属性添加到 @AfterThrowing 注解中, 在异常通知方法可以捕获到任何错误和异常.
也可以将参数声明为其他异常的参数类型. 然后通知就只在抛出这个类型及其子类的异常时才被执行
5 @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 > 元素声明.
1 定义在 < aop:aspect > 元素下: 只对当前切面有效
2 定义在 < 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)
1原子性(atomicity):"原子" 的本意是 "不可再分", 事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可. 事务的原子性要求事务中的所有操作要么都执行, 要么都不执行.
2一致性(consistency):"一致" 指的是数据的一致, 具体是指: 所有数据都处于满足业务规则的一致性状态.
3隔离性(isolation): 隔离性原则要求多个事务在并发执行过程中不会互相干扰.
4持久性(durability): 通常情况下, 事务对数据的修改应该被写入到持久化存储器中.
编程式事务
使用原生的 JDBC API 实现事务管理是所有事务管理方式的基石, 但是需要将事务管理代码嵌入到业务方法中, 事务与业务代码相耦合, 代码相对分散且混乱. 所以: 建议使用声明式事务.
1获取数据库连接 Connection 对象
2取消事务的自动提交
3执行操作
4正常完成操作时手动提交事务
5执行失败时回滚事务
6关闭相关资源
声明式事务
事务管理代码的固定模式作为一种横切关注点, 可以通过 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 属性中定义.
1REQUIRED 传播行为
当一个事务方法调用另一个事务方法时, 它默认会在现有的事务内运行. 因此在整个事务方法的开始和终止边界内只有一个事务. 即: 如果当前存在事务, 就使用当前事务. 如果当前没事务, 就创建一个新的事务, 去使用.
2. REQUIRES_NEW 传播行为
表示该事务方法必须启动一个新事务, 并在自己的事务内运行. 如果有事务在运行, 就应该先挂起它. 即: 无论当前是否存在事务, 都必须创建新事务, 去使用. 等新建事务运行结束后, 继续执行被挂起事务
事务的隔离级别
一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱. 主要为避免各种并发问题.
数据库提供的 4 种事务隔离级别:
读未提交(1), 存在问题: 脏读
读已提交(2), 存在问题: 不可重复读(建议使用)
可重复读(4), 存在问题: 幻读(建议使用)
串行化 (8), 存在问题: 效率低
用 @Transactional 注解声明式地管理事务时可以在 @Transactional 的 isolation 属性中设置隔离级别
触发事务回滚的异常
捕获到 RuntimeException 或 Error 时回滚, 而捕获到编译时异常不回滚.
通过 @Transactional 注解
1 rollbackFor 属性: 指定遇到时必须进行回滚的异常类型, 可以为多个
2 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>
来源: https://www.cnblogs.com/Open-ing/p/12164492.html