1.AOP的概念
1.1 AOP简单样例
我们来先通过一个例子来对AOP进行理解,这个例子就是有关Spring的事务的一个样例,有关Spring是怎么实现事务的,这个事务其实本质上就是对于我们代码的一个增强。废话不多说,上程序,请各位同学自行感悟。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<!--有单元测试的环境,Spring5版本,Junit4.12版本-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- Spring整合Junit测试的jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
然后我们去看一眼我们的数据库,有没有一个Spring_db的表,(如果没有请参照上一篇博客第四小节应该有,创建数据库的sql语句)
然后我们创建一个model的包,包中有创建一个Account的实体类
/**
* JavaBean 实体类
* 封装数据
*/
public class Account {
private int id;
private String name;
private double money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public Account() {
}
public Account(int id, String name, double money) {
this.id = id;
this.name = name;
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
然后创建Dao的包,写一个AccountDao的接口
/**
* 持久层接口
* 实现保存转账金额的方法
*/
public interface AccountDao {
public void saveAll (Account account);
}
再写一个AccountDaoImpl的实现类
/**
* 持久层的实现类
*/
public class AccountDaoImpl implements AccountDao {
@Override
public void saveAll(Account account) {
//jdbc的程序
System.out.println("持久层保存成功");
}
}
创建service包,再service包下写AccountService的接口
/**
* 业务层的接口
*/
public interface AccountService {
public void saveAll(Account account, Account account1);
}
然后写一个AccountServiceImpl的实现类
public class AccountServiceImpl implements AccountService{
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void saveAll(Account account, Account account1) {
System.out.println("业务层保存方法执行");
accountDao.saveAll(account);
accountDao.saveAll(account1);
}
}
写一个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">
<bean id="accountService" class="com.qcby.service.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<bean id="accountDao" class="com.qcby.dao.AccountDaoImpl"/>
</beans>
然后在test下创建com.qcby的包结构,写一个Test1的测试类
public class Test1 {
@Test
public void run1(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationcontext.xml");
AccountService as=(AccountService) ac.getBean("accountService");
Account account=new Account(1,"熊大",10000.00);
Account account1=new Account(2, "熊二",9999.99);
as.saveAll(account,account1);
}
}
然后我们去写持久层,在写持久层的时候,我们首先要有一个工具类
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
public class TxUtils {
//静态的类属性 连接池对象
public static DruidDataSource ds=null;
//使用ThreadLocal存储当前线程中的connection对象
private static ThreadLocal<Connection> threadLocal=new ThreadLocal<>();
//在静态代码中创建数据库连接池
static {
try {
//通过代码创建druid数据库连接池
ds=new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql///spring_db");
ds.setUsername("root");
ds.setPassword("2020");
}catch (Exception e){
throw new ExceptionInInitializerError(e);
}
}
public static Connection getConnection() throws Exception{
Connection conn=threadLocal.get();
if (conn==null){
//从数据源获取数据连接conn
conn= getDataSource().getConnection();
//把conn对象存储到当前线程
threadLocal.set(conn);
}
return conn;
}
/**
* 开启事务
*/
public static void startTransaction() {
try{
Connection conn=threadLocal.get();
if (conn==null){
conn=getConnection();
threadLocal.set(conn);
}
//开启事务
conn.setAutoCommit(false);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 回滚事务
*/
public static void rollback(){
try{
Connection conn=threadLocal.get();
if (conn!=null){
conn.rollback();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 提交事务
*/
public static void commit(){
try{
Connection conn=threadLocal.get();
if (conn!=null){
conn.commit();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 关闭数据库连接,这里的关闭指的是把数据库连接还给数据库连接池
*/
public static void close(){
try{
Connection conn=threadLocal.get();
if (conn!=null){
conn.close();
threadLocal.remove();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 获取数据
*/
public static DruidDataSource getDataSource(){
return ds;
}
}
这个工具类有几个值得注意的点我们来看一下,第一个,就是我们在类中设置了一个静态的类的属性 ,可以用threadLocal存储这对象,达到只用一个连接的目的,还有就是我们把close的方法写到这个工具类里了,因为我们不能在持久层关闭连接,我们可以从业务层进行关闭。
然后我们再回到持久层,写连接
public class AccountDaoImpl implements AccountDao {
@Override
public void saveAll(Account account) throws Exception {
//jdbc的连接数据库进行保存数据
//获取连接加载驱动 保证事务
System.out.println("持久层保存操作!!!");
//把数据存到数据库之中
//获取连接
Connection conn = TxUtils.getConnection();
//编写sql
String sql="insert into account values(null,?,?)";
//预编译SQL
PreparedStatement statement= conn.prepareStatement(sql);
//设置值
statement.setString(1,account.getName());
statement.setDouble(1,account.getMoney());
//执行操作
statement.executeUpdate();
//关闭资源,(conn的关闭不能写在持久层)
statement.close();
}
}
这时候我们的业务实现层肯定会有一个异常,跑出去就行了。
然后运行一下test测试
那么我们接下来模拟这么一个场景,就是插入第一条数据后,在插入第二条数据第二条数据时模拟一个异常。
try {
accountDao.saveAll(account);
int i=10/0;
accountDao.saveAll(account1);
} catch (SQLException e) {
e.printStackTrace();
}
这时候就会有一个除零的异常
按照测试代码,我们应该加入小明和小红两条数据,但是加入小明之后我们就出现异常就终止执行了,就没法插入小红的这条数据了。
@Test
public void run(){
ApplicationContext ac=new ClassPathXmlApplicationContext("ApplicationContext.xml");
AccountService accountService =(AccountService) ac.getBean("accountService");
Account account=new Account(null,"小明",1000.0);
Account account1=new Account(null,"小红",1000.0);
accountService.saveAll(account,account1);
}
然后我们去业务层中,加上事务的管理
public void saveAll(Account account, Account account1) {
System.out.println("业务层方法执行了");
//开启事务 conn是一个连接
TxUtils.startTransaction();
try {
accountDao.saveAll(account);
int i=10/0;
accountDao.saveAll(account1);
//提交事务
TxUtils.commit();
} catch (SQLException e) {
e.printStackTrace();
//回滚事务
TxUtils.rollback();
}
}
然后我们把那个除零的异常语句给注释掉,我们执行以下看一下效果
能加进来
然后我们去把那个异常的注释给解掉,然后再来运行看一下效果。
数据库中就没有添加进去小明。说明进行了事务的回滚。
然后如果我们要在业务写一个转账的方法,也需要些开启事务,提交事务,还有回滚事务,就比较麻烦了。
就是加事务这个代码,我们会发现与业务本身的逻辑没有关系,就是需要在业务逻辑执行前开启事务,执行完提交事务,要是出现问题就要回滚事务。这个事务就式对我们代码的一个增强。
我们如果要做到不在改源代码的情况下实现事务,我们实现的思路有哪些?
jdk动态代理
静态代理
父子类(父类做业务逻辑,子类做增强)
装饰者模式
AOP的底层就是动态代理
1.2 jdk动态代理类
上面说了,我们用jdk的动态代理的目的就是实现代码的增强。
我们首先就要有一个代理的类
代理类中有一个获取代理对象的方法,这个方法有三个参数,类的加载器,代理对象所实现的接口,回调函数,然后就在invoke方法中写我们对原代码的增强 method让目标对象执行方法(反射调用)。
public class JdkProxy {
//获取代理对象的方法
//增强目标对象的方法
public static Object getProxy(AccountService accountService){
/**
* 使用jdk的动态代理生成代理对象
* Proxy一个有关反射的一个类 三个参数 类的加载器 代理对象所实现的接口 回调函数 并把对象给返回
* 通过Java反射的一个类,里面有一个生成代理对象的方法
* 静态代理和动态代理的区别 一个是继承,一个是基于接口实现的让目标对象执行方法(反射调用),然后
*/
Object proxy=Proxy.newProxyInstance(JdkProxy.class.getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
//
/**
* 调用代理对象的方法,invoke方法就会执行
* method就是对目标对象方法
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result=null;
try{
//事务处理,开启事务
TxUtils.startTransaction();
//让目标对象执行方法(反射调用)
method.invoke(accountService,args);
//提交事务
TxUtils.commit();
}catch (Exception e){
e.printStackTrace();
//回滚事务
TxUtils.rollback();
}finally {
TxUtils.close();
}
return proxy;
}
});
return null;
}
然后在测试方法中这么使用就可以了
@Test
public void run(){
ApplicationContext ac=new ClassPathXmlApplicationContext("ApplicationContext.xml");
AccountService accountService =(AccountService) ac.getBean("accountService");
Account account=new Account(null,"小明",1000.0);
Account account1=new Account(null,"小红",1000.0);
// accountService.saveAll(account,account1);
//增强的代理对象来执行
Object proxyobj= JdkProxy.getProxy(accountService);
//转型
AccountService proxy=(AccountService) proxyobj;
//调用逻辑
proxy.saveAll(account,account1);
}
然后我们要是想看到这个方法的相互的调用的过程的话就打个断点debug走一下就明白了