文章目录

一、今日内容

1、转账编码
2、解决转账问题
3、动态代理回顾
4、解决转账问题
5、什么是AOP
6、AOP的xml配置
7、AOP的注解配置

二、转账代码(初步编码+回顾IOC)

spring-day03-底层事务、AOP_xml

1、引入依赖

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.36</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
</dependencies>

2、实体类

public class Account {
private Integer id;
private String name;
private Float money;
}

3、持久层

AccountDao

package cn.ahpu.dao;

import cn.ahpu.Account;

/**
* @author 寒面银枪
* @create 2020-02-15 23:04
*/
public interface AccountDao {
/**
* 根据账户名查找账户
* @param fromName
* @return
*/
Account findByName(String fromName);

/**
* 更新账户
* @param fromAccount
*/
void update(Account fromAccount);
}

AccountDaoImpl

package cn.ahpu.dao.impl;

import cn.ahpu.Account;
import cn.ahpu.dao.AccountDao;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.sql.SQLException;

/**
* @author 寒面银枪
* @create 2020-02-15 23:06
*/
@Repository
public class AccountDaoImpl implements AccountDao {

@Autowired
QueryRunner queryRunner;

/**
* 根据账户名查找账户
*/
@Override
public Account findByName(String name) {
String sql="select * from account where name = ? ";
try {
return queryRunner.query(sql,new BeanHandler<Account>(Account.class),name);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}



/**
* 更新账户
*/
@Override
public void update(Account account) {
String sql="update account set money =? where name=?";
try {
queryRunner.update(sql,account.getMoney(),account.getName());
} catch (SQLException e) {
e.printStackTrace();
}

}
}

4、业务层

AccountService

package cn.ahpu.service;

/**
* @author 寒面银枪
* @create 2020-02-15 22:53
*/
public interface AccountService {


/**
* @param fromName 从哪个账户转出
* @param toName 转入到哪个账户
* @param money 本次转账金额
*/
public void transfer(String fromName,String toName,Float money);

}

AccountServiceImpl

package cn.ahpu.service.impl;

import cn.ahpu.Account;
import cn.ahpu.dao.AccountDao;
import cn.ahpu.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* @author 寒面银枪
* @create 2020-02-15 22:56
*
* 一个事务必须在一个connection中完成
* ThreadLocal:线程绑定
* 绑定connection对象
* 业务层和持久层需要connection时从ThreadLocal中获取即可
*
*/
@Service
public class AccountServiceImpl implements AccountService {

@Autowired
AccountDao accountDao;

/**
* @param fromName 从哪个账户转出
* @param toName 转入到哪个账户
* @param money 本次转账金额
*/
@Override
public void transfer(String fromName, String toName, Float money) {
Account fromAccount=accountDao.findByName(fromName);
Account toAccount=accountDao.findByName(toName);
fromAccount.setMoney(fromAccount.getMoney()-money);
toAccount.setMoney(toAccount.getMoney()+money);
accountDao.update(fromAccount);
System.out.println(1/0);//异常
accountDao.update(toAccount);

}

}

5、配置文件

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!--扫描包 创建对象-->
<context:component-scan base-package="cn.ahpu"></context:component-scan>

<!--创建queryRunner对象:构造方法中需要DataSource-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>

<!--创建连接池:ComboPooledDataSource-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

</beans>

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring331
jdbc.username=root
jdbc.password=root

6、测试

package cn.ahpu;

import cn.ahpu.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
* @author 寒面银枪
* @create 2020-02-15 23:24
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestTrans {

@Autowired
AccountService accountService;

@Test
public void test(){
accountService.transfer("aaa","bbb",100f);
}

}

7、发现问题

两次更新之间一旦有异常,就出错,A钱少了,B钱没加,银行越来越富,老百姓越来越穷
解决:添加事务处理
AccountServiceImpl

package cn.ahpu.service.impl;

import cn.ahpu.Account;
import cn.ahpu.dao.AccountDao;
import cn.ahpu.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* @author 寒面银枪
* @create 2020-02-15 22:56
*
* 一个事务必须在一个connection中完成
* ThreadLocal:线程绑定
* 绑定connection对象
* 业务层和持久层需要connection时从ThreadLocal中获取即可
*
*/
@Service
public class AccountServiceImpl implements AccountService {

@Autowired
AccountDao accountDao;

/**
* @param fromName 从哪个账户转出
* @param toName 转入到哪个账户
* @param money 本次转账金额
*/
@Override
public void transfer(String fromName, String toName, Float money) {
try {
//事务1:开启事务 conn.setAutoCommit(false)


//查询要转出的账户
Account fromAccount=accountDao.findByName(fromName);
//查询转入的账户
Account toAccount=accountDao.findByName(toName);
//修改要转出账户的余额:假设余额充足
fromAccount.setMoney(fromAccount.getMoney()-money);
//修改要转入账户的余额
toAccount.setMoney(toAccount.getMoney()+money);
//持久化到数据库
accountDao.update(fromAccount);
System.out.println(1/0);//异常
accountDao.update(toAccount);
//事务2:提交事务 conn.commit

} catch (Exception e) {
//事务3:回滚事务 conn.rollback
e.printStackTrace();
} finally {
//事务4:还原状态 conn.setAutoCommit(true)
}

}

/* 初版代码就这么简单
Account fromAccount=accountDao.findByName(fromName);
Account toAccount=accountDao.findByName(toName);
fromAccount.setMoney(fromAccount.getMoney()-money);
toAccount.setMoney(toAccount.getMoney()+money);
accountDao.update(fromAccount);
accountDao.update(toAccount);
*/

}

三、解决转账问题

此次代码实在上述代码基础上修改

1、引入工具类

新建包utils,复制两个工具类:

spring-day03-底层事务、AOP_sql_02


ConnectionUtil.java(保证当前线程获取的connection是同一个)

package cn.ahpu.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
* 一个管理连接的工具类,用于实现连接和线程的绑定
* 保证当前线程获取的connection是同一个
* @author 黑马程序员
* @Company http://www.ithiema.com
* @Version 1.0
*/
@Component
public class ConnectionUtil {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();//获得当前线程
@Autowired
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上绑定的连接
* @return
*/
public Connection getThreadConnection() {
try {
//1.先看看线程上是否绑了
Connection conn = tl.get();
if(conn == null) {
//2.从数据源中获取一个连接
conn = dataSource.getConnection();
//3.和线程局部变量绑定
tl.set(conn);
}
//4.返回线程上的连接
return tl.get();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}

/**
* 把连接和当前线程解绑
*/
public void remove() {
tl.remove();
}
}

TransactionManager.java(封装一下事务4步)

package cn.ahpu.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.SQLException;

/**
* 事务管理器
* @author 黑马程序员
* @Company http://www.ithiema.com *
* @Version 1.0
*/
@Component
public class TransactionManager {

@Autowired
private ConnectionUtil connectionUtil;

public void setConnectionUtil(ConnectionUtil connectionUtil) {
this.connectionUtil = connectionUtil;
}

//开启事务
public void beginTransaction() {
//从当前线程上获取连接,实现开启事务
try {
connectionUtil.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}

//提交事务
public void commit() {
try {
connectionUtil.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}


//回滚事务
public void rollback() {
try {
connectionUtil.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}

//释放连接
public void release() {
try {
connectionUtil.getThreadConnection().setAutoCommit(true);
//关闭连接(还回池中)
connectionUtil.getThreadConnection().close();
//解绑线程:把连接和线程解绑
connectionUtil.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

注意两个工具类都有@Component注解 服务器启动时spring都会创建唯一单例类到容器中,也即两个工具类都可以自动注入了

2、修改业务层

加获取connection
AccountDaoImpl.java

package cn.ahpu.dao.impl;

import cn.ahpu.Account;
import cn.ahpu.dao.AccountDao;
import cn.ahpu.utils.ConnectionUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.sql.Connection;
import java.sql.SQLException;

/**
* @author 寒面银枪
* @create 2020-02-15 23:06
*/
@Repository
public class AccountDaoImpl implements AccountDao {

@Autowired
QueryRunner queryRunner;

@Autowired
ConnectionUtil connectionUtil;

/**
* 根据账户名查找账户
*/
@Override
public Account findByName(String name) {
String sql="select * from account where name = ? ";
try {
//从线程中获取一个连接对象
Connection conn = connectionUtil.getThreadConnection();
return queryRunner.query(conn,sql,new BeanHandler<Account>(Account.class),name);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}

/**
* 更新账户
*/
@Override
public void update(Account account) {
String sql="update account set money =? where name=?";
try {
Connection conn = connectionUtil.getThreadConnection();
queryRunner.update(conn,sql,account.getMoney(),account.getName());
} catch (SQLException e) {
e.printStackTrace();
}

}
}

修改处:

spring-day03-底层事务、AOP_sql_03

spring-day03-底层事务、AOP_xml_04

AccountServiceImpl

package cn.ahpu.service.impl;

import cn.ahpu.Account;
import cn.ahpu.dao.AccountDao;
import cn.ahpu.service.AccountService;
import cn.ahpu.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* @author 寒面银枪
* @create 2020-02-15 22:56
*
* 一个事务必须在一个connection中完成
* ThreadLocal:线程绑定
* 绑定connection对象
* 业务层和持久层需要connection时从ThreadLocal中获取即可
*
*/
@Service
public class AccountServiceImpl implements AccountService {

@Autowired
AccountDao accountDao;

@Autowired
TransactionManager txManager;

/**
* 假设需要事务管理 此方法仅为体现问题 不用
* @param account
*/
public void update(Account account){
try {
//事务1:开启事务 conn.setAutoCommit(false)
txManager.beginTransaction();
accountDao.update(account);//本来一行的事
//事务2:提交事务 conn.commit
txManager.commit();
} catch (Exception e) {
//事务3:回滚事务 conn.rollback
txManager.rollback();
e.printStackTrace();
} finally {
//事务4:还原状态 conn.setAutoCommit(true)
txManager.release();
}
}

/**
* @param fromName 从哪个账户转出
* @param toName 转入到哪个账户
* @param money 本次转账金额
*/
@Override
public void transfer(String fromName, String toName, Float money) {
try {
//事务1:开启事务 conn.setAutoCommit(false)
txManager.beginTransaction();


//查询要转出的账户
Account fromAccount=accountDao.findByName(fromName);
//查询转入的账户
Account toAccount=accountDao.findByName(toName);
//修改要转出账户的余额:假设余额充足
fromAccount.setMoney(fromAccount.getMoney()-money);
//修改要转入账户的余额
toAccount.setMoney(toAccount.getMoney()+money);
//持久化到数据库
accountDao.update(fromAccount);
System.out.println(1/0);//异常
accountDao.update(toAccount);
//事务2:提交事务 conn.commit
txManager.commit();

} catch (Exception e) {
//事务3:回滚事务 conn.rollback
txManager.rollback();
e.printStackTrace();
} finally {
//事务4:还原状态 conn.setAutoCommit(true)
txManager.release();
}
}

}

此时再测试就没问题了!

3、发现新问题

问题:

1. 重复代码(每一个业务需求函数都要加那4步代码)
2. 代码臃肿问题(本来更新account一行即可 转账业务也6行即可 现在try-catch-事务等弄了一大堆)
3. 技术与业务整合到一起了(业务逻辑代码里冲刺着大量技术相关代码(事务处理))

解决思路:

1. 提取重复的代码
2. 业务层中不需要技术代码
3. 不修改业务层源码的情况下,技术增强
4. 使用动态代理

动态代理

特点:随用随创建,随用随加载
不修改原来代码的基础上,对原来的代码增强

四、动态代理回顾

spring-day03-底层事务、AOP_xml_05


准备工作:

pom.xml:

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies>

ProductFactory

public class ProductFactory {

/**
* 制造产品
*/
public void make(){
System.out.println("生产了一个产品!");
}

}

OldSale

/**
* 建立了一个销售的网点:直营
* 厂家直营 没有中间商赚差价
* @author 寒面银枪
* @create 2020-02-16 22:33
*/
public class OldSale {

public void sale(Float money){
System.out.println("正在以"+money+"价格卖出");
}
}

NewSale

package cn.ahpu.sale;

/**
* 总经销商
* @author 寒面银枪
* @create 2020-02-16 22:39
*/
public interface NewSale {

/**
* 卖商品
* @param money
*/
public void sale(Float money);
}

NewSaleImpl

public class NewSaleImpl implements NewSale {
/**
* 卖商品
* @param money
*/
@Override
public void sale(Float money) {
System.out.println("正在以"+money+"价格卖出");
}
}

1、jdk动态代理

jdk动态代理: 基于接口的动态代理
必须实现接口 必须有一个统一接口

TestProxy

@Test
public void testJdkProxy(){
//真实的对象
NewSale newSale=new NewSaleImpl();

//创建代理对象 本质也是NewSale接口的一个实现类 不过是在已有类的基基础上新建的一个实现类
//参数1:类加载器
//参数2:类实现的接口
//参数3:真实对象的增强部分 即:实现了InvocationHandler接口的类(此处就用匿名内部类)

NewSale sale= (NewSale) Proxy.newProxyInstance(newSale.getClass().getClassLoader(), newSale.getClass().getInterfaces(), new InvocationHandler() {
/**
* 增强内容
* @param proxy 代理对象:增强后的对象
* @param method 代理的方法:未增强的方法
* @param args 代理方法的参数
* @return 代理方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//生成产品
ProductFactory productFactory = new ProductFactory();
productFactory.make();
//开始销售:通过反射执行真实对象的方法
//参数1: 真实对象
//参数2: 方法参数
method.invoke(newSale,args);//就是代理的方法 原来的方法 也可以可以获取方法名然后判断是否增强

/*主要就是在method.invoke()前后进行增强 ★*/

//判断是否挣钱
//卖的价格 args[0] 假设成本1500
if((Float)args[0]>1500){
//卖的价格高于成本 可买
System.out.println("卖的价格高于成本 可买");
}else{
//赔了
System.out.println("卖的价格低于成本 不可买");
}
return null;
}
});

sale.sale(2000f);

}

2、cglib动态代理

记得引入cglibjar包

cglib动态代理: 基于的动态代理
第三方jar cglib-2.2.2.jar
代理的类不能用final修饰

/**
* cglib动态代理
* 代理对象是真实对象的一个子类(★ 写个子类增强父类方法 多简单)
*/
@Test
public void testCglibProxy(){
//真实对象
OldSale oldSale = new OldSale();

//创建cglib代理对象
//1.创建增强类对象
Enhancer enhancer = new Enhancer();
//2.指定代理对象的父类
enhancer.setSuperclass(oldSale.getClass());
//3.指定增强内容
// MethodInterceptor接口其实是个方法拦截器
enhancer.setCallback(new MethodInterceptor() {
/**
*
* @param o 代理对象,增强后的对象
* @param method 被代理的方法 未增强的方法
* @param objects 代理方法的参数
* @param methodProxy 代理方法 增强后的方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//生产产品
new ProductFactory().make();

//开始销售:执行真实对象的内容
method.invoke(oldSale,objects);

//判断是否挣钱了
if((Float)objects[0]>1500){
System.out.println("卖的价格高于成本 可买");
}else{
System.out.println("卖的价格低于成本 不可买");
}
return null;
}
});

//4.创建代理对象
OldSale sale = (OldSale) enhancer.create();

sale.sale(1000f);
}

五、动态代理解决新问题(自己写底层代理代码)

上述三代码基础上再修改,两个工具类都还用,不过事务处理不写在service层了,而是测试时用动态代理加事务,一加service层所有方法就都添加上了事务

AccountServiceImpl.java

package cn.ahpu.service.impl;

import cn.ahpu.Account;
import cn.ahpu.dao.AccountDao;
import cn.ahpu.service.AccountService;
import cn.ahpu.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* @author 寒面银枪
* @create 2020-02-15 22:56
* <p>
* 一个事务必须在一个connection中完成
* ThreadLocal:线程绑定
* 绑定connection对象
* 业务层和持久层需要connection时从ThreadLocal中获取即可
*/
@Service
public class AccountServiceImpl implements AccountService {

@Autowired
AccountDao accountDao;

/**
* 更新账户 假设需要事务
* @param account
*/
@Override
public void update(Account account){
accountDao.update(account);
}

/**
* @param fromName 从哪个账户转出
* @param toName 转入到哪个账户
* @param money 本次转账金额
*/
@Override
public void transfer(String fromName, String toName, Float money) {
//查询要转出的账户
Account fromAccount = accountDao.findByName(fromName);
//查询转入的账户
Account toAccount = accountDao.findByName(toName);
//修改要转出账户的余额:假设余额充足
fromAccount.setMoney(fromAccount.getMoney() - money);
//修改要转入账户的余额
toAccount.setMoney(toAccount.getMoney() + money);
//持久化到数据库
accountDao.update(fromAccount);
System.out.println(1 / 0);//异常
accountDao.update(toAccount);
}

}

1、jdk动态代理解决问题

//jdk动态代理
@Test
public void testJDKProxyService() {
//创建业务层代理对象
AccountService service= (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//开启事务
txManager.beginTransaction();

//执行真实对象的方法
method.invoke(accountService,args);

//提交事务
txManager.commit();

} catch(Exception e) {
//事务回滚
txManager.rollback();

e.printStackTrace();
} finally {
//还原状态
txManager.release();
}

return null;
}
});

//AccountService的所有方法就都加上事务了
service.transfer("aaa","bbb",100f);

}

2、cglib动态代理解决问题

//cglib动态代理
@Test
public void testCglibProxy(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(accountService.getClass());
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
try {
//开启事务
txManager.beginTransaction();
//执行方法
method.invoke(accountService,objects);
//提交事务
txManager.commit();
} catch (Exception e) {
//回滚事务
txManager.rollback();
e.printStackTrace();
} finally {
//还原状态
txManager.release();
}
return null;
}
});

//创建代理对象
AccountService service= (AccountService) enhancer.create();
//AccountService的所有方法就都加上事务了
service.transfer("aaa","bbb",100f);

}

六、什么是AOP

概念

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

spring-day03-底层事务、AOP_sql_06

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

spring-day03-底层事务、AOP_xml_07

相关术语

Joinpoint(连接点):

所谓连接点是指那些被拦截到的点。 在spring中,这些点指的是方法,因为spring只支持方法类型的连接点

Pointcut(切入点):

所谓切入点是指我们要对哪些Joinpoint进行拦截的定义

Advice(通知/增强):

所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。

Introduction(引介):

引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field。

Target(目标对象):

代理的目标对象。

Weaving(织入):

是指把增强应用到目标对象来创建新的代理对象的过程。
spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

Proxy(代理):

一个类被AOP织入增强后,就产生一个结果代理类。

Aspect(切面):

是切入点和通知(引介)的结合。

七、AOP的xml配置

AOP写日志,以理解AOP

新建项目

spring-day03-底层事务、AOP_xml_08

1、依赖

<dependencies>
<!--spring核心包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<!--spring测试包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--引入单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<!--aop配置 必备包 切面 版本必须1.8.7以上-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
</dependencies>

2、service

UserService.java

public interface UserService {
public void print();
}

AccountService.java

public interface AccountService {
public void transfer(String name);
}

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {
@Override
public void print() {
System.out.println("service执行了...........");
System.out.println(1/0);
}
}

AccountServiceImpl.java

@Service
public class AccountServiceImpl implements AccountService {
@Override
public void transfer(String name) {
System.out.println("service层转账");
}
}

3、Log

Logger.java

package cn.ahpu.log;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;

/**
* 日志类
* 记录什么时间访问什么类,什么方法,操作什么功能
* @author 寒面银枪
* @create 2020-02-18 0:07
*/
public class Logger {
/**
* @param joinPoint 连接点--拦截到的方法(注意P大写的)
*/
public void before(JoinPoint joinPoint){
//被代理的对象
Object target = joinPoint.getTarget();
//拦截的类的名称
String className = target.getClass().getName();
//方法对象
Signature signature = joinPoint.getSignature();
//方法名
String methodName = signature.getName();
System.out.println("拦截到"+className+"."+methodName);
System.out.println("前置通知:可以开启事务");
}

public void afterReturn(){
System.out.println("后置通知:可以提交事务");
}

public void after(){
System.out.println("最终增强:可以还原事务状态");
}

public void afterThrowing(Exception e){
System.out.println("执行的方法的异常:"+e);
System.out.println("异常通知:可以回滚事务");
}

//最牛环绕增强
/**
* ProceedingJoinPoint 可以执行拦截到的方法的连接点对象
* @param joinPoint
*/
public void around(ProceedingJoinPoint joinPoint){
try {
System.out.println("前置通知:可以开启事务");
//执行原来的方法 可以获取方法的返回值o
Object o = joinPoint.proceed();
System.out.println("后置通知:可以提交事务");
} catch (Throwable e) {//Throwable比exception还要大 错误还是异常都捕捉
e.printStackTrace();
System.out.println("异常通知:可以回滚事务");
} finally {
System.out.println("最终增强:可以还原事务状态");
}
}
}

4、配置

spring第三天讲义
applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!--扫描包,查找@Component,@Controller,@Service,@Repository 创建bean对象-->
<context:component-scan base-package="cn.ahpu"></context:component-scan>

<!--通知对象:拦截到方法时,通知执行的对象-->
<!--
通知的类型:
前置通知:在拦截到的方法之前执行
后置通知:在拦截到的方法执行完之后执行-返回之前执行-如有异常,则不执行
最终通知:方法执行完后总会执行-相当于finally
异常通知:方法出现异常则执行
环绕通知:前置通知+后值通知+最终通知+异常通知(一个代表4个 4处都执行)
-->
<bean id="logger" class="cn.ahpu.log.Logger"></bean>

<!--配置AOP-->
<aop:config>
<!--配置切面=切入点(对哪些方法进行拦截)+通知(通知对象上面已经创建)-->
<aop:aspect ref="logger"><!--通知对象这里指定-->
<!--配置切入点
id:唯一的标志
expression:表达式
eg:* cn.ahpu.service.impl.*.*(..)
第一个*:代表方法返回值类型任意
第二个*:类名任意 即:impl包中所有的类
第三个*:任意方法名
(..):参数任意,个数和类型和顺序都任意

其他的配置方式:
public void cn.ahpu.service.impl.UserServiceImpl.findAll()
void cn.ahpu.service.impl.UserServiceImpl.findAll() (即:public可以省略)
* cn.ahpu.service..UserServiceImpl.findAll() (..表示service包及其所有子包)

注: 通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
execution(* cn.ahpu.service.impl.*.*(..))
execution(* cn.ahpu.service..*.*(..))

-->
<aop:pointcut id="pointcut" expression="execution(* cn.ahpu.service.impl.*.*(..))"></aop:pointcut>

<!--织入:告诉通知对象logger 具体执行哪个方法
两个属性:切入点(拦截到的方法) 和 对应方法(需要执行的方法)
-->
<!--前置通知-->
<!--<aop:before method="before" pointcut-ref="pointcut"></aop:before>-->
<!--后置通知-->
<!--<aop:after-returning method="afterReturn" pointcut-ref="pointcut"></aop:after-returning>-->
<!--最终通知-->
<!--<aop:after method="after" pointcut-ref="pointcut"></aop:after>-->
<!--异常通知 throwing="e" 参数名-->
<!--<aop:after-throwing throwing="e" method="afterThrowing" pointcut-ref="pointcut"></aop:after-throwing>-->

<!--前四个都注释(ctrl+/即可) 只留最后一个-->
<!--环绕通知-->
<aop:around method="around" pointcut-ref="pointcut"></aop:around>

</aop:aspect>
</aop:config>

</beans>

5、测试

TestAOPXml.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAOPXml {

@Autowired
UserService userService;

@Autowired
AccountService accountService;

@Test
public void test(){
userService.print();
System.out.println("---------------------------------------");
accountService.transfer("tom");//所有service层的方法都被增强了
}
}

八、AOP的注解方式

相对于xml仅applicationContext.xml和Logger.java俩文件内容不同
xml里配置一行开启aop注解代理即可
Logger.java里多了一大堆注解

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!--扫描包,查找@Component,@Controller,@Service,@Repository 创建bean对象-->
<context:component-scan base-package="cn.ahpu"></context:component-scan>

<!--开启AOP自动代理-注解方式代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

<!--通知对象:直接类上面@Component即可-->

<!--剩下的配置全部在log类内以注解方式配好了-->

</beans>

Logger.java

package cn.ahpu.log;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;


@Component //创建类对象
@Aspect //配置该类为切面 即:切入点+通知
public class Logger {

/**
* 配置切入点
*/
@Pointcut("execution(* cn.ahpu.service.impl.*.*(..))")
public void ponintcut(){};//方法名和参数无所谓 因为相当于xml中的id


/**
* @param joinPoint 连接点--拦截到的方法(注意P大写的)
*/
// @Before("ponintcut()")
public void before(JoinPoint joinPoint){
//被代理的对象
Object target = joinPoint.getTarget();
//拦截的类的名称
String className = target.getClass().getName();
//方法对象
Signature signature = joinPoint.getSignature();
//方法名
String methodName = signature.getName();
System.out.println("拦截到"+className+"."+methodName);
System.out.println("前置通知:可以开启事务");
}

// @AfterReturning("ponintcut()")
public void afterReturn(){
System.out.println("后置通知:可以提交事务");
}

// @After("ponintcut()")
public void after(){
System.out.println("最终增强:可以还原事务状态");
}

// @AfterThrowing(value = "ponintcut()",throwing = "e")
public void afterThrowing(Exception e){
System.out.println("执行的方法的异常:"+e);
System.out.println("异常通知:可以回滚事务");
}

//最牛环绕增强
/**
* ProceedingJoinPoint 可以执行拦截到的方法的连接点对象
* @param joinPoint
*/
@Around("ponintcut()") //开此注解时注释掉了前4个注解 1个抵4个
public void around(ProceedingJoinPoint joinPoint){
try {
System.out.println("前置通知:可以开启事务");
//执行原来的方法 可以获取方法的返回值o
Object o = joinPoint.proceed();
System.out.println("后置通知:可以提交事务");
} catch (Throwable e) {//Throwable比exception还要大 错误还是异常都捕捉
e.printStackTrace();
System.out.println("异常通知:可以回滚事务");
} finally {
System.out.println("最终增强:可以还原事务状态");
}
}
}

测试代码也同上

九、核心总结

1. aop
什么是aop:对方法进行增强
面向切面编程
面向多个对象管理
面向对象编程
1) 连接点:spring中方法就是连接点
2) 切面: 切入点 + 通知(增强) ===> 织入
3) 切入点:定义切入(拦截)哪个点 ,切入点表达式:
* cn.ahpu.service.impl.*.*(..)
* cn.ahpu.service..*.*(..)
4) 通知:拦截到方法后进行的增强内容
通知的类型
前置通知:
后置通知:
最终通知: finally ,释放资源
异常通知:
环绕通知:
5) 织入 切入点 和通知 织入到一起
6)target :目标对象, 真实的对象
7)



-------------------------------------------------------



作业:用工具类+AOP 管理事务

xml配置AOP

类结构图

spring-day03-底层事务、AOP_xml_09

1、依赖

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<!--AOP-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.36</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
</dependencies>

2、domain

public class Account {
private Integer id;
private String name;
private Float money;
}

3、dao

AccountDao.java

public interface AccountDao {
Account findByName(String name);
void update(Account account);
}

AccountDaoImpl.java

package cn.ahpu.dao.impl;

import cn.ahpu.dao.AccountDao;
import cn.ahpu.domain.Account;
import cn.ahpu.utils.ConnectionUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.sql.Connection;
import java.sql.SQLException;

/**
* @author 寒面银枪
* @create 2020-02-18 21:08
*/
@Repository
public class AccountDaoImpl implements AccountDao {

@Autowired
ConnectionUtil connectionUtil;

@Autowired
QueryRunner queryRunner;

@Override
public Account findByName(String name) {
String sql="select * from account where name=?";
try {
return queryRunner.query(connectionUtil.getThreadConnection(),sql,new BeanHandler<>(Account.class),name);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}

@Override
public void update(Account account) {
String sql="update account set money=? where name=?";
try {
queryRunner.update( connectionUtil.getThreadConnection(),sql,account.getMoney(),account.getName());
} catch (SQLException e) {
e.printStackTrace();
}

}
}

4、service

AccountService.java

public interface AccountService {
public void transfer(String fromName,String toName,Float money);
}

AccountServiceImpl.java

package cn.ahpu.service.impl;

import cn.ahpu.dao.AccountDao;
import cn.ahpu.domain.Account;
import cn.ahpu.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* @author 寒面银枪
* @create 2020-02-18 21:01
*/
@Service
public class AccountServiceImpl implements AccountService {

@Autowired
AccountDao accountDao;

@Override
public void transfer(String fromName, String toName, Float money) {
//查询转出的账户
Account fromAccount=accountDao.findByName(fromName);
//查询接收的账户
Account toAccount=accountDao.findByName(toName);
//开始转账
fromAccount.setMoney(fromAccount.getMoney()-money);
toAccount.setMoney(toAccount.getMoney()+money);
//持久化到数据库
accountDao.update(fromAccount);
System.out.println(1/0);
accountDao.update(toAccount);
}
}

5、utils

ConnectionUtil.java

package cn.ahpu.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
* 一个管理连接的工具类,用于实现连接和线程的绑定
* 保证当前线程获取的connection是同一个
* @author 黑马程序员
* @Company http://www.ithiema.com
* @Version 1.0
*/
@Component
public class ConnectionUtil {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();//获得当前线程
@Autowired
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上绑定的连接
* @return
*/
public Connection getThreadConnection() {
try {
//1.先看看线程上是否绑了
Connection conn = tl.get();
if(conn == null) {
//2.从数据源中获取一个连接
conn = dataSource.getConnection();//queryRunner内不必再获取连接了 可以检测到你获取没有
//3.和线程局部变量绑定
tl.set(conn);
}
//4.返回线程上的连接
return tl.get();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}

/**
* 把连接和当前线程解绑
*/
public void remove() {
tl.remove();
}
}

TransactionManager.java

package cn.ahpu.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.SQLException;

/**
* 事务管理器: 是AOP通知对象
* @author 黑马程序员
* @Company http://www.ithiema.com *
* @Version 1.0
*/
@Component
public class TransactionManager {

@Autowired
private ConnectionUtil connectionUtil;

public void setConnectionUtil(ConnectionUtil connectionUtil) {
this.connectionUtil = connectionUtil;
}

//开启事务
public void beginTransaction() {
//从当前线程上获取连接,实现开启事务
try {
connectionUtil.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}

//提交事务
public void commit() {
try {
connectionUtil.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}


//回滚事务
public void rollback() {
try {
connectionUtil.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}

//释放连接
public void release() {
try {
connectionUtil.getThreadConnection().setAutoCommit(true);
//关闭连接(还回池中)
connectionUtil.getThreadConnection().close();
//解绑线程:把连接和线程解绑
connectionUtil.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

6、配置

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启注解 扫描包-->
<context:component-scan base-package="cn.ahpu"></context:component-scan>

<!--dao层需要注入的类 queryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"></bean>
<!--连接池 dataSource-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--引入外部property-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

<!--
使用AOP解决事务问题
事务管理器就是通知对象
-->
<aop:config>
<!--配置切面 默认类名-->
<aop:aspect ref="transactionManager">
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* cn.ahpu.service.impl.*.*(..))"></aop:pointcut>
<!--织入-->
<!--前置通知:开启事务-->
<aop:before method="beginTransaction" pointcut-ref="pointcut()"></aop:before>
<!--后置增强:提交事务-->
<aop:after-returning method="commit" pointcut-ref="pointcut()"></aop:after-returning>
<!--异常通知:回滚事务-->
<aop:after-throwing method="rollback" pointcut-ref="pointcut()"></aop:after-throwing>
<!--最终通知:还原事务状态-->
<aop:after method="release" pointcut-ref="pointcut()"></aop:after>

</aop:aspect>
</aop:config>

</beans>

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring331
jdbc.username=root
jdbc.password=root

7、测试

TestTransfer.java

package cn.ahpu;

import cn.ahpu.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
* @author 寒面银枪
* @create 2020-02-18 21:30
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestTransfer {

@Autowired
AccountService accountService;

@Test
public void test(){
accountService.transfer("aaa","bbb",100f);
}
}

注解实现AOP

xml基础上仅applicationContext.xml和TransactionManager不同

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启注解 扫描包-->
<context:component-scan base-package="cn.ahpu"></context:component-scan>

<!--dao层需要注入的类 queryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"></bean>
<!--连接池 dataSource-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--引入外部property-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

<!--开启AOP自动代理注解-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>


</beans>

TransactionManager.java

package cn.ahpu.utils;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.SQLException;

/**
* 事务管理器: 是AOP通知对象
* @author 黑马程序员
* @Company http://www.ithiema.com *
* @Version 1.0
*/
@Component
@Aspect
public class TransactionManager {

@Autowired
private ConnectionUtil connectionUtil;

public void setConnectionUtil(ConnectionUtil connectionUtil) {
this.connectionUtil = connectionUtil;
}

//切入点
/* @Pointcut("execution(* cn.ahpu.service.impl.*.*(..))")
public void pointcut(){ }*/

/*注解方式要使用环绕增强 分开4不无法控制顺序 不可行*/
//可以省略切入点定义 每个方法上都要自己写切入点表达式了 更灵活也更麻烦
@Around("execution(* cn.ahpu.service.impl.*.*(..))") //织入 拦截到哪些方法时触发事件
public void around(ProceedingJoinPoint joinPoint){
try {
//开启事务
beginTransaction();
//执行原来的方法
joinPoint.proceed();
//提交
commit();
} catch (Throwable throwable) {
//回滚
rollback();
throwable.printStackTrace();
} finally {
//还原
release();
}
}


//开启事务
public void beginTransaction() {
//从当前线程上获取连接,实现开启事务
try {
connectionUtil.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}

//提交事务
public void commit() {
try {
connectionUtil.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}


//回滚事务
public void rollback() {
try {
connectionUtil.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}

//释放连接
public void release() {
try {
connectionUtil.getThreadConnection().setAutoCommit(true);
//关闭连接(还回池中)
connectionUtil.getThreadConnection().close();
//解绑线程:把连接和线程解绑
connectionUtil.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
}