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;}@Overridepublic 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 {@Overridepublic 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;}@Overridepublic 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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://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 {@Testpublic 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){//从数据源获取数据连接connconn= 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 {@Overridepublic void saveAll(Account account) throws Exception {//jdbc的连接数据库进行保存数据//获取连接加载驱动 保证事务System.out.println("持久层保存操作!!!");//把数据存到数据库之中//获取连接Connection conn = TxUtils.getConnection();//编写sqlString sql="insert into account values(null,?,?)";//预编译SQLPreparedStatement 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*/@Overridepublic 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;}
然后在测试方法中这么使用就可以了
@Testpublic 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走一下就明白了