本篇講的是如何將咱們本身的業務邏輯和Spring框架整合起來,整合的方式主要採用的是註解,裏面涉及到了多個知識點。java
咱們的目的是作出咱們本身的註解,主要是標在接口上,當調用接口裏相應的方法的時候,就會執行咱們本身的邏輯。面試
對的,就是如今的MyBatis和Feign的整合方式,這種也是如今比較容易的,若是你業務裏面xml用的多,你也能夠結合xml來搞,拓展xml的文章我以前已經說過,你能夠翻回去看看。 bash
咱們須要作不少步工做,咱們把這些步驟拆開了一步一步來作app
首先固然是須要自定義出咱們本身的註解框架
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnno {
}
複製代碼
我簡單的說一下上面四個註解的意思哈,@Target表述註解能夠被標註的地方,ElementType.TYPE表示只能被標註在類上。@Retention表示的是註解的生命週期,這裏的RententionPolicy.Runtime表示它在被加載到jvm中以後還依然存在。@Documented表示這個註解會被javadoc工具所記錄。@Inherited表示這個註解是會被繼承的,其實也就是當A有咱們這個@MyAnno的時候,B繼承了A,B也會擁有這個註解而已。jvm
畫外音:我沒有將@Target以及@Retention中全部的值都拿出來說,由於那樣的話我文章就寫不完了,並且這也不是咱們本章的重點,你們能夠自行了解一下這個~ ide
咱們有了本身的註解以後,還須要讓Spring能夠識別的來咱們的註解,那麼此時咱們須要擴展我以前講過的BeanDefinitionRegistryPostProcessor,代碼以下:工具
@Component
public class MyAnnoConfigurationPostProcessor1 implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
private String[] basePackages;
public MyAnnoConfigurationPostProcessor1() {
// 暫且寫死掃描路徑
this.basePackages = new String[]{"com.example.demo.external5"};
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 1. new 一個ClassPathBeanDefinitionScanner對象,ClassPathBeanDefinitionScanner這個玩意是
// Spring 默認的註解掃描器
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);
// 2.爲上面呢建立好的scanner對象添加Filter,主要目的是讓它可以認識咱們的註解
scanner.addIncludeFilter(new AnnotationTypeFilter(MyAnno.class));
// 3.進行掃描
scanner.scan(this.basePackages);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {}
}
複製代碼
其實你也可使用ComonentScan的include屬性,這樣會來的更簡單一些。可是我爲了劇情的進一步發展,就先引出BeanDefinitionRegistryPostProcessor post
而後咱們把咱們的註解找個類標上:測試
@MyAnno
public class Person {
}
複製代碼
接着用這段代碼測試下:
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Config.class);
Person p = annotationConfigApplicationContext.getBean(Person.class);
System.out.println(p);
}
}
// 配置類,實際做用就是上面的ComponentScan註解
@ComponentScan(basePackages = "com.example.demo.external5")
public class Config {
}
複製代碼
咱們能夠看到以下的測試結果:
此時咱們便擁有了一個本身定義的註解,這個註解如今和Spring本來的這四個註解@Component、@Controller、@Repository、@Service的做用是同樣的,而且,它如今仍是對接口無效的,由於標在接口上的話,會被Spring的註解掃描器ClassPathBeanDefinitionScanner這玩意忽略掉,所以咱們接下來須要本身定義咱們本身的註解掃描器。
注意:咱們自定義的註解掃描器須要有掃描接口的功能,咱們先來簡單的實現一下它
public class ClassPathAnnoScanner extends ClassPathBeanDefinitionScanner {
// 必須有這樣一個構造方法,否則報錯,由於父類沒有無參構造,這是因爲java的繼承機制致使的
public ClassPathAnnoScanner(BeanDefinitionRegistry registry) {
super(registry);
// 在構造器中就加上filter,使它天生就能夠認識咱們的自定義註解
this.addIncludeFilter(new AnnotationTypeFilter(MyAnno.class));
}
// 這個方法是使掃描器可以掃描註解的核心
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 調用父類方法的掃描功能,返回BeanDefinition
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
System.out.println("掃描到的 beanDefinitions 是空的,沒法進行進一步操做!");
}
return beanDefinitions;
}
}
複製代碼
而後在代碼中結合BeanDefinitionRegistryPostProcessor去使用:
@Component
public class MyAnnoConfigurationPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
private String[] basePackages;
public MyAnnoConfigurationPostProcessor() {
// 暫且寫死
this.basePackages = new String[]{"com.example.demo.external5"};
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 1. 使用咱們本身的掃描器
ClassPathAnnoScanner scanner = new ClassPathAnnoScanner(registry);
// 2.爲上面呢建立好的scanner對象添加Filter,主要目的是讓它可以認識咱們的註解
scanner.addIncludeFilter(new AnnotationTypeFilter(MyAnno.class));
// 3.進行掃描
scanner.scan(this.basePackages);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 空實現便可
}
}
複製代碼
這時候咱們即可以把咱們的MyAnno註解標在接口上了,可是,若是你此時把它標在接口上面而且啓動的話,那是會報錯滴,緣由也很簡單哈,接口是沒有構造方法的,因此沒法初始化。咱們若是想像MyBatis或者是Feign那樣子在咱們調用一個接口的方法以後能夠執行相應的邏輯的話,須要在運行時期生成一個相應接口的代理,而且這個代理還須要藉助FactoryBean來生成(末尾含FactoryBean面試題)。
可是具體是怎麼作的呢?其實仍是得繼續改造咱們的註解掃描器,咱們來看看改造的代碼:
public class ClassPathAnnoScanner extends ClassPathBeanDefinitionScanner {
// 必須有這樣一個構造方法,否則報錯,由於父類沒有無參構造,這是因爲java的繼承機制致使的
public ClassPathAnnoScanner(BeanDefinitionRegistry registry) {
super(registry);
this.addIncludeFilter(new AnnotationTypeFilter(MyAnno.class));
}
// 這個方法是使掃描器可以掃描註解的核心
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
System.out.println("掃描到的 beanDefinitions 是空的,沒法進行進一步操做!");
} else {
// 調用修改BeanDefinition的方法
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
// 相比上面,多了個這個修改BeanDefinition的方法
// 須要在這裏把interface的beanClass轉爲特定的beanClass
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitions) {
BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
// 這個會使Spring優先選擇對應的有參構造
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
// 把掃描到的interface改成FactoryBean,這樣便能以FactoryBean的方式初始化
beanDefinition.setBeanClassName(MyFactoryBean.class.getName());
}
}
}
複製代碼
能夠看到哈,相比以前的,咱們還須要去修改掃描到的BeanDefinition,否則你讓Spring給你初始化接口,Spring是會讓你嗝屁的。
在用Factory建立代理以前,你首先要知道代理是怎麼建立的,若是這都不知道的話麻煩自行百度jdk的動態代理。
首先咱們先建立出咱們的代理的處理器邏輯
// 這個是屬於jdk動態代理的東西
public class MyServiceProxy implements InvocationHandler {
private Class target;
public MyServiceProxy(Class target) {
this.target = target;
}
public Object getProxy() {
// 建立代理的核心邏輯
return Proxy.newProxyInstance(target.getClassLoader(), new Class[]{target}, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 每次被代理的接口的方法被調用就會走到這裏來,在這裏咱們能夠作不少事情
// 我這裏只是簡單打印了方法的全路徑名稱而已
// 你能夠在這裏根據每一個方法的名稱不一樣作不一樣的事情,甚至還能夠根據方法參數裏的method去拿方法的註解,獲取註解的信息,進而作更多的事情
System.out.println(method.getDeclaringClass().getName() + "." + method.getName());
return null;
}
}
複製代碼
而後再實現咱們本身的FactoryBean:
public class MyFactoryBean<T> implements FactoryBean<T> {
// 必須是接口的class
private Class<T> clazz;
public MyFactoryBean() {
}
public MyFactoryBean(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public T getObject() throws Exception {
// 建立代理
return (T) new MyServiceProxy(clazz).getProxy();
}
@Override
public Class<?> getObjectType() {
return clazz;
}
public void setClass(Class<T> clazz){
this.clazz = clazz;
}
}
複製代碼
在這裏,先說一下FactoryBean的機制。FactoryBean這個也是用於建立對象的,若是咱們某個類好比A.java實現了FactoryBean的話,而且你給這個類標上了@Component這樣的註解,那麼,當調用getBean("a")的時候,咱們獲取到的是FactoryBean中getObject返回的對象。若是咱們想要獲得FactoryBean自己應該則應該調用在參數前加上"&",好比getBean("&a")這樣去調用,或者getBean(A.class)這樣傳個Class對象進去也能夠,具體的緣由能夠看我前面的文章。
此時咱們能夠嘗試着把咱們的@MyAnno註解加在接口上:
@MyAnno
public interface TestService {
void eat();
}
複製代碼
接口很簡單,也沒有實現類。而後用以下測試代碼進行測試
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Config.class);
TestService testService = (TestService) annotationConfigApplicationContext.getBean("testService");
testService.eat();
}
複製代碼
完了能夠看到控制檯輸出的內容以下:
此時還沒完,由於別忘了,咱們的MyAnnoConfigurationPostProcessor裏面的掃描路徑是寫死的。通常來講,咱們會把掃描路徑配合一個註解寫到啓動類上,方便統一管理,就像Mybatis的@MapperScan那樣。明確目標後,而後咱們來解決咱們的問題
咱們定義一下咱們本身的Scanner:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyAnnoScannerRegistrar.class)
public @interface MyAnnoScanner {
// value 爲包掃描的路徑
String[] value() default {};
}
複製代碼
注意這個import註解引進來的這個class是重中之重,它是用來對咱們這個MyAnnoScanner裏面value值對應的包來進行掃描的,咱們來看看代碼:
// 必須實現ImportBeanDefinitionRegistrar
public class MyAnnoScannerRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 獲取被Import註解所標着的類的元數據
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MyAnnoScanner.class.getName()));
List<String> basePackages = new ArrayList();
// 獲取MyAnnoScanner裏面的路徑
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
ClassPathAnnoScanner scanner = new ClassPathAnnoScanner(registry);
for (String basePackage : basePackages) {
// 針對每一個路徑進行掃描
scanner.doScan(basePackage);
}
}
}
複製代碼
最後咱們在咱們的Config.java上加上咱們的註解:
@ComponentScan(basePackages = "com.example.demo.external5")
@MyAnnoScanner(value = "com.example.demo.external5")
public class Config {
}
複製代碼
注意哈,以前的MyAnnoConfigurationPostProcessor這個類咱們就能夠幹掉了,由於它此時已經徹底沒什麼用了。咱們用和上面同樣的測試代碼,發現最後的輸出了咱們想要的東西,此時,完整的一個整合就結束了。
多說一下關於Import註解的東西,你只須要記住,當Import進來的類,沒有實現ImportBeanDefinitionRegistrar這個接口的時候,這個類就會被放進Spring容器中, 你能夠經過@Autowired的方式去自動注入它;反之若是實現了ImportBeanDefinitionRegistrar,那麼這個類以後是不會放入Spring中,這個緣由涉及到的代碼在ConfigurationClassPostProcessor的方法postProcessBeanDefinitionRegistry中,具體是仍是比較複雜的,我後邊若是有時間也會專門再去寫文章講這些。
相信很多人在面試中是遇到過這樣一個面試題的:你能說說FactoryBean和BeanFactory的區別嗎?咱們能夠說他們都是工廠,都是用來建立對象的,可是建立對象的場景是天差地別的,BeanFactory是能夠用來建立各類各樣的對象,可是FactoryBean是用來建立某一類的複雜對象的。而且BeanFactory人家的實現類均可以說是一個一個的容器,可是FactoryBean就不是了。