20201125黄春跃
20201125黄春跃
知识点
什么是 AOP
AOP:全称是 Aspect Oriented Programming 即:面向切面编程。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改
源码的基础上,对我们的已有方法进行增强
AOP 的作用及优势
作用:
在程序运行期间,不修改源码对已有方法进行增强。
优势:
减少重复代码
提高开发效率
维护方便
AOP 的实现方式
使用动态代理技术
AOP的具体应用
案例中问题
这是我们之前中做的增删改查例子。下面是客户的业务层实现类。我们能看出什么问题吗?
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
//省略了部分代码
@Override
public void saveAccount(Account account) {
accountDao.save(account);
}
@Override
public void updateAccount(Account account) {
accountDao.update(account);
}
@Override
public void deleteAccount(Integer accountId) {
accountDao.delete(accountId);
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findById(accountId);
}
@Override
public List<Account> findAllAccount() {
return accountDao.findAll();
}
}
问题就是:
事务被自动控制了。换言之,我们使用了 connection 对象的 setAutoCommit(true)
此方式控制事务,如果我们每次都执行一条 sql 语句,没有问题,但是如果业务方法一次要执行多条
sql语句,这
种方式就无法实现功能了。
请看下面的示例:
我们在业务层中多加入一个方法。
public void transfer(Integer sourceId, Integer targetId, Float money) {
//根据名称查询两个账户信息
Account source = accountDao.findById(sourceId);
Account target = accountDao.findById(targetId);
//转出账户减钱,转入账户加钱
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
//更新两个账户
accountDao.update(source);
int i=1/0; //模拟转账异常
accountDao.update(target);
}
当我们执行时,由于执行有异常,转账失败。但是因为我们是每次执行持久层方法都是独立事务,导致
无法实现事务控制(不符合事务的一致性)
问题的解决
解决办法:让业务层来控制事务的提交和回滚。
改造后的业务层实现类:
注:此处没有使用 spring 的 IoC.
package com.itlaobing.service.impl;
import com.itlaobing.config.JdbcUtils;
import com.itlaobing.dao.IAccountDao;
import com.itlaobing.dao.impl.AccountDaoImpl;
import com.itlaobing.model.Account;
import com.itlaobing.service.IAccountService;
import org.springframework.stereotype.Component;
import java.sql.SQLException;
import java.util.List;
/**
* @Classname AccountServiceImpl
* @Description TODO()
* @Date 2019/12/13 20:37
* @Author by Administrator
* @Version v1.0
*/
@Component("accountService")
public class AccountServiceImpl implements IAccountService {
// @Resource
private IAccountDao accountDao = new AccountDaoImpl();
@Override
public void saveAccount(Account account) {
try {
JdbcUtils.beginTransaction();
accountDao.save(account);
JdbcUtils.commitTransaction();
} catch (SQLException e) {
e.printStackTrace();
try {
JdbcUtils.rollbackTransaction();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
@Override
public void updateAccount(Account account) {
try {
JdbcUtils.beginTransaction();
accountDao.update(account);
JdbcUtils.commitTransaction();
} catch (SQLException e) {
e.printStackTrace();
try {
JdbcUtils.rollbackTransaction();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
@Override
public void deleteAccount(Integer accountId) {
try {
JdbcUtils.beginTransaction();
accountDao.delete(accountId);
JdbcUtils.commitTransaction();
} catch (SQLException e) {
e.printStackTrace();
try { JdbcUtils.rollbackTransaction();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findById(accountId);
}
@Override
public List<Account> findAllAccount() {
return accountDao.findAll();
}
@Override
public void transfer(Integer sourceId, Integer targetId, Float money)
{
try {
JdbcUtils.beginTransaction();
//根据名称查询两个账户信息
Account source = accountDao.findById(sourceId);
Account target = accountDao.findById(targetId);
//转出账户减钱,转入账户加钱
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
//更新两个账户
accountDao.update(source);
int i=1/0; //模拟转账异常
accountDao.update(target);
JdbcUtils.commitTransaction();
} catch (Exception e) {
e.printStackTrace();
try {
JdbcUtils.rollbackTransaction();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
}
JdbcUtils
package com.itlaobing.util;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/*** @Classname JdbcUtils
* @Description TODO()
* @Date 2019/12/15 20:02
* @Author by Administrator
* @Version v1.0
*/
public class JdbcUtils {
//饿汉模式
private static final ComboPooledDataSource ds = new
ComboPooledDataSource();
static{
Properties prop = new Properties();
InputStream in =
JdbcUtils.class.getResourceAsStream("db.properties");
try {
prop.load(in);
ds.setDriverClass(prop.getProperty("jdbc.driver"));
ds.setJdbcUrl(prop.getProperty("jdbc.url"));
ds.setUser(prop.getProperty("jdbc.username"));
ds.setPassword(prop.getProperty("jdbc.password"));
} catch (IOException e) {
e.printStackTrace();
} catch (PropertyVetoException e) {
e.printStackTrace();
}
}
/**
* 创建一个数据源,并存入 spring 容器中
* @return
*/
public static DataSource getDataSource() {
return ds;
}
/**
* 它为null表示没有事务
* 它不为null表示有事务
* 当开启事务时,需要给它赋值
* 当结束事务时,需要给它赋值为null
* 并且在开启事务时,让dao的多个方法共享这个Connection
*/
private static ThreadLocal<Connection> tl = new
ThreadLocal<Connection>();
/**
* dao使用本方法来获取连接
*/
public static Connection getConnection() throws SQLException {
/*
* 如果有事务,返回当前事务的con
* 如果没有事务,通过连接池返回新的con
*/Connection con = tl.get();//获取当前线程的事务连接
if (con != null) {
//有事务
return con;
}
return ds.getConnection();
}
/**
* 开启事务
*/
public static void beginTransaction() throws SQLException {
Connection con = tl.get();//获取当前线程的事务连接
if (con != null){
throw new SQLException("已经开启了事务,不能重复开启!");
}
con = ds.getConnection();//给con赋值,表示开启了事务
con.setAutoCommit(false);//设置为手动提交
tl.set(con);//把当前事务连接放到tl中
}
/**
* 提交事务
*/
public static void commitTransaction() throws SQLException {
Connection con = tl.get();//获取当前线程的事务连接
if (con == null) {
throw new SQLException("没有事务不能提交!");
}
con.commit();//提交事务
con.close();//关闭连接
con = null;//表示事务结束!
tl.remove();
}
/**
* 回滚事务
*/
public static void rollbackTransaction() throws SQLException {
Connection con = tl.get();//获取当前线程的事务连接
if (con == null) {
throw new SQLException("没有事务不能回滚!");
}
con.rollback();
con.close();
con = null;
tl.remove();
}
/**
* 释放Connection
*/
public static void releaseConnection() throws SQLException {
System.out.println("释放Connection");
}
}
动态代理常用的有两种方式
基于接口的动态代理
提供者:JDK 官方的 Proxy 类。
要求:被代理类最少实现一个接口。
基于子类的动态代理
提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。
要求:被代理类不能用 final 修饰的类(最终类)。
使用 JDK 官方的 Proxy 类创建代理对象
此处我们使用的是一个演员的例子:
在很久以前,演员和剧组都是直接见面联系的。没有中间人环节。
而随着时间的推移,产生了一个新兴职业:经纪人(中间人),这个时候剧组再想找演员就需要通过经
纪人来找了。下面我们就用代码演示出来
package com.itlaobing;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Actor implements IActor{
@Override
public void basicAct(double money) {
System.out.println("拿到钱,开始基本表演" + money);
}
@Override
public void dangerAct(double money) {
System.out.println("拿到钱,开始危险表演" + money);
}
public static void main(String[] args) {
//需要找一个演员,没有经纪人的模式:
final Actor actor = new Actor(); //直接创建
IActor proxyActor = (IActor)Proxy.newProxyInstance(actor.getClass().getClassLoader(), actor.getClass().getInterfaces(), new InvocationHandler() {
@Override
/*
* method 当前执行的方法对象 public abstract void com.itlaobing.IActor.basicAct/dangerAct(double)
* args 执行方法所需的参数 money
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//拿到方法名
String name = method.getName();
Double money = (Double) args[0];
Object rtValue = null;
if("basicAct".equalsIgnoreCase(name)){
if(money > 2000){
rtValue = method.invoke(actor , money/2);
}
}
if("dangerAct".equalsIgnoreCase(name)){
if(money > 5000){
rtValue = method.invoke(actor , money/2);
}
}
return null;
}
});
proxyActor.basicAct(8000);
proxyActor.dangerAct(50000);
}
}
package com.itlaobing;
public interface IActor {
//基本演出
public void basicAct(double money);
//危险演出
public void dangerAct(double money);
}
使用 CGLib 的 Enhancer 类创建代理对象
导入CGLib依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
package com.itlaobing;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Actor2 {
public void basicAct(double money) {
System.out.println("拿到钱,开始基本表演,挣了" + money + "元");
}
public void dangerAct(double money) {
System.out.println("拿到钱,开始危险表演,挣了" + money + "元");
}
public static void main(String[] args) {
/**
* 基于子类的动态代理
* 要求:被代理对象不能是最终类
* 用到的类:Enhancer
* 用到的方法:reate(Class, Callback)
* 方法的参数:
* Class:被代理对象的字节码
* Callback:如何代理
* @param args
*/
Actor2 actor2 = new Actor2();
Actor2 proxyActor = (Actor2) Enhancer.create(actor2.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
/**
* 执行被代理对象的任何方法,都会经过该方法。
* 在此方法内部就可以对被代理对象的任何方法进行增强。
*
* 参数:前三个和基于接口的动态代理是一样的。
* MethodProxy:当前执行方法的代理对象。
* 返回值:
* 当前执行方法的返回值
*/
String name = method.getName();
Double money = (Double) args[0];
Object rtValue = null;
if("basicAct".equalsIgnoreCase(name)){
if(money > 2000){
rtValue = method.invoke(actor2, money/2);
}
}
if("dangerAct".equalsIgnoreCase(name)){
if(money > 5000){
rtValue = method.invoke(actor2, money/2);
}
}
return rtValue;
}
});
proxyActor.basicAct(10000);
proxyActor.dangerAct(100000);
}
}
class ActorChild extends Actor2 {
@Override
public void basicAct(double money) {
super.basicAct(money / 2);
}
@Override
public void dangerAct(double money) {
super.dangerAct(money / 2);
}
}
Spring 中的 AOP
AOP 相关术语
Aspect(切面):
是切入点和通知(引介)的结合。
Joinpoint(连接点):
所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连
接点
Advice(通知/增强):
在特定的连接点处采取的操作就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Pointcut(切入点):
匹配连接点的谓词。建议与切入点表达式关联,并在与该切入点匹配的任何连接点处运行(例如,执行
具有特定名称的方法)。切入点表达式匹配的连接点的概念是AOP的核心,并且Spring默认使用
AspectJ切入点表达语言。
Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法
或 Field。
Target(目标对象):
代理的目标对象。一个或多个方面通知的对象。由于Spring AOP是使用运行时代理实现的,因此该对象
将始终是代理对象。
Proxy(代理):
一个类被 AOP 织入增强后,就产生一个结果代理类。由AOP框架创建的一个对象,用于实现方法执行
等。在Spring框架中,AOP代理将是JDK动态代理或CGLIB代理。
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。
spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
学习 spring 中的 AOP 要明确的事
- 开发阶段(我们做的)
编写核心业务代码(开发主线):要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后再做)
在配置文件中,声明切入点与通知间的关系,即切面。 - 运行阶段(Spring 框架完成的)
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目
标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的
代码逻辑运行。
关于代理的选择
在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
基于 XML 的 AOP 配置
按照pdf一步一步的来
总结
晚自习后面把Spring从头开始看了看。
近期评论