1.代理模式深入学习(一)——动态代理的实现及解析
原文链接:https://blog.csdn.net/wangyy130/article/details/48828595
一、代理模式
分为静态和动态代理。静态代理,我们通常都很熟悉。有一个写好的代理类,实现与要代理的类的一个共同的接口,目的是为了约束也为了安全。
具体不再多说。
这里主要想说的是关于动态代理。我们知道静态代理若想代理多个类,实现扩展功能,那么它必须具有多个代理类分别取代理不同的实现类。
这样做的后果是造成太多的代码冗余。那么我们会思考如果做,才能既满足需求,又没有太多的冗余代码呢?——————动态代理。
它通过在运行时创建代理类,来适应变化。主要用到的是Reflec中的Proxy和InvocationHandler类。
先通过一段代码来理解一下动态代理模式的实现过程:
public class LogHandler implements InvocationHandler {
private Object targetObject; //将要代理的对象保存为成员变量
//将被代理的对象传进来,通过这个方法生成代理对象
public Object newProxyInstance(Object targetObject) {
this.targetObject = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(), this);
}
//代理模式内部要毁掉的方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("start-->>" + method.getName());//方法执行前的操作
for (int i=0; i<args.length; i++) {
System.out.println(args[i]);
}
Object ret = null;
try {
//调用目标方法,如果目标方法有返回值,返回ret,如果没有抛出异常
ret = method.invoke(targetObject, args);
System.out.println("success-->>" + method.getName()); //方法执行后操作
}catch(Exception e) {
e.printStackTrace();
System.out.println("error-->>" + method.getName());//出现异常时的操作
throw e;
}
return ret;
}
}
//客户端调用
public static void main(String[] args) {
LogHandler logHandler = new LogHandler();
UserManager userManager = (UserManager)logHandler.newProxyInstance(new UserManagerImpl());
//userManager.addUser("0001", "张三");
String name = userManager.findUser("0001");
System.out.println("Client.main() --- " + name);
}
代码解析
首先我们要了解一下类的加载机制,在每创建一个Java类时,都会生产一个.class文件,在内存中对应也会生成一个class对象,来表示该类的类型信
息,我们可以用.class来获取这个类的所有信息,也可以通过getClass()方法来读取这个类的所有信息,比如
getClass().getInterfaces()获取类的接口信息等。在Java类加载时,要通过一个类加载器classloader来将生成的Java类加载到JVM
中才能执行。
理解了类的加载机制后,我们再看代码中的newProxyInstance方法,在这个方法中,我们将被代理对象传进来后,通过
Proxy.newProxyInstance这个方法来动态的创建一个被代理类的一个代理类的实例。
在Proxy.newProxyInstance方法中,共有三个参数:
1、targetObject.getClass().getClassLoader()目标对象通过getClass方法获取类的所有信息后,调用getClassLoader()
方法来获取类加载器。获取类加载器后,可以通过这个类型的加载器,在程序运行时,将生成的代理类加载到JVM即Java虚拟机中,以便运行时需要!
2、targetObject.getClass().getInterfaces()获取被代理类的所有接口信息,以便于生成的代理类可以具有代理类接口中的所有方法。
3、this:我们使用动态代理是为了更好的扩展,比如在方法之前做什么,之后做什么等操作。这个时候这些公共的操作可以统一交给代理类去做。
这个时候需要调用实现了InvocationHandler 类的一个回调方法。由于自身变实现了这个方法,所以将this传递过去。
invoke方法的参数
1、Object proxy生成的代理对象,在这里不是特别的理解这个对象,但是个人认为是已经在内存中生成的proxy对象。
2、Method method:被代理的对象中被代理的方法的一个抽象。
3、Object[] args:被代理方法中的参数。这里因为参数个数不定,所以用一个对象数组来表示。
执行过程
在执行过程中,由于被代理的方法可能有返回值,可能直接就是void来表示,那么为了适应于所有方法,所以定义一个返回值,将返回的值
通过ret来接收,当然会抛出异常。ret = method.invoke(targetObject, args);就是调用被代理对象的方法,来执行最原始的方法。在执行完后,
进行额外的处理操作。
以上就是在学习代理模式中自己的一些理解。代理模式是一个很重要的模式,也是一个很实用的模式,利用它可以实现事务的封装,不用我们每次
需要事务操作时,都要进行手动去写,直接调用代理就好了,具体见下篇博客。
2.深入剖析ThreadLocal
原文链接:https://www.cnblogs.com/dolphin0520/p/3920407.html
探讨下ThreadLocal的使用方法和实现原理。首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码分析了其实现原理和使用需要注意的地方,最后给出了两个应用场景。
以下是本文目录大纲:
一.对ThreadLocal的理解
二.深入解析ThreadLocal类
三.ThreadLocal的应用场景
若有不正之处请多多谅解,并欢迎批评指正。
请尊重作者劳动成果,转载请标明原文链接:
http://www.cnblogs.com/dolphin0520/p/3920407.html
一.对ThreadLocal的理解
ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
这句话从字面上看起来很容易理解,但是真正理解并不是那么容易。
我们还是先来看一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
假设有这样一个数据库链接管理类,这段代码在单线程中使用是没有任何问题的,但是如果在多线程中使用呢?很显然,在多线程中使用会存在线程安全问题:第一,这里面的2个方法都没有进行同步,很可能在openConnection方法中会多次创建connect;第二,由于connect是共享变量,那么必然在调用connect的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作,而另外一个线程调用closeConnection关闭链接。
所以出于线程安全的考虑,必须将这段代码的两个方法进行同步处理,并且在调用connect的地方需要进行同步处理。
这样将会大大影响程序执行效率,因为一个线程在使用connect进行数据库操作的时候,其他线程只有等待。
那么大家来仔细分析一下这个问题,这地方到底需不需要将connect变量进行共享?事实上,是不需要的。假如每个线程中都有一个connect变量,各个线程之间对connect变量的访问实际上是没有依赖关系的,即一个线程不需要关心其他线程是否对这个connect进行了修改的。
到这里,可能会有朋友想到,既然不需要在线程之间共享这个变量,可以直接这样处理,在每个需要使用数据库连接的方法中具体使用时才创建数据库链接,然后在方法调用完毕再释放这个连接。比如下面这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
这样处理确实也没有任何问题,由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不尽严重影响程序执行效率,还可能导致服务器压力巨大。
那么这种情况下使用ThreadLocal是再适合不过的了,因为ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。
但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。
二.深入解析ThreadLocal类
在上面谈到了对ThreadLocal的一些理解,那我们下面来看一下具体ThreadLocal是如何实现的。
先了解一下ThreadLocal类提供的几个方法:
1 2 3 4 |
|
get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,下面会详细说明。
首先我们来看一下ThreadLocal类是如何为每个线程创建一个变量的副本的。
先看下get方法的实现:
第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是 this,而不是当前线程t。
如果获取成功,则返回value值。
如果map为空,则调用setInitialValue方法返回value。
我们上面的每一句来仔细分析:
首先看一下getMap方法中做了什么:
可能大家没有想到的是,在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。
那么我们继续取Thread类中取看一下成员变量threadLocals是什么:
实际上就是一个ThreadLocalMap,这个类型是ThreadLocal类的一个内部类,我们继续取看ThreadLocalMap的实现:
可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。
然后再继续看setInitialValue方法的具体实现:
很容易了解,就是如果map不为空,就设置键值对,为空,再创建Map,看一下createMap的实现:
至此,可能大部分朋友已经明白了ThreadLocal是如何为每个线程创建变量的副本的:
首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
下面通过一个例子来证明通过ThreadLocal能达到在每个线程中创建变量副本的效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
这段代码的输出结果为:
从这段代码的输出结果可以看出,在main线程中和thread1线程中,longLocal保存的副本值和stringLocal保存的副本值都不一样。最后一次在main线程再次打印副本值是为了证明在main线程中和thread1线程中的副本值确实是不同的。
总结一下:
1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;
3)在进行get之前,必须先set,否则会报空指针异常;
如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。
因为在上面的代码分析过程中,我们发现如果没有先set的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。
看下面这个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
在main线程中,没有先set,直接get的话,运行时会报空指针异常。
但是如果改成下面这段代码,即重写了initialValue方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
|
就可以直接不用先set而直接调用get了。
三.ThreadLocal的应用场景
最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。
如:
1 2 3 4 5 6 7 8 9 10 |
|
下面这段代码摘自:
http://www.iteye.com/topic/103804
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
3.为避免多线程Connection混乱使用ThreadLocal来封装事务
原文链接:https://blog.csdn.net/wangyy130/article/details/48952371
上一篇博客总结了动态代理的使用及代码的含义。接下来,继续探究动态代理的实地应用——利用动态代理来封装事务。
首先,要先来回忆一下最原始的封装好的事务的代码,这里在连接数据库时用到了TheadLocal这个类,通过它可以来保证在执行业务逻辑过程中来
保证每一次使用的connection的连接对象都执行的是同一个线程内的connection。
事务的封装
/**
* 采用ThreadLocal封装Connection
* @author yanyan
*
*/
public class ConnectionManager {
//声明一个Connection类型的ThreadLocal
private static ThreadLocal<Connection> connectionHolder=new ThreadLocal<Connection>();
public static Connection getConnection(){
//通过TheadLocal的get方法来获取当前线程连接对象的实例
Connection conn=connectionHolder.get();
if(conn==null){
try{
//获取数据库驱动
JdbcConfig jdbcConfig=XmlConfigReader.getInstance().getJdbcConfig();
Class.forName(jdbcConfig.getDriverName());
conn=DriverManager.getConnection(jdbcConfig.getUrl(),jdbcConfig.getUserName(),jdbcConfig.getPassword());
//设置数据库连接对象
connectionHolder.set(conn);
}catch (Exception e) {
throw new ApplicationException("系统错误,请联系管理员!");
}
}
return conn;
}
//关闭数据库连接
public static void closeConnection(){
Connection conn=connectionHolder.get();
if(conn!=null){
try{
conn.close();
//从集合中清除
connectionHolder.remove();
}catch(SQLException e){
e.printStackTrace();
}
}
}
/**
* 手动提交事务
* @param conn
*/
public static void beginTransaction(Connection conn){
try{
if(conn !=null){
if(conn.getAutoCommit()){
conn.setAutoCommit(false);
}
}
}catch(SQLException e){}
}
/**
*
* 提交事务
* @param conn
*/
public static void commitTransaction(Connection conn){
try{
if(conn !=null){
if(!conn.getAutoCommit()){
conn.commit();
}
}
}catch(SQLException e){}
}
/**
* 回滚事务
* @param conn
*/
public static void rollbackTransaction(Connection conn){
try{
if(conn!=null){
if(!conn.getAutoCommit()){
conn.rollback();
}
}
}catch(SQLException e){}
}
/**
* 重置connection状态
* @param conn
*/
public static void resetConnection(Connection conn){
try{
if(conn!=null){
if(conn.getAutoCommit()){
conn.setAutoCommit(false);
}else{
conn.setAutoCommit(true);
}
}
}catch(SQLException e){}
}
//关闭preparedStatement
public static void close(Statement pstmt){
if(pstmt !=null){
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//关闭ResultSet
public static void close(ResultSet rs){
if(rs !=null){
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
通过利用ThreadLocal这个类来封装Connection,这样在业务逻辑层获取connection的时候,就会避免了很多麻烦。在遇到多重业务逻辑调用
多种方法时,调用到的connection会在当前线程中查找获取到connection这个对象进行数据库操作。
接下来,就该介绍如何利用动态代理来封装事务了,请见下篇博客!
4.代理模式深入学习(二)——实现动态代理对事务的封装
原文链接:https://blog.csdn.net/wangyy130/article/details/49228495
从动态代理的实现衍生原理到threadLocal来封装事务,到最后真正的利用动态代理来封装事务。缺少每一
步都似乎显得有些冒进了!现在剩下的就只是把先前封装好的事务加进到写好的动态代理类中就好了!
动态代理与事务结合
package com.bjpowernode.drp.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
/**
* 事务的封装,利用动态代理封装事务
* @author yanyan
*
*/
public class TransactionHandler implements InvocationHandler {
private Object targetObject;
/**
* 将要代理的类传递给该类,同时在该类中保存这个类的对象
* @param targetObject
* @return
*/
public Object newProxyInstance(Object targetObject){
this.targetObject=targetObject;
/**
* 1/targetObject:要代理的类的对象
* 2/代理的类所实现的接口,便于生成的代理类去实现这个接口
* 3/每个生成的代理类要回调一个实现了InvocationHandler的内部函数,这个函数放在这个类里面就是invoke函数,为了适应多种类,要写活
*/
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Connection conn=null;
Object ret=null; //定义代理的方法返回对象
try{
//从ThreadLocal中取得connection
conn=ConnectionManager.getConnection();//打开连接
if(method.getName().startsWith("add") || method.getName().startsWith("del") || method.getName().startsWith("modify")){
//手动控制事务提交
ConnectionManager.beginTransaction(conn);
}
//调用目标对象的业务逻辑方法
ret=method.invoke(targetObject, args);
if(!conn.getAutoCommit()){
//提交事务
ConnectionManager.commitTransaction(conn);
}
}catch(ApplicationException e){
//回滚事务
ConnectionManager.rollbackTransaction(conn);
throw e;
}catch(Exception e){
e.printStackTrace();
if(e instanceof InvocationTargetException){
InvocationTargetException ete=(InvocationTargetException)e;
throw ete.getTargetException();
}
//回滚事务
ConnectionManager.rollbackTransaction(conn);
throw new ApplicationException("操作失败!");
}finally{
ConnectionManager.closeConnection();
}
return ret;
}
}
将动态代理封装的事务嵌进原始调用对象中
package com.bjpowernode.drp.util;
import java.util.HashMap;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.bjpowernode.drp.flowcard.manager.FlowCardManager;
/**
* 抽象工厂,主要创建两个系列的产品
* 1、manager系列
* 2、dao系列
* @author yanyan
*
*/
public class BeanFactory {
private final String beansConfigFile="beans-config.xml";
private Document doc;
private static BeanFactory instance=new BeanFactory();
private Map serviceMap=new HashMap();
private Map daoMap=new HashMap();
private BeanFactory(){
//装载配置文件
try {
doc=new SAXReader().read(Thread.currentThread().getContextClassLoader().getResourceAsStream(beansConfigFile));
} catch (DocumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new RuntimeException();
}
}
public static BeanFactory getInstance(){
return instance;
}
/**
* 根据产品编号取得service系列产品
* @param beanId
* @return
*/
public synchronized Object getServiceObject(Class c){
//首先判断是否存在该对象
if(serviceMap.containsKey(c.getName())){
return serviceMap.get(c.getName());
}
//如果不存在,则创建一个
// //代表以bean路径结尾的所有元素,用“//” 表示所有路径以"//"后指定的子路径结尾的元素
Element beanElt=(Element) doc.selectSingleNode("//service[@id=\""+c.getName()+"\"]");
/*String className=beanElt.getTextTrim();*/
String className=beanElt.attributeValue("class");
Object service=null;
try {
service=Class.forName(className).newInstance();
TransactionHandler transactionHandler=new TransactionHandler();
service=transactionHandler.newProxyInstance(service);
//将创建好的对象放到map中
serviceMap.put(c.getName(), service);
} catch (Exception e) {
throw new RuntimeException(e);
}
return service;
}
/**
* 根据产品编号取得dao系列产品
* @param beanId
* @return
*/
public synchronized Object getDaoObject(Class c){
//首先判断是否存在该对象
if(daoMap.containsKey(c.getName())){
return daoMap.get(c.getName());
}
//如果不存在,则创建一个
// //代表以bean路径结尾的所有元素,用“//” 表示所有路径以"//"后指定的子路径结尾的元素
Element beanElt=(Element) doc.selectSingleNode("//dao[@id=\""+c.getName()+"\"]");
/*String className=beanElt.getTextTrim();*/
String className=beanElt.attributeValue("class");
Object dao=null;
try {
dao=Class.forName(className).newInstance();
TransactionHandler transactionHandler=new TransactionHandler();
dao=transactionHandler.newProxyInstance(dao);
//将创建好的对象放到map中
daoMap.put(c.getName(), dao);
} catch (Exception e) {
throw new RuntimeException("创建失败");
}
return dao;
}
}
调用
private FlowCardDao flowCardDao;
//这里通过构造函数调用的beanFactory实现代码中已经启用了动态代理封装的事务
this.flowCardDao=(FlowCardDao)BeanFactory.getInstance().getDaoObject(FlowCardDao.class);
}
public void addFlowCard(FlowCard flowCard) throws ApplicationException {
//Connection conn=null;
try{
//conn=ConnectionManager.getConnection();
//开始事务
//ConnectionManager.beginTransaction(conn);
//生成流向单单号
String flowCardVouNo=flowCardDao.generateVouNo();
//添加流向单主信息
flowCardDao.addFlowCardMaster(flowCardVouNo, flowCard);
//添加明细信息
flowCardDao.addFlowCardDetail(flowCardVouNo, flowCard.getFlowCardDetailList());
//提交事务
//ConnectionManager.commitTransaction(conn);
}catch(DaoException e){
//回滚事务
//ConnectionManager.rollbackTransaction(conn);
throw new ApplicationException(e);
}/*finally{
ConnectionManager.closeConnection();
}
*/
}
从注释的代码中可以看到,我们利用动态代理封装的事务可以大大减少代码的重复。