本篇讲的是如何将咱们本身的业务逻辑和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就不是了。