前面花了大量篇幅講解spring如何完成掃描注冊的遍膜。注意碗硬,此時的注冊是將業(yè)務類class所對應的BeanDefinition,要想使用業(yè)務類的功能瓢颅,必須先實例化恩尾。spring肯定不會直接new一個業(yè)務對象來管理,spring是通過動態(tài)代理技術完成業(yè)務類的實例化挽懦。
什么是CGLIB
CGLIB(Code Generator Library)是一個強大的翰意、高性能的代碼生成庫。其被廣泛應用于AOP框架(Spring巾兆、dynaop)中猎物,用以提供方法攔截操作虎囚。CGLIB代理主要通過對字節(jié)碼的操作角塑,為對象引入間接級別,以控制對象的訪問淘讥。我們知道Java中有一個動態(tài)代理也是做這個事情的圃伶,那我們?yōu)槭裁床恢苯邮褂肑ava動態(tài)代理,而要使用CGLIB呢?答案是CGLIB相比于JDK動態(tài)代理更加強大,JDK動態(tài)代理雖然簡單易用,但是其有一個致命缺陷是粉怕,只能對接口進行代理巷嚣。如果要代理的類為一個普通類、沒有接口亭珍,那么Java動態(tài)代理就沒法使用了。
CGLIB底層使用了ASM(一個短小精悍的字節(jié)碼操作框架)來操作字節(jié)碼生成新的類。除了CGLIB庫外唧取,腳本語言(如Groovy何BeanShell)也使用ASM生成字節(jié)碼。ASM使用類似SAX的解析器來實現(xiàn)高性能划提。我們不鼓勵直接使用ASM枫弟,因為它需要對Java字節(jié)碼的格式足夠的了解
CGLIB的簡單使用
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
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 SampleClass {
public void test(){
System.out.println("hello world");
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before method run...");
Object result = proxy.invokeSuper(obj, args);
System.out.println("after method run...");
return result;
}
});
SampleClass sample = (SampleClass) enhancer.create();
sample.test();
}
}
在mian函數(shù)中,我們通過一個Enhancer和一個MethodInterceptor來實現(xiàn)對方法的攔截鹏往,運行程序后輸出為:
before method run...
hello world
after method run...
Process finished with exit code 0
上面就是CGLIB的簡單介紹及應用淡诗,CGLIB不是本文的重點,這里不再詳述伊履。Spring就是依靠CGLIB完成業(yè)務類的動態(tài)代理韩容。
拋磚引玉
老規(guī)矩,首先祭出我們的配置類
@ComponentScan("com.app")
@Configuration
public class Config {
}
啟動spring
public class SpringTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Config.class);
context.refresh();
System.out.println(context.getBean(Config.class));
}
}
spring啟動后唐瀑,我們獲取Config配置對象宙攻,打印結果如下:
com.config.Config$$EnhancerBySpringCGLIB$$efc56a4d@33e5ccce
,不言而喻介褥,Conifg被CGLIB動態(tài)代理成了另一個增強型的對象座掘。
我們看,Config類上有個@Configuration注解柔滔,這個注解的作用前面文章講過好多了溢陪,表示這是一個配置類,spring掃描注冊的時候會解析這個類睛廊,但是如果把這個注解去掉形真,我們看一下打印結果:
此時,spring就沒有對它進行動態(tài)代理了超全。其實咆霜,Configuration的作用遠不止如此,我們繼續(xù)測試嘶朱。
首先生成兩個業(yè)務類E和F
public class E {
}
public class F {
}
通過@Bean方式注入
@ComponentScan("com.app")
@Configuration
public class Config {
@Bean
public E getE(){
System.out.println("get class E");
return new E();
}
@Bean
public F getF(){
getE();
System.out.println("get class F");
return new F();
}
}
getE方法會生成E類的實例對象蛾坯,getF方法在生成F實例對象的同時,會再一次調用getE方法生成一個E實例對象疏遏,會嗎脉课?
public class SpringTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Config.class);
context.refresh();
System.out.println(context.getBean(E.class));
System.out.println(context.getBean(F.class));
}
}
打印結果:
getE在整個過程中救军,只被調用了一次,換句話講倘零,getE()方法在getF()中并沒有起作用唱遭!好神奇!如果去掉@Configuration這個注解呈驶,情況就不一樣了拷泽,讀者可自行測試。
spring是怎么判斷Config是否需要代理的呢袖瞻?
context.refresh()
完成了啟動過程跌穗,跟進代碼找到invokeBeanFactoryPostProcessors(beanFactory);
,繼續(xù)跟進虏辫,第一行PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
蚌吸,invokeBeanFactoryPostProcessors這個方法就是調用各種后置處理器的,前面博文也講過太多了砌庄,這里不再詳述羹唠,如果讀者看到這里有點懵的話,建議按順序閱讀本專題娄昆。繼續(xù)佩微,ConfigurationClassPostProcessor完成了掃描注冊,完事找到方法里這么一行代碼
invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
這行代碼跟進去萌焰,看下源碼
private static void invokeBeanFactoryPostProcessors(
Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {
for (BeanFactoryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanFactory(beanFactory);
}
}
假設我們程序員沒有提供后置處理器的話哺眯,這里的postProcessors只有一個,就是ConfigurationClassPostProcessor扒俯,怎么老是這個后置處理器奶卓,這個類實現(xiàn)了BeanDefinitionRegistryPostProcessor接口,而BeanDefinitionRegistryPostProcessor接口又繼承了BeanFactoryPostProcessor接口撼玄,ConfigurationClassPostProcessor通過BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法完成了掃描注冊夺姑,spring緊接著調用ConfigurationClassPostProcessor所實現(xiàn)BeanFactoryPostProcessor接口的postProcessBeanFactory方法完成后續(xù)處理,這個后續(xù)處理就是上面的源碼啦掌猛!
源碼很簡單盏浙,就是調用了后置處理器的postProcessBeanFactory方法,在這里就是ConfigurationClassPostProcessor的postProcessBeanFactory方法荔茬,我們看下它的源碼
/**
* Prepare the Configuration classes for servicing bean requests at runtime
* by replacing them with CGLIB-enhanced subclasses.
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
int factoryId = System.identityHashCode(beanFactory);
if (this.factoriesPostProcessed.contains(factoryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + beanFactory);
}
this.factoriesPostProcessed.add(factoryId);
if (!this.registriesPostProcessed.contains(factoryId)) {
// BeanDefinitionRegistryPostProcessor hook apparently not supported...
// Simply call processConfigurationClasses lazily at this point then.
processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
}
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
方法的注釋的意思是废膘,準備配置類以便在運行時為bean請求提供服務,方法是用cglib增強的子類替換它們慕蔚,也就是使用cglib的代理的方式增強beanDefinition丐黄。
前面是對當前beanDefinitionRegister做判斷,是否已經處理過和注冊過坊萝,不出意外的話就會進入enhanceConfigurationClasses(beanFactory);這個方法孵稽,顧名思義许起,就是增強配置類的十偶,這也解釋了為什么前面的Config加上@Configuration就會被動態(tài)代理菩鲜。那么下面的重點就是閱讀enhanceConfigurationClasses的源碼嘍,源碼首先找出所有的帶有@Configuration注解的配置類并存放到LinkedHashMap集合中惦积。在這里只有咱們的Config配置類符合接校,然后遍歷集合進行增強處理:
首先,根據(jù)BeanDefinition獲取對應的class對象
Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
然后就是增強處理:
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
下一個分析enhance方法看如何增強的
/**
* Loads the specified class and generates a CGLIB subclass of it equipped with
* container-aware callbacks capable of respecting scoping and other bean semantics.
* @return the enhanced subclass
*/
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Ignoring request to enhance %s as it has " +
"already been enhanced. This usually indicates that more than one " +
"ConfigurationClassPostProcessor has been registered (e.g. via " +
"<context:annotation-config>). This is harmless, but you may " +
"want check your configuration and remove one CCPP if possible",
configClass.getName()));
}
return configClass;
}
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
if (logger.isTraceEnabled()) {
logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s",
configClass.getName(), enhancedClass.getName()));
}
return enhancedClass;
}
看這個方法的注釋狮崩,加載指定的類并生成一個CGLIB代理的子類蛛勉。源碼最重要的一行
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
首先分析newEnhancer方法:
/**
* Creates a new CGLIB {@link Enhancer} instance.
*/
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
//把業(yè)務類,這里是Config睦柴,設置成代理類的父類
enhancer.setSuperclass(configSuperClass);
//代理類實現(xiàn)EnhancedConfiguration接口
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
enhancer.setUseFactory(false);
//設置代理類名稱的生成策略
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
return enhancer;
}
看注釋诽凌,生成一個CGLIB代理實例,里面用到了Enhancer 坦敌,不就是文章開頭講的那個小demo吧侣诵!
然后就是createClass方法:
/**
* Uses enhancer to generate a subclass of superclass,
* ensuring that callbacks are registered for the new subclass.
*/
private Class<?> createClass(Enhancer enhancer) {
Class<?> subclass = enhancer.createClass();
// Registering callbacks statically (as opposed to thread-local)
// is critical for usage in an OSGi environment (SPR-5932)...
Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
return subclass;
}
通過Enhancer 生成一個所代理的類的子類。代理結束后返回狱窘,一直返回到enhanceConfigurationClasses方法的Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);杜顺,這時候的enhancedClass 就是代理類了,OK蘸炸,這行代碼執(zhí)行完后往下執(zhí)行了beanDef.setBeanClass(enhancedClass);
躬络,意思很明朗,就是將Config的BeanDefinitino中的class替換成代理class搭儒。之后就會實例化代理類而不是Config類本身穷当。
現(xiàn)在解釋清楚了,為什么Config加上@Configuration注解后就會被spring動態(tài)代理淹禾。再解釋上文getE()方法在getF()中并沒有起作用膘滨!很明顯,spring既然代理了Config稀拐,那么執(zhí)行getF方法時不是真的執(zhí)行Config里的getF方法火邓,而是執(zhí)行代理類的getF方法,在哪里執(zhí)行的呢德撬?
newEnhancer方法中铲咨,有一個過濾器的設置enhancer.setCallbackFilter(CALLBACK_FILTER);
,CALLBACK_FILTER是一個變量:
private static final Callback[] CALLBACKS = new Callback[] {
new BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
};
private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);
文章一開始舉的CGLIB案例中,就是通過回調完成了方法的攔截對吧蜓洪? 這里有兩個回調類BeanMethodInterceptor和BeanFactoryAwareMethodInterceptor纤勒,他倆組成了一個回調鏈,依次調用而已隆檀。這兩個回調其實是在bean的聲明周期過程中調用的摇天,這是后續(xù)章節(jié)的內容粹湃,這里我們簡單講下,后面會詳細講泉坐。
在實例化過程中为鳄,我們主要關注BeanMethodInterceptor這個回調。我們在調用getF方法時腕让,會先執(zhí)行回調BeanMethodInterceptor中的intercept方法孤钦。intercept方法很復雜很復雜,大概意思是纯丸,在執(zhí)行getF中的getE方法時判斷getE返回的bean是否已經實例化了偏形,如果已經實例化了就不再調用該方法了。getF和getE調用的時候都是先調用回調函數(shù)的觉鼻,都會判斷是否已經實例化了俊扭。spring以此保證@Bean返回的實例是單例的。
本篇講的比較簡單比較淺坠陈,估計讀者也是明白個大概的原理萨惑,因為這里涉及到后續(xù)的知識,沒關系畅姊,后面會再詳細講解的咒钟。
這里大家主要了解
- CGLIB的使用
- Spring是如何利用到CGLIB的
- 如何使用內置的代理回調類BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor來增強我們的目標類方法的即可若未。
- BeanDefinition將對應的class用代理類替換掉業(yè)務類朱嘴,后期實例化的是代理類