文章目录
- 1.动态代理
- 1.1 概念
- 1.2 jdk动态代理(重点)
- 1.3 基于子类的动态代理(了解)
- 2.AOP
- 2.1 概念
- 2.2 springAop — 基于AspectJ技术
- 2.2.1 AspectJ使用(XML)
- 2.2.2 AspectJ使用(注解开发)
- 3. JdbcTemplate
- 3.1 JdbcTemplate的基本概述
- 3.2 JdbcTemplate在spring的ioc中配置
- (1)XML配置
- (2)注解配置
- (4)JdbcTemplate的CRUD操作
- 4. Spring的事务管理
- 4.1 Spring的事务管理的API
- 4.2 配置编程式事务(了解)
- 4.3 配置声明式事务(XML方式)(重点)
- 4.4 配置声明式事务(注解方式)(重点)
- 4.5 编程式事务与声明式事务对比
1.动态代理
1.1 概念
- 作用: 将核心功能与辅助功能(事务、日志、性能监控代码)分离,达到核心业务功能更纯粹、辅助业务功能可复用。
- 功能分离:
- 分类:
- 基于接口的动态代理jdk
- 基于子类的动态代理cglib
- 两种代理的返回对象:
- 基于接口的动态代理返回对象为代理接口类型(接口引用指向实现)
- 基于子类的动态代理返回对象为代理类对象
1.2 jdk动态代理(重点)
(1)特点、作用、分类
- 特点: 代理对象在程序的运行过程中创建
- 作用: 不修改源码的基础上对目标类方法进行增强
- 分类:
- 基于接口的动态代理
- 涉及的类:Proxy
- 提供者:JDK官方
- 基于接口的动态代理
(2)使用
-
如何创建代理对象
- 使用Proxy类中的newProxyInstance方法
-
创建代理对象的要求
- 被代理类最少实现一个接口,如果没有则不能使用
-
newProxyInstance方法的参数
- ClassLoader
- 类加载器
- 它是用于加载代理对象字节码的,和被代理对象使用相同的类加载器。
- 固定写法(写被代理对象的类加载器)
- Class[ ]
- 字节码数组
- 它是用于让代理对象和被代理对象有相同的方法(只要两者都实现了同一个接口,那么两者的方法必然相同,所以我们传接口的字节码文件即可)。
- 固定写法(写接口字节码文件)
- InvocationHandler
- 处理器
- 用于提供增强的代码,它是让我们写如何代理,我们一般都是写一个InvocationHandler接口的实现类。通常情况下都是匿名内部类,但不是必须的,此接口的实现类都是谁用谁写
- ClassLoader
-
InvocationHandler中invoke方法参数
- @param proxy :当前代理对象的引用,一般很少用
- @param method: 当前执行的方法(被代理的方法)
- @param args: 当前执行方法需要的参数
- @return 和被代理对象一样的返回值,很少使用
(3)示例
- 演示转账工程中,事务代理
- 实现过程
- 使用工厂普通方法产生代理对象
- 配置bean.xml文件
- 测试
1.使用工厂普通方法产生代理对象
public class BeanFactory {
// 需要使用到的依赖类
private TransactionManager transactionManager;
private AccountService accountService; //被代理的对象
// 用于bean.xml注入(spring容器使用setter方法注入)
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
// 通过工厂创建方法
public AccountService getBean(){
AccountService proxy = (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 判断需要代理的方法
if ("transfer".equals(method.getName())) {
try {
// 开启事务
transactionManager.begin();
// 利用反射实现被代理对象的方法调用
method.invoke(accountService,args);
// 提交事务
transactionManager.commit();
} catch (Exception e) {
// 回滚事务
transactionManager.rollback();
e.printStackTrace();
} finally {
// 释放资源
transactionManager.realease();
}
}
return null;
}
});
return proxy;
}
}
2.配置bean.xml文件
<!-- 工厂注册-->
<bean id="beanFactory" class="com.qfedu.factory.BeanFactory">
<property name="transactionManager" ref="transactionManager"></property>
<property name="accountService" ref="accountService"></property>
</bean>
<!-- 使用工厂产生代理对象-->
<bean id="proxy" class="com.qfedu.service.impl.AccountServiceImpl" factory-bean="beanFactory" factory-method="getBean"></bean>
3.测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml" )
public class TestDemo01 {
@Autowired
@Qualifier("proxy")
private AccountService accountService;
@Test
public void testProxy(){
accountService.transfer("james","curry",100.0);
}
}
1.3 基于子类的动态代理(了解)
- 那我们要如何代理一个普通的Java类呢?我们可以使用基于子类的动态代理
(1)特点、作用、分类
- 特点: 字节码随用随创建,随用随加载。
- 作用: 不修改源码的基础上对目标类方法进行增强
- 分类:
- 第三方cglib库
- 使用Enhancer类
(2)使用
- 依赖的库(cglib)
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
-
如何创建代理对象
- 使用Enhancer类中的create方法
-
创建代理对象的要求
- 被代理类不能是最终类(也就是这个类不能用final修饰,因为最终类不能创建子类)
-
create方法的参数
- class:字节码
- 它是用于指定被代理对象的字节码
- callback:用于提供增强的代码
- 我们一般写的都是callback接口的子接口实现类:MethodInterceptor(为方法拦截的意思)
- class:字节码
-
MethodInterceptor实现类的intercept风法参数
- @param o 当前代理对象的引用
- @param method 代理对象需要执行的方法
- @param objects 代理对象执行该方法需要用到的参数
- @param methodProxy 当前执行方法的代理对象(不常用)
- @return 返回值也是跟被代理对象执行方法的返回值一样
-
示例:代理买电脑
1. 生产商
(类—不实现接口)
public class Producer{
public void buyProduct(String name) {
System.out.println(name + "售卖了电脑");
}
}
2. 代理商
/**
* 消费者
* 分别直接买
* 让代理买
*/
public class Consumer {
public static void main(String[] args) {
final Producer producer = new Producer();
Producer proxy = (Producer) Enhancer.create(Producer.class, new MethodInterceptor() {
/**
* @param o 当前代理对象的引用
* @param method 代理对象需要执行的方法
* @param objects 代理对象执行该方法需要用到的参数
* @param methodProxy 当前执行方法的代理对象(不常用)
* @return 返回值也是跟被代理对象执行方法的返回值一样
* @throws Throwable
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if(method.getName().equals("buyProduct")){
System.out.println("代理商对方法进行了增强");
method.invoke(producer,objects);
}
return null;
}
});
//代理商对象调用
proxy.buyProduct("经销商");
}
}
2.AOP
2.1 概念
- AOP:面向切面编程把通用共有的功能抽取出来,降低耦合,通过动态代理的方式来去执行。
- AOP利用的是一种横切技术,解剖开封装的对象内部,并将那些影响多个类的公共行为封装到一个可重用模块,这就是所谓的Aspect方面/切面。
- 切面,简单点所说,就是将哪些与业务无关,却为业务模块所共同调用的行为(方法)提取封装,减少系统的重复代码,以达到逻辑处理过程中各部分之间低耦合的隔离效果。
- AOP采取横向抽取机制,取代了传统的纵向继承体系重复性代码
2.2 springAop — 基于AspectJ技术
(1)AspectJ简介
- 使用AspectJ的原因:
- SpringAop的底层采用的是动态代理技术,但是动态代理(jdk动态代理 cglib字节码代理)过于繁琐。于是spring引入了第三方的AspectJ框架
- AspectJ
- AspectJ是一个AOP框架,Spring引入AspectJ作为自身的AOP的开发
- Spring两套AOP开发方式
- Spring传统方式动态代理(弃用)
- Spring基于AspectJ的AOP的开发(使用)
(2)AOP的相关术语
- 连接点: 一个普通的方法(任何方法都可以被增强,这些方法就称为连接点)
- 切点: 需要进行增强的方法
- 通知: 增强的逻辑(方法增强的功能)
- 切面: 切点+切面
- 织入: 将通知应用到目标的过程
2.2.1 AspectJ使用(XML)
(1)引入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
(2)expression:切点表达式
- public void com.qfdu.dao.impl.UserDaoImpl.findAll()
- 修饰符 返回值类型 方法名(方法参数)
- 1.修饰符可以省略
- 2.返回值类型不能省略 可以使用*来代替
- 3、包可以使用* 代替
- 4.方法()不能省略 * .():代表任意方法
- 5.(…) 方法参数使用…来替代
(3)AOP配置
- 前置通知:在目标方法执行之前进行增强
- 后置通知: 在目标方法执行之后进行的操作。
- 环绕通知: 在目标方法执行前和执行后都要执行(特别)
- 异常通知: 在目标方法执行出现异常的时候,进行增强(捕获到异常后才会进行增强)
- 最终通知: 无论代码是否有异常,总会执行
- 配置通知时,method表示切面类中的方法,pointcut-ref表示需要增强的方法
- 配置通知时,method表示切面类中的方法,pointcut-ref表示需要增强的方法
- 基于XML的AOP实现事务控制
<!-- AOP(面向切面编程)事务开发-->
<!-- 配置目标增强对象-->
<bean id="accountService" class="com.qfedu.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 将切面类交给spring管理(事务切面)-->
<bean id="transactionManager" class="com.qfedu.utils.TransactionManager">
<property name="connectionUtil" ref="connectionUtil"></property>
</bean>
<!-- 开启aop配置-->
<aop:config>
<!-- 1.定义切点-->
<aop:pointcut id="transaction" expression="execution(* com.qfedu.service.impl.AccountServiceImpl.transfer(..))"/>
<!-- 2.配置切面-->
<aop:aspect ref="transactionManager">
<!-- 前置通知+切点-->
<aop:before method="begin" pointcut-ref="transaction"></aop:before>
<!-- 后置通知+切点-->
<aop:after-returning method="commit" pointcut-ref="transaction"></aop:after-returning>
<!-- 环绕通知+切点-->
<!-- <aop:around method="transactionController" pointcut-ref="transaction"></aop:around>-->
<!-- 异常通知+切点-->
<aop:after-throwing method="rollback" pointcut-ref="transaction"></aop:after-throwing>
<!-- 最终通知+切点-->
<aop:after method="realease" pointcut-ref="transaction"></aop:after>
</aop:aspect>
</aop:config>
(4)环绕通知
- 切面环绕通知引用方法配置
- 在AOP配置中,配置环绕通知
2.2.2 AspectJ使用(注解开发)
(1)在配置文件引入spring对aop注解的支持
<!--开启包扫描-->
<context:component-scan base-package="com.qf"></context:component-scan>
<!--开启spring对aop注解的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
(2)声明切面类 —— @Aspect
(3)在切面类中配置切点和通知
- 方式一:
- 方式二:
(4)存在问题
- 在使用@Aspect时,由于Spring的版本不同,其通知注解的执行顺寻可能会存在为题
- @After 在 @AfterReturning和 @AfterThrowing之前执行
3. JdbcTemplate
3.1 JdbcTemplate的基本概述
(1)概念:
- JdbcTemplate是spring框架中提供的一个模板类,是对原生jdbc的简单封装.
- 模板类: 各种原生操作的封装工具
(2)常见操作的模板类:
- 操作关系型数据库
- JdbcTemplate
- HibernateTemplate
- 操作nosql数据库
- RedisTemplate
- 操作消息队列
- JmsTemplate
(3)作用
- 它是用和数据库交互的,实现数据表的crud操作
(4)依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<!-- tx:事务相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
3.2 JdbcTemplate在spring的ioc中配置
- JdbcTemplate 配置必须指定数据源
- DriverManagerDataSource 时Spring提供的数据源类,其祖先继承于
java.sql.DataSource
(1)XML配置
(2)注解配置
@PropertySource(value = "classpath:db.properties")
public class JdbcConfig {
// 数据库连接属性
@Value("${driverClass}")
private String driver;
@Value("${jdbcUrl}")
private String url;
@Value("${user}")
private String username;
@Value("${password}")
private String password;
// jdbcTemplate
@Bean("jdbcTemplate")
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
// 数据源
@Bean("dataSource")
public DataSource getDataSource(){
DriverManagerDataSource dataSource=new DriverManagerDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setUrl(url);
return dataSource;
}
}
(4)JdbcTemplate的CRUD操作
- execute:可以执行无返回值sql语句,常用于建表、删表的操作
- query: 用于查询结果集,并自动进行结果集映射封装(可以使用可变参数)
- update: 对数据库表进行更新
4. Spring的事务管理
4.1 Spring的事务管理的API
(1) PlatformTransactionManager
- 平台事务管理器:接口,是Spring用于管理事务的真正的对象。
- 实现类:
- DataSourceTransactionManager :底层使用JDBC管理事务。
- HibernateTransactionManager :底层使用Hibernate管理事务。
(2)TransactionDefinition
- 事务定义:用于定义事务的相关的信息,隔离级别、超时信息、传播行为、是否只读。
- 获取事务传播行为
- REQUIRED
如果当前没有事务,就新建一个事务,如果已经存在一个事务,就加入到该事务中(默认值)。
-
SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)。 -
NEVER
以非事务方式运行,如果当前存在事务,抛出异常。 -
获取事务是否只读:
- boolean isReadOnly()
读写型事务:增加、删除和修改会开启事务。
只读型事务:执行查询时,也会开启事务。
建议查询时设置为只读。
- boolean isReadOnly()
4.2 配置编程式事务(了解)
(1)配置平台事务管理器和事务管理模板
- 平台事务管理器必须指定数据源
- 事务管理模板必须指定事务管理器
<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理模板-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<!--注入平台事务管理器-->
<property name="transactionManager" ref="transactionManager"></property>
</bean>
(2)在业务类中注入事务管理模板
- 因为事务管理模板才是真正干活对象,真正进行事务的控制
private AccountDao accountDao;
private TransactionTemplate transactionTemplate;
//使用set方法注入
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
(3)在业务类里面进行事务控制
- 使用TransactionTemplate提供的
execute
方法- 其参数为抽象类的实现对象
TransactionCallbackWithoutResult
- 实现抽象方法
doInTransactionWithoutResult
- 将业务纯粹的业务逻辑写入
doInTransactionWithoutResult
方法中,实现事务管理
- 其参数为抽象类的实现对象
// 配置编程式事务
public void transfer(String sourceName, String targetName, Double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
//根据名称查询出转账用户
Account sourceAccount = accountDao.findAccountByName(sourceName);
//根据名称查询转入账用户
Account targetAccount = accountDao.findAccountByName(targetName);
//转出账户扣钱
sourceAccount.setMoney(sourceAccount.getMoney() - money);
//转入账户加钱
targetAccount.setMoney(targetAccount.getMoney() + money);
//更新转出账户
accountDao.updateAccount(sourceAccount);
int i = 1/0;
//更新转入账户
accountDao.updateAccount(targetAccount);
}
});
}
4.3 配置声明式事务(XML方式)(重点)
(1)配置事务增强
tx:advice
需要指定平台事务管理器
<!-- 平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
tx:advice
- 配置
<!--配置事务增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务的属性-->
<tx:attributes>
<!--name:给哪个业务方法配置事务 propagation:事务的传播行为 REQUIRED默认值-->
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
(2)配置aop,将事务增强和切点连接在一起
<!-- 配置aop-->
<aop:config>
<!-- 配置切点-->
<aop:pointcut id="pointCut" expression="execution(* com.qfedu.service.impl.*.*(..))"/>
<!-- 配置织入-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut"></aop:advisor>
</aop:config>
4.4 配置声明式事务(注解方式)(重点)
(1)配置开启注解对事务的支持
- 方式一: bean.xml中配置(指定平台事务管理器)
<!-- 扫描事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
- 方式二: 在Spring配置文件中添加 @EnableTransactionManagement 注解(扫描事务注解)
-
示例:
-
疑问: @EnableTransactionManagement怎么指定平台事务管理器呢?
- @EnableTransactionManagement会从容器中按照类型获取一个PlatformTransactionManager平台事务管理器
- 注解源码解释
-
(2)在业务类或业务类中方法上添加注解 @Transactional
- @Transactional解析
- 在@Transactional注解中,可以添加用于定义事务的相关的信息,隔离级别、超时信息、传播行为、是否只读。
- 在@Transactional注解中,可以添加用于定义事务的相关的信息,隔离级别、超时信息、传播行为、是否只读。
4.5 编程式事务与声明式事务对比
- 编程式事务: 通过硬编码的方式实现对事务的管理,存在耦合,此方式不可取
- 主要通过事务管理器和事务模板,来进行事务管理,执行事务模板的execute方法。存在严重耦合,不采用
- 声明式事务: 通过AOP的方式实现对事务的管理,不存在耦合性,便于管理(可配置)