`
收藏列表
标题 标签 来源
CGLIB简介 转 http://bolan392.iteye.com/blog/1436337
CGLIB简介 
博客分类: CGLIB 
 
CGlib是什么? 
CGlib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。 
当然这些实际的功能是asm所提供的,asm又是什么?Java字节码操控框架,具体是什么大家可以上网查一查,毕竟我们这里所要讨论的是cglib, 
cglib就是封装了asm,简化了asm的操作,实现了在运行期动态生成新的class。 
可能大家还感觉不到它的强大,现在就告诉你。 
实际上CGlib为spring aop提供了底层的一种实现;为hibernate使用cglib动态生成VO/PO (接口层对象)。 

下面我们将通过一个具体的事例来看一下CGlib体验一下CGlib。 
* CGlib 2.13 
* ASM 2.23 
以一个实例在简单介绍下cglib的应用。 
我们模拟一个虚拟的场景,模拟对表的操作。 
1. 开始我们对表提供了CRUD方法。 
我们现在创建一个对Table操作的DAO类。 


Java代码   
public class TableDAO {   
    public void create(){   
        System.out.println("create() is running !");   
    }   
    public void query(){   
        System.out.println("query() is running !");   
    }   
    public void update(){   
        System.out.println("update() is running !");   
    }   
    public void delete(){   
        System.out.println("delete() is running !");   
    }   
}   


OK,它就是一个javaBean,提供了CRUD方法的javaBean。 
下面我们创建一个DAO工厂,用来生成DAO实例。 


Java代码   
public class TableDAOFactory {   
    private static TableDAO tDao = new TableDAO();   
    public static TableDAO getInstance(){   
        return tDao;   
    }   
}   


接下来我们创建客户端,用来调用CRUD方法。 


Java代码   
public class Client {   
   
    public static void main(String[] args) {   
        TableDAO tableDao = TableDAOFactory.getInstance();   
        doMethod(tableDao);   
    }   
    public static void doMethod(TableDAO dao){   
        dao.create();   
        dao.query();   
        dao.update();   
        dao.delete();   
    }   
}   


OK,完成了,CRUD方法完全被调用了。当然这里并没有CGlib的任何内容。问题不会这么简单的就结束,新的需求来临了。 
2. 变化随之而来,Boss告诉我们这些方法不能开放给用户,只有“张三”才有权使用。阿~!怎么办,难道我们要在每个方法上面进行判断吗? 
好像这么做也太那啥了吧,对了对了Proxy可能是最好的解决办法。jdk的代理就可以解决了。 好了我们来动手改造吧。等等jdk的代理需要实现接口,这样, 
我们的dao类需要改变了。既然不想改动dao又要使用代理,我们这就请出CGlib。 
我们只需新增一个权限验证的方法拦截器。 


Java代码   
public class AuthProxy implements MethodInterceptor {   
    private String name ;   
    //传入用户名称   
    public AuthProxy(String name){   
        this.name = name;   
    }   
    public Object intercept(Object arg0, Method arg1, Object[] arg2,   
            MethodProxy arg3) throws Throwable {   
        //用户进行判断   
        if(!"张三".equals(name)){   
            System.out.println("你没有权限!");   
            return null;   
        }   
        return arg3.invokeSuper(arg0, arg2);   
    }   
}   


当然不能忘了对我们的dao工厂进行修改,我们提供一个使用代理的实例生成方法 


Java代码   
public static TableDAO getAuthInstance(AuthProxy authProxy){   
    Enhancer en = new Enhancer();   
    //进行代理   
    en.setSuperclass(TableDAO.class);   
    en.setCallback(authProxy);   
    //生成代理实例   
    return (TableDAO)en.create();   
}   


我们这就可以看看客户端的实现了。添加了两个方法用来验证不同用户的权限。 


Java代码   
public static void haveAuth(){   
    TableDAO tDao = TableDAOFactory.getAuthInstance(new AuthProxy("张三"));   
    doMethod(tDao);   
}   
public static void haveNoAuth(){   
    TableDAO tDao = TableDAOFactory.getAuthInstance(new AuthProxy("李四"));   
    doMethod(tDao);   
}   


OK,"张三"的正常执行,"李四"的没有执行。 
看到了吗?简单的aop就这样实现了 
难道就这样结束了么? 
3. Boss又来训话了,不行不行,现在除了"张三"其他人都用不了了,现在不可以这样。他们都来向我反映了,必须使用开放查询功能。 
哈哈,现在可难不倒我们了,因为我们使用了CGlib。当然最简单的方式是去修改我们的方法拦截器,不过这样会使逻辑变得复杂,且 
不利于维护。还好CGlib给我们提供了方法过滤器(CallbackFilter),CallbackFilte可以明确表明,被代理的类中不同的方法, 
被哪个拦截器所拦截。下面我们就来做个过滤器用来过滤query方法。 


Java代码   
public class AuthProxyFilter implements CallbackFilter{   
    public int accept(Method arg0) {   
        if(!"query".equalsIgnoreCase(arg0.getName()))   
            return 0;   
        return 1;   
    }   
   
}   


OK,可能大家会对return 0 or 1感到困惑,用到的时候就会讲解,当然下面就会用到了。 
我们在工场中新增一个使用了过滤器的实例生成方法。 


Java代码   
public static TableDAO getAuthInstanceByFilter(AuthProxy authProxy){   
    Enhancer en = new Enhancer();   
    en.setSuperclass(TableDAO.class);   
    en.setCallbacks(new Callback[]{authProxy,NoOp.INSTANCE});   
    en.setCallbackFilter(new AuthProxyFilter());   
    return (TableDAO)en.create();   
}   


看到了吗setCallbacks中定义了所使用的拦截器,其中NoOp.INSTANCE是CGlib所提供的实际是一个没有任何操作的拦截器, 
他们是有序的。一定要和CallbackFilter里面的顺序一致。明白了吗?上面return返回的就是返回的顺序。也就是说如果调用query方法就使用NoOp.INSTANCE进行拦截。 
现在看一下客户端代码。 


Java代码   
public static void haveAuthByFilter(){   
    TableDAO tDao = TableDAOFactory.getAuthInstanceByFilter(new AuthProxy("张三"));   
    doMethod(tDao);   
   
    tDao = TableDAOFactory.getAuthInstanceByFilter(new AuthProxy("李四"));   
    doMethod(tDao);   
}   


ok,现在"李四"也可以使用query方法了,其他方法仍然没有权限。 
哈哈,当然这个代理的实现没有任何侵入性,无需强制让dao去实现接口。 

hibernate延迟加载的原理与实现 转 http://superleo.iteye.com/blog/243322
hibernate延迟加载的原理与实现 转
大概一年半前,我开始了Java之旅。上来就是spring MVC + hibernate3 + spring的架构让我最头痛的就是hiberante3。后来项目因为数据量大,被迫改成了JDBC。现在回想当初那个hibernate3使用的相当菜了(现在似乎也算刚刚入门),而且对很多hibernate的概念原理懵懵懂懂,用的不好,招来老板对hibernate的一顿质疑。一年半后的今天。当我再次看待hibernate时,除了当年隐隐约约的“委屈”涌上心头,更希望让大家了解hibernate,不要仅仅从应用角度出发。好了,咱们今天来看看hibernate关于延迟加载的原理与实现。主要使用的就是CGLib。

 

 

首先看一段熟悉的代码:

 

Java代码  
public void testLazy() {   
    // 自己弄了一个丑陋的sessionFactory和session,主要是因为自己写的,比较容易控制。   
    SessionFactory<User, String> sessionFactory = new SessionFactoryImpl<User, String>(   
            User.class);   
    Session<User, String> session = sessionFactory.openSession();   
    User u = session.load("1");   
    // 这一句不会触发数据库查询操作,请看图1   
    assertEquals("1", u.getId());   
    // 访问的是非主键属性,开始查询数据库,请看图2   
    assertNotSame("11", u.getName());   
    session.close();   
}  

	public void testLazy() {
		// 自己弄了一个丑陋的sessionFactory和session,主要是因为自己写的,比较容易控制。
		SessionFactory<User, String> sessionFactory = new SessionFactoryImpl<User, String>(
				User.class);
		Session<User, String> session = sessionFactory.openSession();
		User u = session.load("1");
		// 这一句不会触发数据库查询操作,请看图1
		assertEquals("1", u.getId());
		// 访问的是非主键属性,开始查询数据库,请看图2
		assertNotSame("11", u.getName());
		session.close();
	} 

 图1:通过断点,我们可以看到User对象只是一个代理,并且只有主键id有值

 



 

图2:通过断点,我们可以看到原本属于代理对象的User,其targetObject一项已经有值了,表示已经发出select语句从数据库取值了。



 

 

好,有了这点感性认识,咱们继续前进。

 

原理:在hibernate中,如果使用了延迟加载(比如常见的load方法),那么除访问主键以外的其它属性时,就会去访问数据库(假设不考虑hibernate的一级缓存),此时session是不允许被关闭。 

 

先简单看看要操作的对象User

 

Java代码  
@Entity  
public class User{   
    @Id  
    private String id;   
  
    @Column  
    private String name;   
  
    ........set,get省略   
}  

@Entity
public class User{
	@Id
	private String id;

	@Column
	private String name;

	........set,get省略
}
 

 这些@Entity,@Id,@Column也是我写的一些标注,让大家感觉更贴近hibernate(或jpa)些所做的一些模拟。所有的标注都是空实现,比如说@Id

 

Java代码  
@Retention(RetentionPolicy.RUNTIME)   
@Target(ElementType.FIELD)   
public @interface Id {   
  
}  

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Id {

}
 

这些标注在后面的反射操作中会用到。

 

 

好现在我们从session.load方法慢慢深入

 

Java代码  
public T load(PK id) {   
        // annotationParas利用反射解析被标注为@Entity的type类型(比如说上文提到的User.class),   
        // 然后将标注为@Id和@Column的属性存入FieldClass对象,供下面进一步使用   
        final FieldClass fieldClass = annotationParas.generatorSQL(type);   
        T obj = null;   
        // 因为是load方法,默认给它加一个基于CGLib的拦截器,该拦截器是实现延迟加载的关键,稍后我们再详细看看   
        LazyInitializer<T, PK> interceptor = new LazyInitializerImpl<T, PK>();   
        // 将当前的session对象设置给该拦截器,以便在取非主键属性时,能够正常查询数据库   
        // 从而将对象初始化   
        interceptor.setSession(this);   
  
        // 默认生成的是一个基于CGLib的代理,并非真实的对象,通过图1,图2,大家应该可以看到   
        // User=User$$EnhancerByCGLib$$... 我就不多说了   
        Enhancer enhancer = new Enhancer();   
        enhancer.setSuperclass(type);   
        // 注意别忘记将刚才生成的拦截器注入到代理中去   
        enhancer.setCallback(interceptor);   
        obj = (T) enhancer.create();   
  
        try {   
            // 因为通过CGLib生成的User对象,主键属性id=null   
            // 所以我们还得执行主键的set方法(比如说setId),这样就可以像图1显示那样,id="1"是有值的   
            // 到此,load方法执行完毕,始终没有查询数据库   
            Method method = type.getMethod(getMethodFromField(fieldClass   
                    .getKey()),   
                    new Class<?>[] { fieldClass.getKey().getType() });   
            method.invoke(obj, new Object[] { id });   
            return obj;   
        } catch (Exception e) {   
            e.printStackTrace();   
        }   
  
        throw new RuntimeException("找不到主键为:[" + id + "]的实体");   
    }  

public T load(PK id) {
		// annotationParas利用反射解析被标注为@Entity的type类型(比如说上文提到的User.class),
		// 然后将标注为@Id和@Column的属性存入FieldClass对象,供下面进一步使用
		final FieldClass fieldClass = annotationParas.generatorSQL(type);
		T obj = null;
		// 因为是load方法,默认给它加一个基于CGLib的拦截器,该拦截器是实现延迟加载的关键,稍后我们再详细看看
		LazyInitializer<T, PK> interceptor = new LazyInitializerImpl<T, PK>();
		// 将当前的session对象设置给该拦截器,以便在取非主键属性时,能够正常查询数据库
		// 从而将对象初始化
		interceptor.setSession(this);

		// 默认生成的是一个基于CGLib的代理,并非真实的对象,通过图1,图2,大家应该可以看到
		// User=User$$EnhancerByCGLib$$... 我就不多说了
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(type);
		// 注意别忘记将刚才生成的拦截器注入到代理中去
		enhancer.setCallback(interceptor);
		obj = (T) enhancer.create();

		try {
			// 因为通过CGLib生成的User对象,主键属性id=null
			// 所以我们还得执行主键的set方法(比如说setId),这样就可以像图1显示那样,id="1"是有值的
			// 到此,load方法执行完毕,始终没有查询数据库
			Method method = type.getMethod(getMethodFromField(fieldClass
					.getKey()),
					new Class<?>[] { fieldClass.getKey().getType() });
			method.invoke(obj, new Object[] { id });
			return obj;
		} catch (Exception e) {
			e.printStackTrace();
		}

		throw new RuntimeException("找不到主键为:[" + id + "]的实体");
	} 

 

annotationParas其实就是一个工具类,完成实体类与数据库表之间的映射。里面无非就是反射,判断,组装,最后组成一个我们想要的数据信息装进一个载体里——在这里是一个叫FieldClass 的JavaBean。对hibernate来说,将对象映射工作是在程序启动之初就完成了。

 

接下来是LazyInitializer,咱们先看它的实现:

 

 

Java代码  
public class LazyInitializerImpl<T, PK extends Serializable> implements  
        LazyInitializer<T, PK>, MethodInterceptor {   
  
    private Session<T, PK> session; // 绑定的session对象   
    private boolean isAlreadyInit = false; // 是否已经查询过数据库   
    private T targetObject; // 目标对象   
  
    // 通CGLib生成的对象,如果设置了此拦截器,那么其方法每次调用时,都会触发此方法   
    public Object intercept(Object obj, Method method, Object[] args,   
            MethodProxy proxy) throws Throwable {   
        // 继续利用反射得到代理对象的标有@Id的主键属性   
        Class<?> clas = obj.getClass();   
        Field field = getPrimaryKey(clas);   
  
        assert (field != null);   
        // 如果当前调用的方法是标注为@Id的话,那么就不从数据库里取,直接返回代理   
        // 即如果是getId()的话,直接用代理调用;如果是getName()的话,那就必须查询数据库,取出实际对象,并进行相应的调用了   
        if (method.getName().toLowerCase().indexOf(field.getName()) > -1) {   
            return proxy.invokeSuper(obj, args);   
        } else {   
            if (!isAlreadyInit) {   
                field.setAccessible(true);   
                // session.get方法直接查询数据库,并将ResultSet结果组将成User对象   
                targetObject = session.get((PK) field.get(obj));   
                isAlreadyInit = true;   
            }   
            return method.invoke(targetObject, args);   
  
        }   
  
    }   
  
        ..............省略其它辅助方法   
  
}  

public class LazyInitializerImpl<T, PK extends Serializable> implements
		LazyInitializer<T, PK>, MethodInterceptor {

	private Session<T, PK> session; // 绑定的session对象
	private boolean isAlreadyInit = false; // 是否已经查询过数据库
	private T targetObject; // 目标对象

	// 通CGLib生成的对象,如果设置了此拦截器,那么其方法每次调用时,都会触发此方法
	public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy proxy) throws Throwable {
		// 继续利用反射得到代理对象的标有@Id的主键属性
		Class<?> clas = obj.getClass();
		Field field = getPrimaryKey(clas);

		assert (field != null);
		// 如果当前调用的方法是标注为@Id的话,那么就不从数据库里取,直接返回代理
		// 即如果是getId()的话,直接用代理调用;如果是getName()的话,那就必须查询数据库,取出实际对象,并进行相应的调用了
		if (method.getName().toLowerCase().indexOf(field.getName()) > -1) {
			return proxy.invokeSuper(obj, args);
		} else {
			if (!isAlreadyInit) {
				field.setAccessible(true);
				// session.get方法直接查询数据库,并将ResultSet结果组将成User对象
				targetObject = session.get((PK) field.get(obj));
				isAlreadyInit = true;
			}
			return method.invoke(targetObject, args);

		}

	}

        ..............省略其它辅助方法

} 

 

当我们User u = session.load("1")对象后,

 

调用u.getId()时,会立即转入LazyInitializer的intercept()方法,然后按照上面的逻辑,自然是直接返回getId()的值,根本不会与数据库打交道。 
当调用u.getName()时,也会先立即转入LazyInitializer的intercept()方法,然后发现"getName()".indexOf("id")>-1==false,于是立即利用已经绑定的session对象去用主键ID往数据库里查询。这也是为什么在hibernate中,如果使用了延迟加载使得一个代理没有被初始化,而你又关闭了session,再次去取除主键外的其它属性时,常常出现session close异常。 
看到这里,大家是不是觉得所谓的延迟加载并不是那么神秘,而且从数据库I/O操作上来说,会觉得这种设计确实是比较优雅。当然hibernate还有很多很多值得学习和借鉴的特性,下次有时间我再整理整理。

Global site tag (gtag.js) - Google Analytics