http://git.oschina.net/zhuqiang/smart-framework 跟着书上学习的 框架git地址
http://git.oschina.net/zhuqiang/mrweb 依赖上面框架的demo练习
ThreadLocal
ThreadLocal : 本地线程(局部变量),作用其实就是,让每个线程拥有独立的一份变量,比如同一个类中的一个static属性,如果用ThreadLocal来存储,那么无论你new 多少个线程共享这个类,但是这个被static修饰的变量在每个线程里面的值都是唯一的.
用法就不写了.直接模拟实现这个ThreadLocal.他有4个方法.
- T get() 返回此线程局部变量的当前线程副本中的值。
- protected T initialValue() 返回此线程局部变量的当前线程的“初始值”。
- void remove() 移除此线程局部变量当前线程的值。
- void set(T value) 将此线程局部变量的当前线程副本中的值设置为指定值。
/** * Created by zhuqiang on 2015/10/29 0029. */
public class MyThreadLocal<T> {
Map<Thread, T> container = new ConcurrentHashMap<>();
/** 返回此线程局部变量的当前线程副本中的值。 */
public T get() {
Thread ct = Thread.currentThread();
if (container.containsKey(ct)) {
return container.get(ct);
} else { //如果没有值,就返回默认值
return initialValue();
}
}
/** 返回此线程局部变量的当前线程的“初始值”。 */
protected T initialValue() {
return null;
}
/** 移除此线程局部变量当前线程的值。 */
public void remove() {
Thread ct = Thread.currentThread();
if (container.containsKey(ct)) {
container.remove(ct);
}
}
/** 将此线程局部变量的当前线程副本中的值设置为指定值。 */
public void set(T value) {
container.put(Thread.currentThread(), value);
}
}
class Client{
public static void main(String[] args) {
MyThreadLocal<String> mt = new MyThreadLocal<>();
new Thread(new Task(mt)).start();
new Thread(new Task(mt)).start();
new Thread(new Task(mt)).start();
}
}
class Task implements Runnable{
private MyThreadLocal<String> mt;
public Task(MyThreadLocal<String> mt) {
this.mt = mt;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(String.format("当前线程%s,mt中的value=%s", name,mt.get()));
try {
// 随机休眠,如果有线程并发问题.或则 如果这样还是共享的一个变量的话.就能看出来端倪
TimeUnit.MILLISECONDS.sleep((long)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
mt.set(name);
System.out.println(String.format("改变值后:当前线程%s,mt中的value=%s", name,mt.get()));
}
}
某一次运行结果
当前线程Thread-2,mt中的value=null
当前线程Thread-1,mt中的value=null
当前线程Thread-0,mt中的value=null
改变值后:当前线程Thread-0,mt中的value=Thread-0
改变值后:当前线程Thread-2,mt中的value=Thread-2
改变值后:当前线程Thread-1,mt中的value=Thread-1
ThreadLocal 底层用的是 ThreadLocalMap来存储,ThreadLocalMap中用Entry数组来实现的.写了那么多.我也看太懂.总之感觉好强大的样子.
他的使用场景在上面自己模拟实现的时候也看到了. 虽然共享的是一个类,但是他们每个线程存储和读取都是独立的. 那么,就可以把它这个特性用到 对事务的控制,对那一对jdbc代码的开关等的封装.
事务管理
什么是事务
通俗的理解: 一件事情,你要么不做,要么就从头做到尾巴,中途不能退缩. (原子性(Atomicity))
数据库领域: 执行多条sql有关联的语句,要么都成功要么都失败,经典的例子就是:A打款给B,A扣款成功,那么B要么加款成功,要么就加款失败,如果加款失败,A的扣款操作就必须无效. 否则就保证不了一致性. 还有就是隔离性,如果有多个终端在操作同一条数据,那么就要保证竞态条件的安全性,就如多线程一样的道理.所以就有了事务隔离级别(Transaction Isolation Level):
- READ_UNCOMMITTED : 读未提交
- READ_COMMITTED : 读已提交
- REPEATABLE_READ : 可重复读
- SERIALIZABLE : 序列化
百度找的一段描述 :
serializable:
- 这种隔离级别对数据的要求最为严格,自然也是性能最差的一种隔离级别。在所有的select语句中都是默认加了一个lock in share mode的锁,
- 在这种隔离级别中没有一致读的,所有的select都将返回最近的数据状态。
- 由于这种隔离级别的对数据高度一致的严格,所以会产生很多的锁,自然也会导致很多的死锁,对性能的影响不言而喻。
repeatable read:
- 所有的select在第一次一致读以后在事务中都会使用一样的数据状态快照。
- update,delete都会使用间隙锁来保证数据的安全。防止phantom。
- 这是采用最广的事务隔离级别,也是mysql默认的事务隔离级别。
read commited:
- 每一个select都会使用各自的数据状态的快照。
- 如果当前的数据状态已更新到最新,但是当当个select的时候仍然会产生不一致的数据状态。
- 更少的间隙锁意味着更少的死锁。
- 唯一key的检查在第二索引和其它外键检查的时候也会产生间隙所。(gap必须被锁定以防止在parent row被删除后仍在child row中插入相关数据)。
- 这种隔离级别也是使用的非常普遍的隔离级别尤其是在5.1以后的版本中。
- 征对在5.0更早的版本中,可以通过innodb_locks_unsafe_for_binlog移除gap locking。
(In V5.1, most gap-locking is removed w/ this level, but you MUST use row-based logging/replication。)
read uncommitted:
- 这种隔离级别几乎不被使用,在selelct将会看到各种奇怪的数据现象,当然包括其它事务还未提交的数据。
- 强烈不推荐,不能保证数据的一致性。
以上四种隔离级别其实就是解决以下对应的几种并发问题的:
- 脏读: 事务A读取了事务B未提交的数据,并在这个基础上又做了其他的操作.
- 不可重复读: 事务A读取了事务B已提交的更改数据
- 幻读(读已提交):事务A读取了事务B已提交的新增数据.
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ_UNCOMMITTED | √ | √ | √ |
READ_COMMITTED | × | √ | √ |
REPEATABLE_READ | × | × | √ |
SERIALIZABLE | × | × | × |
Mysql的默认级别是:REPEATABLE_READ,原子性是基础,隔离性是手段,持久性是目的.一致性是并发的最终安全目的.
Spring事务的传播行为
其实我有不明白的地方,比如:这些事务,好像和数据库没的事务没有什么关联吧,我觉得只是增强了代码中处理复杂方法的一些手段.
spring的事务传播行为:
- PROPAGATION_REQUIRED–支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
- PROPAGATION_SUPPORTS–支持当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_MANDATORY–支持当前事务,如果当前没有事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW–新建事务,如果当前存在事务,把当前事务挂起。
- PROPAGATION_NOT_SUPPORTED–以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER–以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED–如果没有就新建事务,如果有,就在当前事务中嵌套其他事务,所嵌套的子事务与主事务之间是有关联的(当主事务提交或回滚,子事务也会提交或回滚)
还支持一些附加功能:
- 事务超时(Transaction Timeout): 为了解决事务时间太长,小号太多资源的往年提,所以故意设置一个最大时长,如果超时了,就回滚事务.
- 只读事物(Readonly Transaction) : 为了忽略哪些不需要事务的方法,比如只读取数据.就可以提高一点性能.
至于使用,和配置什么的就不演示了,看了上面的, 我觉得如果要来模拟实现上面的这些特征,简单粗暴的模拟的话,都太困难,只能做到其中的一些, 用aop来做,得最好多逻辑判断呀.也只能想到这么一点了.
事务管理总结思维图
实现事务控制特性
诶。本来很早就能写完的。 后来电脑老蓝屏,10分钟就蓝屏两次,把代码搞丢了。从win10装回win7,环境什么的浪费了好几天。现在都不太记得思路了。 非常不好意思。这章的运行流程什么的。我自己都不太记得起前面章节那么清楚了。 只能写成这样了。只能以后自己做框架的时候 才能真正的理解明白 他这个运行流程了。
实现思路:
- 首先编写Transaction注解类,标识哪些方法是需要事务的。
- 编写事务的切面代理类。我们需要在这个里面处理 哪些方法需要进行事务等操作。
- 让框架支持 这个事务代理类。
- 前一篇讲了 怎么支持AspectProxy普通切面类,这里也一样,让框架支持 事务切面代理类。
- 需要对 目标类 和 增强它的切面实例进行关联。
- 创建代理类,在创建代理类的时候,还是在intercept方法中返回 我们自定义的代理链条。在执行该方法的时候,就会由 代理连框架去 处理调用 切面类。从而完成切面增强操作。
- 最后使用创建好的代理类,替换掉bean容器中的普通代理对象
/** * Created by zhuqiang on 2015/11/2 0002. * 事务注解 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Transaction {
}
/** * @author zhuqiang * @version V1.0 * @date 2015/11/5 20:59 * 事务界面代理类 */
public class TransactionProxy implements Proxy {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(TransactionProxy.class);
private static final ThreadLocal<Boolean> FLAG_HOLDER = new ThreadLocal<Boolean>(){ //用来存标志是否已经开启了事务
@Override
protected Boolean initialValue() {
return false;
}
};
@Override
public Object doProxy(ProxyChain proxyChain) throws Throwable {
Method method = proxyChain.getTargetMethod();
Boolean flag = FLAG_HOLDER.get();
Object result ;
if(!flag && method.isAnnotationPresent(Transaction.class)){ //该方法还没有开启过事务,并且该方法有事务注解
try {
FLAG_HOLDER.set(true);
DataBaseHelper.beginTransaction(); // 我们把jdbc的操作都封装在了 这个类里面。开启事务
LOGGER.debug("begin transaction");
result = proxyChain.doProxyChain();
DataBaseHelper.commitTracsaction(); //提交事务
LOGGER.debug("commit transaction");
}catch (Exception e){
DataBaseHelper.roolbackTransaction(); //r如果在执行目标(代理链条)方法的时候出现了错误,就回滚事务
LOGGER.debug("roolback transaction");
throw e;
}finally {
FLAG_HOLDER.remove();
}
}else{ // 如果没有事务注解,就直接执行代理链
result = proxyChain.doProxyChain();
}
return result;
}
}
// 添加 对该代理的支持,把之前的aopHelper稍微的修改了下
public final class AopHelper {
/** * 获取需要被代理的 所有 class 对象 * @return key= 切面 ,value = 该切面所增强的所有 class 对象 * @throws Exception */
private static Map<Class<?>,Set<Class<?>>> createProxyMap() throws Exception{
Map<Class<?>,Set<Class<?>>> proxyMap = new HashMap<>();
addAspectProxy(proxyMap);
addTransactionProxy(proxyMap);
return proxyMap;
}
/** * 关联普通界面代理类 * @param proxyMap */
private static void addAspectProxy(Map<Class<?>,Set<Class<?>>> proxyMap) throws Exception {
// 获取 所有的切面代理类 的 子类
Set<Class<?>> aspectProxySet = ClassHelper.getClassSetBySuper(AspectProxy.class);
for (Class<?> cls : aspectProxySet) {
if(cls.isAnnotationPresent(Aspect.class)){// 判断该class 是否有 aspect注解(是否是一个切面标注)
Aspect aspect = cls.getAnnotation(Aspect.class); // 获取注解类
Set<Class<?>> targetClassSet = createTargetClassSet(aspect); // 由于 aspect 的值是接收一个 注解类,所以这里是获取到 使用该注解类的所有class
proxyMap.put(cls,targetClassSet);
}
}
}
/** * 关联事务代理 * @param proxyMap * @throws Exception */
private static void addTransactionProxy(Map<Class<?>,Set<Class<?>>> proxyMap) throws Exception {
// 获取 所有的service类,这里是简单的处理,默认就把services注解标识的类。都拿过来换成事务代理对象,然后在代理执行的时候,会根据
//是否有Transaction注解。而去对事务进行操作。
Set<Class<?>> servicesProxySet = ClassHelper.getClassSetByAnnotation(Services.class);
proxyMap.put(TransactionProxy.class,servicesProxySet); //将 切面 与 目标类进行关联
}
}
/** * Created by zhuqiang on 2015/10/17 0017. * 数据库助手类 */
public class DataBaseHelper {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(DataBaseHelper.class);
//用于存放当前线程的 sql 链接对象
private static final ThreadLocal<Connection> CONNECTION_HOLDER = new ThreadLocal<Connection>();
************ 其他代码 请看 git中的。 或则以前的章节
/** 开启事务 */
public static void beginTransaction(){
Connection conn = getConnection();
if(conn != null){
try {
conn.setAutoCommit(false); //关闭自动提交,每次操作数据库都需要手动的提交(也就是开启了事务)
} catch (SQLException e) {
LOGGER.error("begin transaction failure",e);
throw new RuntimeException(e);
}finally {
CONNECTION_HOLDER.set(conn);
}
}
}
/** 提交事务 **/
public static void commitTracsaction(){
Connection conn = getConnection();
if(conn != null){
try {
conn.commit(); //提交事务
conn.close();
} catch (SQLException e) {
LOGGER.error("commit tracsaction failure",e);
throw new RuntimeException(e);
}finally {
CONNECTION_HOLDER.remove(); //移除链接
}
}
}
/** 回滚事务 */
public static void roolbackTransaction(){
Connection conn = getConnection();
if(conn != null){
try {
conn.rollback();
conn.close();
} catch (SQLException e) {
LOGGER.error("roolback transaction failure",e);
throw new RuntimeException(e);
}finally {
CONNECTION_HOLDER.remove();
}
}
}
}
最后整理下思路:
让框架支持aop的流程:
比如,为action层所有方法加上执行方法耗时的功能。
- 创建增强这个功能的切面类
- 创建普通切面注解类,里面接受一个注解类型(比如放入Controller注解,那么就表示,被普通切面注解类标注的切面类,增强的对象就是 所有的被Controller注解标注的对象)
- 在框架初始化的时候需要 框架创建出这些被增强的Controller类的代理对象,并放入bean容器中。(然后再执行 注入处理)
- 在运行的时候,会通过代理类去调用目标方法,而我们的代理类是用代理链机制去处理增强目标对象的处理。
* 然而,框架是怎么实现对aop的支持的呢?:*
- 首先,我们要把框架所管理的类class拿到(ClassHelper)
- 然后实例化这些class。接下来应该就是注入了,但是由于有了aop,下面一步需要把代理对象替换掉bean容器中的普通实例对象(BeanHelper)
- AopHelper出场了,它的最终作用,就是对 被增强的 目标对象建立代理对象。然后替换掉bean容器中的普通对象
- 获取 切面 和 它对应的需要增强的所有的目标 class类。
- 通过第一步,然后反转过来,获取,目标class类 与对应的 切面切面实例进行关联(因为一个目标类可以被多个切面进行增强)【这一部很重要,因为要拿到所有增强这个目标类的切面列表】
- 通过第2步,有了目标class类,也有了切面实例列表,然后 使用cglib创建出代理对象,在代理方法中,把 切面列表交给我们的 代理链类进行处理。
- 对需要依赖的属性,进行注入操作。 (IocHelper)
– 我感觉这个流程其实蛮复杂的。主要那个代理链的处理,如果最开始没有想到的话。那么这一套处理流程的实现 可能都写不出来。