本篇文章,从 Spring1.x 到 Spring 5.x 的迭代中,站在当初的角度去思考 Spring 注解驱动的倒退过程,这将有助于咱们更好的了解 Spring 中的注解设计。
Spring Framework 1.x
在 SpringFramework1.x 时代,其中在 1.2.0 是这个时代的分水岭,过后 Java5 刚刚公布,业界正衰亡了应用 Annotation 的技术风,Spring Framework 天然也提供了反对,比方过后曾经反对了 @Transactional 等注解,然而这个时候,XML 配置形式还是惟一抉择。
-
在 xml 中增加 Bean 的申明
<bean name="testService" class="com.gupaoedu.controller.TestService"/>
-
测试
public class XmlMain {public static void main(String[] args) {ApplicationContext context=new FileSystemXmlApplicationContext("classpath:applicationContext.xml"); TestService testService=(TestService)context.getBean("testService"); System.out.println(testService); } }
Spring Framework 2.x
Spring Framework2.x 时代,2.0 版本在 Annotation 中增加了 @Required、@Repository 以及 AOP 相干的 @Aspect 等注解,同时也晋升了 XML 配置能力,也就是可扩大的 XML,比方 Dubbo 这样的开源框架就是基于 Spring XML 的扩大来完满的集成 Spring,从而升高了 Dubbo 应用的门槛。
在 2.x 时代,2.5 版本也是这个时代的分水岭,它引入了一些很外围的 Annotation
- Autowired 依赖注入
- @Qualifier 依赖查找
- @Component、@Service 组件申明
- @Controller、@RequestMappring 等 spring mvc 的注解
只管 Spring 2.x 时代提供了不少的注解,然而依然没有脱离 XML 配置驱动,比方 <context:annotation-config> <context:componet-scan> , 前者的职责是注册 Annotation 处理器,后者是负责扫描 classpath 下指定包门路下被 Spring 模式注解标注的类,将他们注册成为 Spring Bean
-
在 applicationContext.xml 中定义 <context:componet-scan>
<context:component-scan base-package="com.gupaoedu.controller"/>
-
增加注解申明
@Service public class TestService {}
-
测试类
public class XmlMain {public static void main(String[] args) {ApplicationContext context=new FileSystemXmlApplicationContext("classpath:applicationContext.xml"); TestService testService=(TestService)context.getBean("testService"); System.out.println(testService); } }
Spring Framework 3.x
Spring Framework3.0 是一个里程碑式的时代,他的性能个性开始呈现了十分大的扩大,比方全面拥抱 Java5、以及 Spring Annotation。更重要的是,它提供了配置类注解 @Configuration,他呈现的首要任务就是取代 XML 配置形式,不过比拟遗憾的是,Spring Framework3.0 还没有引入替换 XML 元素 <context:componet-scan> 的注解,而是抉择了一个过渡形式 @ImportResource。
@ImportResource 容许导入遗留的 XML 配置文件,比方
@ImportResource("classpath:/META-INF/spring/other.xml")
@Configuration
public class SpringConfiguration{}
并且在 Spring Frameworkd 提供了 AnnotationConfigApplicationContext 注册,用来注册 @Configuration Class,通过解析 Configuration 类来进行拆卸。
在 3.1 版本中,引入了 @ComponentScan,替换了 XML 元素 <Context:component-scan> , 这个注解尽管是一个小的降级,然而对于 spring 来说在注解驱动畛域却是一个很大的提高,至此也体现了 Spring 的无配置化反对。
Configuration 配置演示
-
Configuration 这个注解大家应该有用过,它是 JavaConfig 模式的基于 Spring IOC 容器的配置类应用的一种注解。因为 SpringBoot 实质上就是一个 spring 利用,所以通过这个注解来加载 IOC 容器的配置是很失常的。所以在启动类外面标注了 @Configuration,意味着它其实也是一个 IoC 容器的配置类。
举个非常简单的例子
- 测试代码
ConfigurationDemo
@Configuration
public class ConfigurationDemo {
@Bean
public DemoClass demoClass(){return new DemoClass();
}
}
DemoClass
public class DemoClass {public void say(){System.out.println("say:Hello Mic");
}
}
ConfigurationMain
public class ConfigurationMain {public static void main(String[] args) {
ApplicationContext applicationContext=
new AnnotationConfigApplicationContext
(ConfigurationDemo.class);
DemoClass demoClass=applicationContext.getBean(DemoClass.class);
demoClass.say();}
}
Component-scan
ComponentScan 这个注解是大家接触得最多的了,相当于 xml 配置文件中的 <context:component-scan>。它的次要作用就是扫描指定门路下的标识了须要拆卸的类,主动拆卸到 spring 的 Ioc 容器中。
标识须要拆卸的类的模式次要是:@Component、@Repository、@Service、@Controller 这类的注解标识的类。
-
在 spring-mvc 这个工程中,创立一个独自的包门路,并创立一个 OtherServcie。
@Service public class OtherService {}
-
在 Controller 中,注入 OtherService 的实例,这个时候拜访这个接口,会报错,提醒没有 otherService 这个实例。
@RestController public class HelloController { @Autowired OtherService otherService; @GetMapping("/hello") public String hello(){System.out.println(otherService); return "Hello Gupaoedu"; } }
-
增加 conpoment-scan 注解,再次拜访,谬误解决。
@ComponentScan("com.gupaoedu")
ComponentScan 默认会扫描以后 package 下的的所有加了相干注解标识的类到 IoC 容器中;
Import 注解
import 注解是什么意思呢?联想到 xml 模式下有一个<import resource/>
模式的注解,就明确它的作用了。import 就是把多个分来的容器配置合并在一个配置中。在 JavaConfig 中所表白的意义是一样的。
-
创立一个包,并在外面增加一个独自的 configuration
public class DefaultBean { } @Configuration public class SpringConfig { @Bean public DefaultBean defaultBean(){return new DefaultBean(); } }
-
此时运行测试方法,
public class MainDemo {public static void main(String[] args) {ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfig.class); String[] defNames=ac.getBeanDefinitionNames(); for(String name:defNames){System.out.println(name); } } }
-
在另外一个包门路下在创立一个配置类。此时再次运行后面的测试方法,打印 OtherBean 实例时,这个时候会报错,提醒没有该实例
public class OtherBean { } @Configuration public class OtherConfig { @Bean public OtherBean otherBean(){return new OtherBean(); } }
-
批改 springConfig,把另外一个配置导入过去
@Import(OtherConfig.class) @Configuration public class SpringConfig { @Bean public DefaultBean defaultBean(){return new DefaultBean(); } }
- 再次运行测试方法,即可看到对象实例的输入。
至此,咱们曾经理解了 Spring Framework 在注解驱动时代,齐全代替 XML 的解决方案。至此,Spring 团队就此止步了吗?你们太单纯了。尽管无配置化可能缩小配置的保护带来的困扰,然而,还是会存在很对第三方组建的根底配置申明。同样很繁琐,所以 Spring 退出了 @Enable 模块驱动。这个个性的作用是把雷同职责的性能组件以模块化的形式来拆卸,更进一步简化了 Spring Bean 的配置。
Enable 模块驱动
咱们通过 spring 提供的定时工作机制来实现一个定时工作的性能,别离拿演示在应用 Enable 注解和没应用 Enable 的区别。让大家感触一些 Enable 注解的作用。
应用 EnableScheduing 之前
-
在 applicationContext.xml 中增加定时调度的配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.gupaoedu.controller"/> <!--AnnotationDrivenBeanDefinitionParser--> <task:annotation-driven scheduler="scheduler"/> <!-- 定时器开关 --> <task:scheduler id="scheduler" pool-size="5"/> </beans>
-
编写工作解决类
@Service public class TaskService {@Scheduled(fixedRate = 5000) // 通过 @Scheduled 申明该办法是打算工作,应用 fixedRate 属性每隔固定工夫执行 public void reportCurrentTime(){System.out.println("每隔 5 秒执行一次"+new Date()); } }
-
编写测试类
public class TestTask {public static void main(String[] args) {ApplicationContext applicationContext=new FileSystemXmlApplicationContext("classpath:applicationContext.xml"); } }
应用 EnableScheding 之后
-
创立一个配置类
@Configuration @ComponentScan("com.gupaoedu.controller") @EnableScheduling public class SpringConfig {}
-
创立一个 service
@Service public class TaskService {@Scheduled(fixedRate = 5000) // 通过 @Scheduled 申明该办法是打算工作,应用 fixedRate 属性每隔固定工夫执行 public void reportCurrentTime(){System.out.println("每隔 5 秒执行一次"+new Date()); } }
-
创立一个 main 办法
public class TaskMain {public static void main(String[] args) {ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class); } }
- 启动服务即可实现定时调度的性能。
思考应用 Enable 省略了哪个步骤呢?
首先咱们看没应用 Enable 的代码,它外面会有一个
<task:annotation-driven scheduler="scheduler"/>
这个 scheduler 是一个注解驱动,会被 AnnotationDrivenBeanDefinitionParser 这个解析器进行解析。
在 parse 办法中,会有如下代码的定义
builder = BeanDefinitionBuilder.genericBeanDefinition("org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor");
builder.getRawBeanDefinition().setSource(source);
这个类是用来解析 @Scheduled 注解的。
ok,咱们再看一下 EnableScheduling 注解,咱们能够看到,它会主动注册一个 ScheduledAnnotationBeanPostProcessor 的 bean。所以,通过这个例子,就是想表白 Enable 注解的作用,它能够帮咱们省略一些第三方模块的 bean 的申明的配置。
public class SchedulingConfiguration {public SchedulingConfiguration() { }
@Bean(name = {"org.springframework.context.annotation.internalScheduledAnnotationProcessor"}
)
@Role(2)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {return new ScheduledAnnotationBeanPostProcessor();
}
}
Spring Framework 4.x
Spring 4.x 版本,是注解的欠缺时代,它次要是晋升条件拆卸能力,引入了 @Conditional 注解,通过自定义 Condition 实现配合,补救了之前版本条件化配置的短板。
简略来说,Conditional 提供了一个 Bean 的装载条件判断,也就是说如果这个条件不满足,那么通过 @Bean 申明的对象,不会被主动装载进来,具体是怎么用的呢?,先来简略带大家理解一下它的根本应用。
Conditional 的概述
@Conditional 是一个注解,咱们察看一下这个注解的申明,它能够接管一个 Condition 的数组。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {Class<? extends Condition>[] value();}
@FunctionalInterface
public interface Condition {boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
这个 Condition 是一个函数式接口,提供了一个 matchers 的办法,简略来说,它就是提供了一个匹配的判断规定,返回 true 示意能够注入 bean,返回 false 示意不能注入。
Conditional 的实战
-
自定义个一个 Condition,逻辑比较简单,如果以后操作系统是 Windows,则返回 true,否则返回 false
public class GpCondition implements Condition{ @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { // 此处进行条件判断,如果返回 true,示意须要加载该配置类或者 Bean // 否则,示意不加载 String os=conditionContext.getEnvironment().getProperty("os.name"); if(os.contains("Windows")){return true;} return false; } }
-
创立一个配置类,装载一个 BeanClass
@Configuration public class ConditionConfig { @Bean @Conditional(GpCondition.class) public BeanClass beanClass(){return new BeanClass(); } }
- 在 BeanClass 的 bean 申明办法中减少 @Conditional(GpCondition.class),其中具体的条件是咱们自定义的 GpCondition 类。上述代码所表白的意思是,如果 GpCondition 类中的 matchs 返回 true,则将 BeanClass 装载到 Spring IoC 容器中
-
运行测试方法
public class ConditionMain {public static void main(String[] args) { AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(ConditionConfig.class); BeanClass beanClass=context.getBean(BeanClass.class); System.out.println(beanClass); } }
总结
通过对 Spring 注解驱动的整体剖析,不难发现,咱们现在之所以可能十分不便的基于注解来实现 Spring 中大量的性能,得益于 Spring 团队一直解决用户痛点而做的各种致力。
而 Spring Boot 的主动拆卸机制,也是在 Spring 注解驱动的根底上演变而来,在后续的内容中,我会专门剖析 Spring Boot 的主动拆卸机制。
版权申明:本博客所有文章除特地申明外,均采纳 CC BY-NC-SA 4.0 许可协定。转载请注明来自
Mic 带你学架构
!
如果本篇文章对您有帮忙,还请帮忙点个关注和赞,您的保持是我一直创作的能源。欢送关注同名微信公众号获取更多技术干货!