通过一个工具类更深入理解动态代理和Threadlocal
动态代理和Threadlocal
一个代理类返回指定的接口,将方法调用指定的调用处理程序的代理类的实例。返回的是一个代理类,由指定的类装载器的定义和实现指定接口指定代理实例调用处理程序最近用到一个工具类,里面用到两个动态代理,以前一个动态代理还是用过,上两个,看来就必要好好研究一把了,这是一个连接数据源的工具类,用到动态代理,主要是为了为了更好的实现service和dao的解耦,同时也避免了一些冗余的代码,这个工具类的作用主要是在service层中一些方法可能用到事务,一些方法可能不用到事务,但是它们都要与数据源连接,传统的做法就是要用到事务管理的时候,就用QueryRunner让事务去连接,因为在事务管理中,涉及到并发和同时处理多个事件,那么每次都要为其单独写一个事务连接,那么有没有一种更好的方法,即能实现开启事务时,也能用,普通的不开启事务时,也能用用到动态代理,这是一种通过一个代理对象的方式,当代理对象被Invocation时,在它动态代理的invoke方法中,我们再对其判断进行处理,通过该方法是带有标记的具有事务管理的。我们就让其开启事务,并在此用事务去单独连接数据源。并在结束后,作回滚操作。而另一方面,我们对不开启事务的方法,就按正常的方式DataSource中的source连接数据源
这样的话就可以两全其美,我重点想说的是,两个动态代理同时运行时,执行过程是怎样的?
第二个代理对象是Connection,因为每次在事务管理中用Threadlocal(线程本地变量)。ThreadLocal多用在多线程的并发问题,对于同一个静态,多个线程并发访问数据,在连接数据库时,须要考虑到同一时刻,多个用户进来进行连接,我们用threadlocal就可以很方便的解决了不用等到这个用户用完下一个用户再来用连接的问题,我们知道,线程是程序内部处理事务的流程,每个线程里都有一个map对象,打个比方,如果说线程是一条河流里的水,threadlocal就是一个载着信息的小船,每当有用户来访问连接时,就给用户开启一条小船,带着它所请求的信息,到达想要去的地方,而每一个threadlocal都可以同时在河流上开启。。这样的话,就能为不同的用户传递不同的信息,就能保证每个线程都是用的都是自己的变量。我们知道,一般都是请求一次连接,然后再把这个连接给关上,然而,在事务阶段,当一个连接被打开后,可能还有下一条事务要进行处理,如果你把它关了话,
就会进行事务回滚,达不到我们所想要的目的。所以第二个动态代理,主要是用来管理代理对象Connection中的close方法。。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class DaoUtils {
private static DataSource source=new ComboPooledDataSource();
private DaoUtils(){
}
//普通情况
/*
public static DataSource getSource(){
return source;
}*/
/**
* 改造此方法,在调用这个方法时检查,当前线程是否开启过事务
* 如果没有开启事务,返回的是最普通的数据源
* 如果开启过过事务,则返回改造过的数据源--改造底层获取连接的getConnection
*/
public static DataSource getSource(){
if(TransactionManager.hasStarTran()){
//开启过事务,返回一个改造getConnection--每次返回都开启了事务的的连接,此方法每次都返回,当前线程
DataSource proxy= (DataSource) Proxy.newProxyInstance(source.getClass().getClassLoader(),
source.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//if("getconnction".equals(method.getName())){
if("getConnection".equals(method.getName())){
//当前调用getConnection方法,使它每次都返回当前的线程变量中保存当前线程中使用开启事务的连接
final Connection conn=TransactionManager.getconn();
Connection proxy2= (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(),
conn.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("close".equals(method.getName())){
return null;//什么也不做
}else{
return method.invoke(conn, args) ;
}
}
});
return proxy2;
}else{
return method.invoke(source, args);
}
}
});
return proxy;
}else{//如果没有开启事务,就返回
return source;
}
}
public static Connection conn(){
try {
return source.getConnection();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
事务的工具类:
import java.sql.Connection;
import javax.sql.DataSource;
import org.apache.commons.dbutils.DbUtils;
public class TransactionManager {
/*因为存在多个线程共用一个连接,一个ThreadLocal代表一个变量,
故其中里只能放一个数据,你有两个变量都要线程范围内共享,
则要定义两个ThreadLocal对象。如果有一个百个变量要线程共享呢?
那请先定义一个对象来装这一百个变量,然后在ThreadLocal中存储这一个对象。*/
/*private static ThreadLocal<Connection> conn_local=new ThreadLocal<Connection>(){
protected Connection initialValue() {
return DaoUtils.conn();//连接数据库
}
};*/
private static ThreadLocal<Connection> conn_local=new ThreadLocal<Connection>(){
@Override
protected Connection initialValue() {
return DaoUtils.conn();
};
};
//判断是否开启事务
private static ThreadLocal<Boolean> hasStarTran_local=new ThreadLocal<Boolean>(){
@Override
protected Boolean initialValue() {
return false;
};
};
private TransactionManager(){
}
//开启事务
public static void startTran(){
try {
conn_local.get().setAutoCommit(false);
//开启事务
hasStarTran_local.set(true);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
//判断是否开启事务
public static boolean hasStarTran(){
return hasStarTran_local.get();
}
//提交事务
public static void commit(){
try {
conn_local.get().commit();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
//回滚事务
public static void rollback(){
try {
conn_local.get().rollback();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static Connection getconn(){
return conn_local.get();
}
//释放连接
public static void release(){
DbUtils.closeQuietly(conn_local.get());
conn_local.remove();
hasStarTran_local.remove();
}
}
最简单的动态代理运用
public class MyInvocationHandler implements InvocationHandler {
// 目标对象
private Object target;
/**
* 构造方法
* @param target 目标对象
*/
public MyInvocationHandler(Object target) {
super();
this.target = target;
}
/**
* 执行目标对象的方法
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在目标对象的方法执行之前
System.out.println("------------------before------------------");
// 执行目标对象的方法
Object result = method.invoke(target, args);
// 在目标对象的方法执行之后
System.out.println("-------------------after------------------");
return result;
}
/**
* 获取目标对象的代理对象
* @return 代理对象
*/
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
target.getClass().getInterfaces(), this);
}
}
下面是动态代理的源码:类加载器,一大堆接口,还有就是代理的实现类
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {
throw new NullPointerException();
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, interfaces); // stack walk magic: do not refactor
/*
* 调用它的构造函数指定调用处理程序。
*/
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
SecurityManager sm = System.getSecurityManager();
if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
// create proxy instance with doPrivilege as the proxy class may
// implement non-public interfaces that requires a special permission
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
return newInstance(cons, ih);
}
});
} else {
return newInstance(cons, ih);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
}
}
private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
try {
return cons.newInstance(new Object[] {h} );
} catch (IllegalAccessException | InstantiationException e) {
throw new InternalError(e.toString());
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString());
}
}
}
/**
* Returns true if and only if the specified class was dynamically
* generated to be a proxy class using the {@code getProxyClass}
* method or the {@code newProxyInstance} method.
*
* <p>The reliability of this method is important for the ability
* to use it to make security decisions, so its implementation should
* not just test if the class in question extends {@code Proxy}.
*
* @param cl the class to test
* @return {@code true} if the class is a proxy class and
* {@code false} otherwise
* @throws NullPointerException if {@code cl} is {@code null}
*/
public static boolean isProxyClass(Class<?> cl) {
if (cl == null) {
throw new NullPointerException();
}
return proxyClasses.containsKey(cl);
}
/**
* Returns the invocation handler for the specified proxy instance.
*
* @param proxy the proxy instance to return the invocation handler for
* @return the invocation handler for the proxy instance
* @throws IllegalArgumentException if the argument is not a
* proxy instance
*/
public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException
{
/*
* Verify that the object is actually a proxy instance.
*/
if (!isProxyClass(proxy.getClass())) {
throw new IllegalArgumentException("not a proxy instance");
}
Proxy p = (Proxy) proxy;
return p.h;
}
进去ProxyGenerator类的静态方法generateProxyClass,这里是真正生成代理类class字节码的地方。
你可以去你的web-info目录下的classes中看到用到动态代理的类,都会多出一个字节码文件。带$的,
可以用ju-gui(反编译工具)看到,里面就是一个代理对象。
public static byte[] generateProxyClass(final String name,
Class[] interfaces)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces);
final byte[] classFile = gen.generateClassFile();
// 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
FileOutputStream file =
new FileOutputStream(dotToSlash(name) + ".class");
file.write(classFile);
file.close();
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
// 返回代理类的字节码
return classFile;
}
通过一个工具类更深入理解动态代理和Threadlocal的更多相关文章
- 用Java开发一个工具类,提供似于js中eval函数功能的eval方法
今天在看到<Java疯狂讲义>中一个章节习题: 开发一个工具类,该工具类提供一个eval()方法,实现JavaScript中eval()函数的功能--可以动态运行一行或多行程序代码.例如: ...
- java中使用反射做一个工具类,来为指定类中的成员变量进行赋值操作,使用与多个类对象的成员变量的赋值。
//------------------------------------------------我是代码的分割线 // 首选是一个工具类,在该工具类里面,定义了一个方法,public void s ...
- Java三种代理模式:静态代理、动态代理和cglib代理
一.代理模式介绍 代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能. 简言之,代理模式就是 ...
- java的静态代理、jdk动态代理和cglib动态代理
Java的代理就是客户端不再直接和委托类打交道,而是通过一个中间层来访问,这个中间层就是代理.使用代理有两个好处,一是可以隐藏委托类的实现:二是可以实现客户与委托类之间的解耦,在不修改委托类代码的情况 ...
- JAVA高级架构师基础功:Spring中AOP的两种代理方式:动态代理和CGLIB详解
在spring框架中使用了两种代理方式: 1.JDK自带的动态代理. 2.Spring框架自己提供的CGLIB的方式. 这两种也是Spring框架核心AOP的基础. 在详细讲解上述提到的动态代理和CG ...
- Java动态代理和CGLib代理
本文参考 在上一篇"Netty + Spring + ZooKeeper搭建轻量级RPC框架"文章中涉及到了Java动态代理和CGLib代理,在这篇文章中对这两种代理方式做详解 下 ...
- JDK动态代理和CGLib动态代理简单演示
JDK1.3之后,Java提供了动态代理的技术,允许开发者在运行期间创建接口的代理实例. 一.首先我们进行JDK动态代理的演示. 现在我们有一个简单的业务接口Saying,如下: package te ...
- SpringAOP-JDK 动态代理和 CGLIB 代理
在 Spring 中 AOP 代理使用 JDK 动态代理和 CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类. 1.JDK 动态代理 那么接 ...
- 静态代理、动态代理和cglib代理
转:https://www.cnblogs.com/cenyu/p/6289209.html 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处 ...
随机推荐
- windows的常用快捷键(实用篇)
整理一下windows的常用快捷键,有些快捷键老不用都忘记了,这里整理一下方便自己以后忘记时翻阅. 一.Fn键的使用 1.F1帮助 2.F2重命名 3.F3打开搜索 4.F4打开地址栏常用地址 5.F ...
- SpringMVC 教程 - URI 链接
原文链接:https://www.codemore.top/cates/Backend/post/2018-04-22/spring-mvc-uri-links 这一节主要讲的是Spring Fram ...
- 关于thymeleaf th:replace th:include th:insert 的区别
关于thymeleaf th:replace th:include th:insert 的区别 th:insert :保留自己的主标签,保留th:fragment的主标签. th:re ...
- mybatis choose标签的使用
MyBatis 提供了 choose 元素.if标签是与(and)的关系,而 choose 是或(or)的关系. choose标签是按顺序判断其内部when标签中的test条件出否成立,如果有一个成立 ...
- JVM体系结构-----深入理解内存结构
一.概述 内存在计算机中占据着至关重要的地位,任何运行时的程序或者数据都需要依靠内存作为存储介质,否则程序将无法正常运行.与C和C++相比,使用Java语言编写的程序并不需要显示的为每一个对象编写对应 ...
- 女儿开始bababababa的发声了
女儿八个半月,开始bababababa的发声了,而不是像以前总啊啊啊的.
- Go 处理yaml类型的配置文件
先说一下,这里用到了很多关于反射类型的功能,可能刚开始看代码,如果对反射不熟悉的可能会不是非常清晰,但是同时也是为了更好的理解golang中的反射,同时如果后面想在代码中可以直接从我的git地址get ...
- jsp&servlet初体验——用户登录功能实现
数据库准备-创建db_login数据库 t_user表 1.创建web工程 2.创建用户model user.java package com.gxy.model; public class U ...
- Windows环境下,从零开始搭建Nodejs+Express+Ejs框架(一)---安装nodejs
第一步,安装nodejs https://nodejs.org/en/download/ 这个是nodejs的官网,由于操作系统是win7 64位的,所以,我下载的是node-v8.11.1-x64的 ...
- jQuery 效果 – 隐藏和显示
在 jQuery 中可以使用 hide() 和 show() 方法来隐藏和显示 HTML 元素,以及使用 toggle() 方法能够切换 hide() 和 show() 方法. 隐藏.显示.切换,滑动 ...