在上一節(jié)中,我介紹了Spring中極為重要的BeanPostProcessor BeanFactoryPostProcessor Import ImportSelector,還介紹了一些其他的零碎知識(shí)點(diǎn),正如我上一節(jié)所說(shuō)的,Spring實(shí)在是太龐大了,是眾多Java開發(fā)大神的結(jié)晶,很多功能,很多細(xì)節(jié),可能一輩子都不會(huì)用到,不會(huì)發(fā)現(xiàn),作為普通開發(fā)的我們,只能盡力去學(xué)習(xí),去挖掘,也許哪天可以用到呢。
讓我們進(jìn)入正題吧。
Full Lite
在上一節(jié)中的第一塊內(nèi)容,我們知道了Spring中除了可以注冊(cè)我們最常用的配置類,還可以注冊(cè)一個(gè)普通的Bean,今天我就來(lái)做一個(gè)補(bǔ)充說(shuō)明。
如果你接到一個(gè)需求,要求寫一個(gè)配置類,完成掃描,你會(huì)怎么寫?
作為經(jīng)常使用Spring的來(lái)說(shuō),這是一個(gè)入門級(jí)別的問(wèn)題,并且在20秒鐘之內(nèi)就可以完成編碼:
@Configuration
@ComponentScan
public class AppConfig {
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
context.getBean(ServiceImpl.class).query();
}
}
@Component
public class ServiceImpl{
public void query() {
System.out.println("正在查詢中");
}
}
運(yùn)行:

但是你有沒有嘗試過(guò)把AppConfig類上的@Configuration注解給去除?你在心里肯定會(huì)犯嘀咕,這不能去除啊,這個(gè)@Configuration注解申明了咱們的AppConfig是一個(gè)Spring配置類,去除了@Configuration注解,怎么可能可以呢?但是事實(shí)勝于雄辯,當(dāng)我們把@Configuration注解給刪除,再次運(yùn)行,你會(huì)見證到奇跡:
@ComponentScan
public class AppConfig {
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
context.getBean(ServiceImpl.class).query();
}
}

一點(diǎn)問(wèn)題都沒有?。。∈遣皇堑竭@里已經(jīng)顛覆了你對(duì)Spring的認(rèn)知。
其實(shí),在Spring內(nèi)部,把帶上了@Configuration的配置類稱之為Full配置類,把沒有帶上@Configuration,但是帶上了@Component @ComponentScan @Import @ImportResource等注解的配置類稱之為L(zhǎng)ite配置類。
原諒我,實(shí)在找不到合適的中文翻譯來(lái)表述這里的Full和Lite。
也許你會(huì)覺得這并沒什么用,只是“茴的四種寫法”而已。
別急,讓我們看下去,將會(huì)繼續(xù)刷新你的三觀:
@ComponentScan
public class AppConfig {
}
注意現(xiàn)在的AppConfig類上沒有加上@Configuration注解。
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean(AppConfig.class).getClass().getSimpleName());
}
}
我們注冊(cè)了Lite配置類,并且從Spring容器中取出了Lite配置類,打印出它的類名。
運(yùn)行:

可以看到從容器取出來(lái)的就是AppConfig類,各位看官肯定會(huì)想,這不是廢話嗎,難道從容器取出來(lái)會(huì)變成了一只老母雞?
別急嘛,讓我們繼續(xù)。
我們?cè)僭贏ppConfig類加上@Configuration注解,使其變成Full配置類,然后還是一樣,注冊(cè)這個(gè)配置類,取出這個(gè)配置類,打印類名:

你會(huì)驚訝的發(fā)現(xiàn),的確從容器里取出了一個(gè)老母雞,哦,不,是一個(gè)奇怪的類,從類名我們可以看到CGLIB這個(gè)關(guān)鍵字,CGLIB是動(dòng)態(tài)代理的一種實(shí)現(xiàn)方式,也就是說(shuō)我們的Full配置類被CGLIB代理了。
你是不是從來(lái)都沒有注意過(guò),竟然會(huì)有如此奇怪的設(shè)定,但是更讓人驚訝的事情還在后頭,讓我們想想,為什么好端端的類,Spring要用Cglib代理?這又不是AOP。Spring內(nèi)部肯定做了一些什么!沒錯(cuò),確實(shí)做了?。?!
下面讓我們看看Spring到底做了什么:
public class ServiceImpl {
public ServiceImpl() {
System.out.println("ServiceImpl類的構(gòu)造方法");
}
}
ServiceImpl類中有一個(gè)構(gòu)造方法,打印了一句話。
public class OtherImpl {
}
再定義一個(gè)OtherImpl類,里面什么都沒有。
public class AppConfig {
@Bean
public ServiceImpl getServiceImpl() {
return new ServiceImpl();
}
@Bean
public OtherImpl getOtherImpl() {
getServiceImpl();
return new OtherImpl();
}
}
這個(gè)AppConfig沒有加上@Configuration注解,是一個(gè)Lite配置類,里面定義了兩個(gè)@Bean方法,其中g(shù)etServiceImpl方法創(chuàng)建并且返回了ServiceImpl類的對(duì)象,getOtherImpl方法再次調(diào)用了getServiceImpl方法。
然后我們注冊(cè)這個(gè)配置類:
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
}
}
運(yùn)行:

發(fā)現(xiàn)打印了兩次"ServiceImpl類的構(gòu)造方法",這也很好理解,因?yàn)閚ew了兩次ServiceImpl嘛,肯定會(huì)執(zhí)行兩次ServiceImpl構(gòu)造方法呀。
我們?cè)诎袬Configuration注解給加上,讓AppConfig稱為一個(gè)Full配置類,再次運(yùn)行:

你會(huì)驚訝的發(fā)現(xiàn)只打印了一次"ServiceImpl類的構(gòu)造方法",說(shuō)明只調(diào)用了一次ServiceImpl類的構(gòu)造方法,其實(shí)這也說(shuō)的通啊,因?yàn)锽ean默認(rèn)是Singleton的,所以只會(huì)創(chuàng)建一次對(duì)象嘛。
但是問(wèn)題來(lái)了,為什么我們明明new了兩次ServiceImpl類,但是真正只new了一次?結(jié)合上面的內(nèi)容,很容易知道答案,因?yàn)镕ull配置類被Cglib代理了,它已經(jīng)不是我們?cè)榷x的AppConfig類了,最終執(zhí)行的是代理對(duì)象。
好了,這個(gè)問(wèn)題就討論到這里,至于為什么說(shuō)(如何證明)帶上@Configuration注解的配置類稱之為Full配置類,不帶的稱之為L(zhǎng)ite配置類,Cglib是怎么代理Full配置類的,代理的規(guī)則又是什么,這就涉及到Spring的源碼解析了,就不在今天的討論內(nèi)容之中了。
ImportBeanDefinitionRegistrar
大家一定使用過(guò)Mybatis,甚至使用過(guò)Mybatis的擴(kuò)展,我在使用的時(shí)候,覺得太特么的神奇了,只要在配置類上打一個(gè)MapperScan注解,指定需要掃描哪些包,然后這些包里面只有接口,根本沒有實(shí)現(xiàn)類,為什么可以完成數(shù)據(jù)庫(kù)的一系列操作,不知道大家有沒有和我一樣的疑惑,直到我知道了ImportBeanDefinitionRegistrar這個(gè)神奇的接口,關(guān)于這個(gè)接口,我不知道該怎么去描述這個(gè)接口的作用,因?yàn)檫@個(gè)接口實(shí)在是太強(qiáng)大了,實(shí)在不是用簡(jiǎn)單的文字可以描述清楚的。下面我就利用這個(gè)接口來(lái)完成一個(gè)假的MapperScan,從中慢慢體驗(yàn)這個(gè)接口的強(qiáng)大,對(duì)了,這個(gè)接口要和Import注解配合使用。
首先需要定義一個(gè)注解:
@Import(CodeBearMapperScannerRegistrar.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface CodeBearMapperScanner {
String value();
}
其中value就是需要掃描的包名,在這個(gè)注解類中又打了一個(gè)Import注解,來(lái)引ImportBeanDefinitionRegistrar類。
再定義一個(gè)注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface CodeBearSql {
String value();
}
這個(gè)注解是打在方法上的,接收的是一個(gè)sql語(yǔ)句。
然后要定義一個(gè)類,去實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口,重寫提供的方法。
public class CodeBearMapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
try {
AnnotationAttributes annoAttrs =
AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(CodeBearMapperScanner.class.getName()));
String packageValue = annoAttrs.getString("value");
String pathValue = packageValue.replace(".", "/");
File[] files = resourceLoader.getResource(pathValue).getFile().listFiles();
for (File file : files) {
String name = file.getName().replace(".class", "");
Class<?> aClass = Class.forName(packageValue + "." + name);
if (aClass.isInterface()&&!aClass.isAnnotation()) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
beanDefinition.setBeanClass(CodeBeanFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(packageValue + "." + name);
registry.registerBeanDefinition(name, beanDefinition);
}
}
} catch (Exception ex) {
}
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
其中ResourceLoaderAware接口的作用不大,我只是利用這個(gè)接口,獲得了ResourceLoader ,然后通過(guò)ResourceLoader去獲得包下面的類而已。這方法的核心就是循環(huán)文件列表,根據(jù)包名和文件名,反射獲得Class,接著判斷Class是不是接口,如果是接口的話,動(dòng)態(tài)注冊(cè)Bean。如何動(dòng)態(tài)去注冊(cè)Bean呢?我在這里利用的是BeanDefinitionBuilder,通過(guò)BeanDefinitionBuilder獲得一個(gè)BeanDefinition,此時(shí)BeanDefinition是一個(gè)很純凈的BeanDefinition,經(jīng)過(guò)一些處理,再把最終的BeanDefinition注冊(cè)到Spring容器。
關(guān)鍵就在于處理的這兩行代碼了,這里可能還看不懂,我們繼續(xù)看下去。
我們需要再定義一個(gè)類,去實(shí)現(xiàn)FactoryBean,InvocationHandler兩個(gè)接口:
public class CodeBeanFactoryBean implements FactoryBean, InvocationHandler {
private Class clazz;
public CodeBeanFactoryBean(Class clazz) {
this.clazz = clazz;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
CodeBearSql annotation = method.getAnnotation(CodeBearSql.class);
String sql= annotation.value();
System.out.println(sql);
return sql;
}
@Override
public Object getObject() throws Exception {
Object o = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{clazz}, this);
return o;
}
@Override
public Class<?> getObjectType() {
return clazz;
}
}
關(guān)于FactoryBean接口,在上一節(jié)中有介紹,這里就不再闡述了。
這個(gè)類有一個(gè)構(gòu)造方法,接收的是一個(gè)Class,這里接收的就是用來(lái)進(jìn)行數(shù)據(jù)庫(kù)操作的接口。getObject方法中,就利用傳進(jìn)來(lái)的接口和動(dòng)態(tài)代理來(lái)創(chuàng)建一個(gè)代理對(duì)象,此時(shí)這個(gè)代理對(duì)象就是FactoryBean生產(chǎn)的一個(gè)Bean了,只要對(duì)JDK動(dòng)態(tài)代理有一定了解的人都知道,返回出來(lái)的代理對(duì)象實(shí)現(xiàn)了我們用來(lái)進(jìn)行數(shù)據(jù)庫(kù)操作的接口。
我們需要把這個(gè)Bean交給Spring去管理,所以就有了CodeBearMapperScannerRegistrar中的這行代碼:
beanDefinition.setBeanClass(CodeBeanFactoryBean.class);
因?yàn)閯?chuàng)建CodeBeanFactoryBean對(duì)象需要一個(gè)Class參數(shù)。所以就有了CodeBearMapperScannerRegistrar中的這行代碼:
//packageValue + "." +name 就是接口的全名稱
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(packageValue + "." + name);
invoke方法比較簡(jiǎn)單,就是獲得CodeBearSql注解上的sql語(yǔ)句,然后打印一下,當(dāng)然這里只是模擬下,所以并沒有去查詢數(shù)據(jù)庫(kù)。
下面讓我們測(cè)試一下吧:
public interface UserRepo {
@CodeBearSql(value = "select * from user")
void get();
}
@Configuration
@CodeBearMapperScanner("com.codebear")
@ComponentScan
public class AppConfig {
}
@Service
public class Test {
@Autowired
UserRepo userRepo;
public void get(){
userRepo.get();
}
}
運(yùn)行結(jié)果:

可以看到我們的功能已經(jīng)實(shí)現(xiàn)了。其實(shí)Mybatis的MapperScan注解也是利用了ImportBeanDefinitionRegistrar接口去實(shí)現(xiàn)的。
可以看到第二塊內(nèi)容,其實(shí)已經(jīng)比較復(fù)雜了,不光光有ImportBeanDefinitionRegistrar,還整合FactoryBean,還融入了動(dòng)態(tài)代理。如果我們不知道FactoryBean,可能這個(gè)需求就很難實(shí)現(xiàn)了。所以每一塊知識(shí)點(diǎn)都很重要。
這一節(jié)的內(nèi)容到這里就結(jié)束了。