Spring注解发展历程
1.springboot基本认识
随着Spring的诞生,IOC 和DI的特性,方便了我们对于Bean的使用和管理,但是对于Bean的配置来说,确显得很笨重,麻烦,引入第三方组件,不得不在xml文件中配置一堆的bean, 同时,最常用的通过SpringMVC来构建一个web项目的步骤也很繁琐:
(1)创建maven项目结构
(2)添加Spring SpringMVC Servlet API等依赖
(3)创建web.xml,配置DispatcherServlet
(4)配置spring: application-context.xml
(5)创建controller,发布http请求,然后发布到jsp/Servlet容器
即使我们想发布一个很简单的项目,也必须经历上面这些步骤,对于单体项目来说没有问题,但是对于分布式架构,SOA架构,甚至微服务架构下的项目构建,这就很要命了。于是,springboot应运而生。
- 什么是springboot?
springboot 框架是为了能够帮助使用 spring 框架的开发者快速高效的构建一个基于 spirng 框架以及 spring 生态体系的应用解决方案。它是“约定优于配置”这个理念下的一个最佳实践。因此它是一个服务于框架的框架,服务的范围是简化配置文件。 - springboot 约定优于配置的体现
- maven的目录结构: 默认resources目录存放配置文件,默认提供application.properties配置文件;默认打包方式为jar
- 只要引入spring-boot-starter-web的依赖,就默认包含spring mvc 相关的依赖以及内置 tomcat 容器,使得构建一个 web 应用更加简单
- EnableAutoConfiguration 默认对于依赖的 starter 进行自动装载(starter 启动依赖解决了不同版本的jar包管理问题,统一管理了所有依赖的jar的版本)
2.springboot快速构建web项目
添加依赖:
创建controller
运行,与SpringMVC相比,同样是通过web项目输出一个hello,springboot整个构建过程不超过5分钟,极大的简化了开发
3. Spring注解的发展
springboot 的奥秘,在于@SpringBootApplication 这个注解,打开 SpringBootApplication 这个注解,可以看到它实际上是一个复合注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
单纯看这些注解,你会一头雾水,为了能够知道这些注解背后到底做了什么,我们有必要知道spring各种注解的发展以及作用。
3.1 spring1.x的注解
SpringFramework1.x时代,bean的定义只能同过xml进行配置:
xml定义bean的缺点: 大量依赖xml配置文件,难以维护;举例:如果要使用一个bean, 首先要在xml中定义一个bean,通过applicationContext.getBean(“beanName”) 来获取并使用bean:
package com.gupaoedu.example;
public class HelloService {
}
applicationContext.xml 中定义bean
<?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">
<bean name="helloService" class="com.gupaoedu.example.HelloService"/>
</beans>
使用bean:
package com.gupaoedu.example;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class DemoMain {
public static void main(String[] args) {
ApplicationContext context=new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
System.out.println(context.getBean(HelloService.class));
}
}
3.2 spring2.x的注解
Spring Framework2.x时代,2.0版本在Annotation中添加了@Required、@Repository以及AOP相关的@Aspect等注解,同时也提升了XML配置能力,也就是可扩展的XML,比如Dubbo这样的开源框架就是基于Spring XML的扩展来完美的集成Spring,从而降低了Dubbo使用的门槛。
spring2.5 版本引入:
@Autowired 依赖注入
@Qualifier 依赖查找
@Component @Service
@Controller (控制层的注解)
@RequestMapping
spring2.x如何使用@Sercie/@Component注解:
(1)类上面加上@Sercie,声明这是一个service类
(2)加上注解后,如何被spring识别到并加载:
在spring的配置文件中加上包扫描:<context:component-scan base-package=“com.gupaoedu.demo02”/>
就可以让spring扫描 classpath下指定的包路径下加了@Servcie @Repository @Controller @Component 等这些注解的类,然后将他们注册成spring的bean,加载到spring容器中
代码示例:
package com.gupaoedu.demo02;
import org.springframework.stereotype.Service;
@Service // bean的生声明
public class Demo02Service {
}
applicationContext.xml配置包扫描:
<context:component-scan base-package="com.gupaoedu.demo02"/>
使用bean:
package com.gupaoedu.demo02;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class Demo02Main {
public static void main(String[] args) {
// 修改application-context.xml扫描包路径为demo02 <context:component-scan base-package="com.gupaoedu.demo02"/>
ApplicationContext context=new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
System.out.println(context.getBean(Demo02Service.class));
}
}
3.3 spring3.x的注解
Spring Framework3.0是一个里程碑式的时代,它的功能特性开始出现了非常大的扩展,比如全面拥抱Java5、以及Spring Annotation。更重要的是,它提供了配置类注解@Configuration取代XML配置方式, spring3.x主要注解有:
- @Configuration 去xml化
- @ComponentScan 实现包的扫描,取代了applicationContext.xml配置文件中的 <context: component-scan>标签
- @Import 导入外部配置
- @EnableXXX 自动装配(启动一个模块)
代码示例1:@Configuration + @ComponentScan 实现bean的定义,扫描与加载
package com.gupaoedu.demo03;
public class Demo03Service {
}
上面定义了一个普通的java类Demo03Service ,下面定义一个配置类,用来加载Demo03Service
package com.gupaoedu.demo03;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan("com.gupaoedu.demo03")
@Configuration //相当于applicationContext.xml
public class SpringConfiguration {
@Bean // 定义bean, 相当于applicationContext.xml中的<bean>标签
public Demo03Service demo03Service(){
return new Demo03Service();
}
}
基于注解配置类加载上下文,并获取bean:
package com.gupaoedu.demo03;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Demo03Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
System.out.println(context.getBean(Demo03Service.class));
}
}
上面Demo03Service 还可以这样写, 加上@Service注解,那么配置类中就不需要重复定义bean了:
package com.gupaoedu.demo03;
import org.springframework.stereotype.Service;
@Service
public class Demo03Service {
}
package com.gupaoedu.demo03;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan("com.gupaoedu.demo03") // 扫描com.gupaoedu.demo03包下的注解
@Configuration //<applicationContext.xml
public class SpringConfiguration {
/*@Bean
public Demo03Service demo03Service(){
return new Demo03Service();
}*/
}
代码示例2:实现依赖注入,使用加了@Service注解的Demo03Service类,然后创建一个Demo04Service,在配置类SpringConfiguration 中定义Demo04Service,Demo04Service这个bean的定义依赖Demo03Service:
package com.gupaoedu.demo03;
public class Demo04Service {
private Demo03Service demo03Service;
public void setDemo03Service(Demo03Service demo03Service) {
this.demo03Service = demo03Service;
}
public Demo03Service getDemo03Service() {
return demo03Service;
}
}
package com.gupaoedu.demo03;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan("com.gupaoedu.demo03")
@Configuration //<applicationContext.xml
public class SpringConfiguration {
@Bean
public Demo04Service demo04Service(Demo03Service demo03Service){
Demo04Service demo04Service = new Demo04Service();
demo04Service.setDemo03Service(demo03Service);
return demo04Service;
}
}
package com.gupaoedu.demo03;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Demo04Main {
public static void main(String[] args) {
ApplicationContext applicationContext=new
AnnotationConfigApplicationContext(SpringConfiguration.class);
System.out.println(applicationContext.getBean(Demo04Service.class));
}
}
代码示例3: @Import注解导入外部配置
package com.gupaoedu.demo04;
public class ImportService {
}
定义配置类:
package com.gupaoedu.demo04;
import org.springframework.context.annotation.Bean;
@Configuration
public class ImportConfiguration {
@Bean
public ImportService importService(){
return new ImportService();
}
}
接下来,我们在demo03的配置类中导入这个新增的配置类:
package com.gupaoedu.demo03;
import com.gupaoedu.demo04.ImportConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@ComponentScan("com.gupaoedu.demo03")
@Configuration //<applicationContext.xml
@Import(ImportConfiguration.class) // 导入外部配置类
public class SpringConfiguration {
@Bean
public Demo04Service demo04Service(Demo03Service demo03Service){
Demo04Service demo04Service = new Demo04Service();
// 依赖注入
demo04Service.setDemo03Service(demo03Service);
return demo04Service;
}
}
获取导入的配置类中定义的bean:
package com.gupaoedu.demo03;
import com.gupaoedu.demo04.ImportConfiguration;
import com.gupaoedu.demo04.ImportService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Demo04Main {
public static void main(String[] args) {
ApplicationContext applicationContext=new
AnnotationConfigApplicationContext(SpringConfiguration.class);
System.out.println(applicationContext.getBean(ImportService.class));
System.out.println(applicationContext.getBean(Demo04Service.class));
}
}
代码示例4: @EnableXXX 注解启动一个模块
以定时任务为例,springMVC中创建一个定时任务如下:
applicationContext.xml中:
<context:component-scan base-package="com.gupaoedu.demo05"/>
<task:annotation-driven scheduler="scheduler"/>
<task:scheduler id="scheduler" pool-size="5"/>
定时任务:
package com.gupaoedu.demo05;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class TaskService {
@Scheduled(fixedRate = 3000)
public void reportCurrentTime(){
System.out.println("current Time:"+new Date());
}
}
package com.gupaoedu.demo05;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class TaskMain {
public static void main(String[] args) {
ApplicationContext context=new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
}
}
通过Enable注解将代码改为如下:
package com.gupaoedu.demo06;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class TaskService02 {
@Scheduled(fixedRate = 3000)
public void reportCurrentTime(){
System.out.println("current Time:"+new Date());
}
}
配置类:@EnableScheduling注解启动定时任务模块,
package com.gupaoedu.demo06;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
@ComponentScan("com.gupaoedu.demo06")
@EnableScheduling
@Configuration
public class TaskConfiguration {
}
package com.gupaoedu.demo06;
import com.gupaoedu.demo03.SpringConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Task02Main {
public static void main(String[] args) {
ApplicationContext applicationContext=new
AnnotationConfigApplicationContext(TaskConfiguration.class);
}
}
两者的效果是一样的,
注解驱动的bean的定义,是通过AnnotationDrivenBeanDefinitionParser这个类去解析的,
这里会声明一个ScheduledAnnotationBeanPostProcessor 的bean去处理@Schedule注解,xml配置中的<task:annotation-driven scheduler=“scheduler”/> 所做的事情
接下来,看下@EnableScheduling注解做了什么事情
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({SchedulingConfiguration.class})
@Documented
public @interface EnableScheduling {
}
这个注解核心作用就是导入了SchedulingConfiguration 这个配置类,这个配置类就是创建一个ScheduledAnnotationBeanPostProcessor 的bean注入到IOC容器中,与xml中是一样的,不同点在于,通过Enable模块驱动,不需要手动去配置ScheduledAnnotationBeanPostProcessor bean的注入,只需要通过@EnableScheduling注解来开启ScheduledAnnotationBeanPostProcessor bean的注入
package org.springframework.scheduling.annotation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
@Configuration
@Role(2)
public class SchedulingConfiguration {
public SchedulingConfiguration() {
}
@Bean(
name = {"org.springframework.context.annotation.internalScheduledAnnotationProcessor"}
)
@Role(2)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
3.4 spring4.x的注解
- @Conditional注解
@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件时才向IOC容器注册bean。
@Conditional的定义如下:
//此注解可以标注在类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
从代码中可以看到,需要传入一个Class数组,并且需要继承Condition接口:
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
Condition是个接口,需要实现matches方法,返回true则注入bean,false则不注入。代码示例如下:
(1)需要在配置类中被装载的bean
package com.gupaoedu.example.demo01;
public class DemoService {
}
(2)自定义Bean的装载条件(自定义条件注解)
package com.gupaoedu.example.demo01;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* 自定义bean条件
*/
public class MyCondition implements Condition{
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
// 1.conditionContext 可以从上下文中获取需要使用的信息
// 2.annotatedTypeMetadata 注解的元数据
if(1 == 1){ // 随便写个判断逻辑模拟一下
return true;
}
return false;
}
}
(3)在配置类中,加上自定义的条件注解
package com.gupaoedu.example.demo01;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfiguration {
// 满足某个条件才装载DemoService 这个bean
@Conditional(MyCondition.class)
@Bean
public DemoService demoService(){
return new DemoService();
}
}
(4)获取bean :DemoService
package com.gupaoedu.example.demo01;
import javafx.application.Application;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ConditionMain {
public static void main(String[] args) {
ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfiguration.class);
System.out.println(context.getBean(DemoService.class));
}
}