# SpringBootDemo **Repository Path**: CandyPop/SpringBootDemo ## Basic Information - **Project Name**: SpringBootDemo - **Description**: study for spring-boot - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-03-12 - **Last Updated**: 2021-03-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #### Spring Boot 编程思想 [SpringBoot自动装配扩展](https://github.com/PopCandier/SpringBootDemo/blob/master/src/main/java/autoconfig/Spring%20Boot%20autoconfig.md) [注解编程驱动](https://github.com/PopCandier/SpringBootDemo/blob/master/src/main/java/enable/Chanpter-8%20Spring%20%E6%B3%A8%E8%A7%A3%E7%BC%96%E7%A8%8B%E9%A9%B1%E5%8A%A8.md) #### 从创建一个Spring Boot项目开始。 start.spring.io 我们可以很轻松的构建一个spring-boot项目 下载完,我们直接导入pom.xml文件,Maven将会帮我们直接构建spring-boot所需要的依赖。 **如何打包** 我们cd到spring-boot的根目录,执行 ``` mvn package ``` 不过前提是你需要拥有这个插件。 ```xml org.springframework.boot spring-boot-maven-plugin ``` 当构建完成后,将会存在一个.jar文件,你可以直接使用 `java -jar xxxx.jar` 去运行这个打包的文件。 这里有两个打包 `.jar`文件和`.jar.original`,前者比较大,因为他将依赖的包打包进来,后者只是代码部分,所以比较小,我们可以通过tree ..看到目录结构。 ```c D:\IDEAWORKSPACE\POP\TARGET\TEMP ├─BOOT-INF │ ├─classes //存放应用编译后的class文件 │ │ └─thinkingSpringBoot │ │ └─Pop │ └─lib //存放依赖的jar包 ├─META-INF //存放应用相关的元信息 MANIFEST.MF │ └─maven │ └─thinkingSpringBoot │ └─Pop └─org //目录存在Spring Boot 相关的 class 文件 └─springframework └─boot └─loader ├─archive ├─data ├─jar └─util D:\IDEAWORKSPACE\POP\TARGET\TEMP2 ├─META-INF │ └─maven │ └─thinkingSpringBoot │ └─Pop └─thinkingSpringBoot └─Pop ``` 为什么使用java -jar 会就会启动? 我们从解包出来的文件看出,这并不是标准的文件目录 而官方文档规定,javar -jar 命令引导的具体启动类必须配置在MANIFEST.MF资源的Main-Class中 而同时,根据 “JAR文件规范” MANIFEST.MF资源必须放在MATA-INF目录下 ```mf Manifest-Version: 1.0 Implementation-Title: Pop Implementation-Version: 0.0.1-SNAPSHOT Start-Class: thinkingSpringBoot.Pop.PopApplication Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Build-Jdk-Spec: 1.8 Spring-Boot-Version: 2.1.5.RELEASE Created-By: Maven Archiver 3.4.0 Main-Class: org.springframework.boot.loader.JarLauncher ``` 所以很显然,能够使得这个能够运行的,奥秘在 `org.springframework.boot.loader.JarLauncher`这里面。 当然,有能够启动jar当然也有能够启动 war `org.springframework.boot.loader.WarLauncher` 当然我们如果希望查看这个的实现,需要添加额外的依赖。 ```xml org.springframework.boot spring-boot-loader provided ``` 然后我们可以看到这个类的全貌 ```java public class JarLauncher extends ExecutableArchiveLauncher { static final String BOOT_INF_CLASSES = "BOOT-INF/classes/"; static final String BOOT_INF_LIB = "BOOT-INF/lib/"; public JarLauncher() { } protected JarLauncher(Archive archive) { super(archive); } @Override protected boolean isNestedArchive(Archive.Entry entry) { if (entry.isDirectory()) {//对于资源的定位 return entry.getName().equals(BOOT_INF_CLASSES); } return entry.getName().startsWith(BOOT_INF_LIB); } public static void main(String[] args) throws Exception { new JarLauncher().launch(args);//<----从这里开始 } } ``` 很明显,这个是启动的关键。 ```java public abstract class Launcher { /** * Launch the application. This method is the initial entry point that should be * called by a subclass {@code public static void main(String[] args)} method. * @param args the incoming arguments * @throws Exception if the application fails to launch */ protected void launch(String[] args) throws Exception { //这一句话表示注册协议 JarFile.registerUrlProtocolHandler(); ClassLoader classLoader = createClassLoader(getClassPathArchives()); launch(args, getMainClass(), classLoader); } //..... } ``` JDK中,默认支持file、HTTP、jar等协议并且这些实现都在sun.net.ww.protocol类中,名字都为Handler,因为这是规定。 sun.net.www.protocel.${protocol}.Handler,中间表示协议名,file、jar、http、https、ftp并且以上都为java.net.URLStreamHandler实现 JarFile.registerUrlProtocolHandler();执行的时候,将会把spring-boot自己实现的Hanlder追加到java的系统属性,java.protocol.handler类中 问题在于jdk已经实现了自己的jar方法,为什么spring-boot还要有自己选择覆盖呢,这点在P49的set方法上有说明。 大概地意思是spring-boot中地jar文件是一个独立地应用,与其他地jar不一样,他除了自己地jar文件之外,还有依赖其余地jar文件,所以jdk那套不适用于他,当执行java jar 命令地时候,内部地sun.net.www.protocol.jar.Handler无法被当做class path,所以需要被覆盖。 ```java ClassLoader classLoader = createClassLoader(getClassPathArchives()); ``` 这一步主要是为了区别目前是通过jar驱动还是war驱动,前者将会使用sprint boot fat jar后者用于解压其内容。接着调用launcher()方法。 ```java launch(args, getMainClass(), classLoader); ``` ```java protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception { Thread.currentThread().setContextClassLoader(classLoader); createMainMethodRunner(mainClass, args, classLoader).run(); } ``` 这一步就是从/META-INF/MAINFEST.MF资源周年读取start-class文件,并调用main方法启动。 **探究WarLauncher和JarLauncher的区别**、 其实两者区别还是很小地,一个明显地差别就是,常量地设定 首先是JarLauncher ```java static final String BOOT_INF_CLASSES = "BOOT-INF/classes/"; static final String BOOT_INF_LIB = "BOOT-INF/lib/"; ``` 其次是WarLauncher ```java private static final String WEB_INF = "WEB-INF/"; private static final String WEB_INF_CLASSES = WEB_INF + "classes/"; private static final String WEB_INF_LIB = WEB_INF + "lib/"; private static final String WEB_INF_LIB_PROVIDED = WEB_INF + "lib-provided/"; ``` war得定义很类似传统地serlvet容器地定义 web-inf/classes 之类地 我们再打包一次,看看区别 ```java thinkingSpringBoot Pop 0.0.1-SNAPSHOT war ``` 执行 mvn clean package ```java ├─META-INF │ └─maven │ └─thinkingSpringBoot │ └─Pop ├─org │ └─springframework │ └─boot │ └─loader │ ├─archive │ ├─data │ ├─jar │ └─util └─WEB-INF ├─classes │ └─thinkingSpringBoot │ └─Pop ├─lib └─lib-provided ``` 其实 ```java private static final String WEB_INF_LIB_PROVIDED = WEB_INF + "lib-provided/"; ``` 是spring-boot地war湿度有地, 这个目录只有一个 spring-boot-loader-x.x.x.RELEASE.jar 文件,不过更具serlvet规范,WBE-INF/lib-provided中地jar不会被servlet容器读取,会被忽略。 这样设计好处是,war是一种兼容地措施,既可以被warLauncher启动,又可以兼容servlet容器环境。换言之,warLauncher与JarLauncher并没本质区别,所以建议Spring-boot应用使用非传统web部署地时候,尽可能使用jar地方式。 #### 理解固化地Maven 此固化仅仅限制于spring-boot应用。 当然我们通过单击继承地方式,会有些限制 ```xml org.springframework.boot spring-boot-starter-parent 2.1.5.RELEASE ``` 可以通过这种依赖地方式替换 ```xml org.springframework.boot spring-boot-dependencies 2.1.5.RELEASE pom import ``` 不过,如果你这样打包会出问题,因为他会认为web.xml是必选,所以在你设置打包方式为 war地情况下,它会依赖maven-war-plugin插件,而web-inf/web.xml又是必须地,所以会报错。我们可以通过以下方式修改。 ```java [ERROR] Failed to execute goal org.apache.maven.plugins:maven-war-plugin:2.2:war (default-war) on project Pop: Error assembling WAR: webxml attribute is required (or pre-existing WEB-INF/web.xml if executing in update mode) -> [Help 1] ``` ```xml org.apache.maven.plugins maven-war-plugin false ``` ##### spring-boot-starter-parent与spring-boot-dependencies的区别 首先从pom文件地继承方式上来看,dependencies是parent地父类,换言之,我们选择直接继承dependencies都是可以的 当然。这个插件地版本,我们可以进到dependencies中,看看对应地版本具体是什么样的,为了兼容。 不过当我们运行java -jar命令地时候 ``` D:\IdeaWorkSpace\Pop\target>java -jar Pop-0.0.1-SNAPSHOT.war Pop-0.0.1-SNAPSHOT.war中没有主清单属性 ``` 会有这样地提示,不过结合前面地理论,首先war肯定是执行地了地,但是真正启动地确实依赖spring-boot-maven-plugin插件,所有java -jar对应地插件没有执行,所以我们需要对插件指定版本。不过这个时候插件依旧无法执行,所以我们还需要额外地配置。 ```xml org.springframework.boot spring-boot-maven-plugin 2.1.5.RELEASE repackage ``` ##### 总结 - 关于maven-war-plugin地差异,是因为版本地差异,2.2版本会出现web.xml必填写地选项,而3.1调整了其默认地必填行为 - 另外,在单独引用spring-boot-maven-plugin地时候,需要配置元素,否则不会将spring-boot地引导依赖重新打包(repackage)进jar,所以无法进行引导当前应用。、 - 最后一点是,一般来说我们不用使用spring-boot-dependencies来作为maven项目地parent,尽管spring-boot-starter-parent与spring-boot-dependencies,两者是继承关系。 ``` 2019-06-15 14:51:04.681 INFO 11348 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' ``` 我们使用 ```xml org.springframework.boot spring-boot-starter-web ``` 就将tomcat容器引导加载我们地应用,明明都不要安装,这是为什么 #### 嵌入式容器 **对于tomcat的嵌入式总结** Tomcat7+Maven插件可以构建可执行jar或者war文件,实现独立的web应用程序,也支持servlet组件的自动装配 如果是tomcat8更高版本的,需要借鉴更高的插件,P75 **对于jetty容器** ```xml org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-tomcat org.springframework.boot spring-boot-starter-jetty ``` 对于undertow容器 ```xml org.springframework.boot spring-boot-starter-undertow ``` ##### 嵌入式Reactive Web容器 原文里,表示Reactive web是springboot2.0的新特性,但是想要激活他,需要增加 spring-boot-starter-webflux依赖,并且不能与spring-boot-starter-web同时存在。 如果同时存在,webflux将会被忽略。 想要看到这个效果,指出 UndertowServletWebServer 是spring boot webserver 的实现类,这里强调servlet的原因是,undertow还有其他实现,那就是 reactive web 的实现 -undertowwebServer,想要这样除了只留下webflux依赖,还要把编译等级改成8 ```java /* * Spring -boot2.0新引入了一周年Application Context的实现 * WebServerApplicationContext 他提供了获取 WebServer的接口方法 getWebServer() * 只需要注入WebServerApplicationContext对象,并且在Springboot应用启动后,在输出到关联的WebServer实现类即可 * */ @Bean public ApplicationRunner runner(WebServerApplicationContext context){ return args->{ System.out.println(context.getWebServer().getClass().getName()); }; } ``` 不过,我们可以通过使用WebServerInitializedEvent,来监听更广的事件,原文中指出 ServletWebServerInitializedEvent是WebServerInitializedEvent的子类 ```java @EventListener(WebServerInitializedEvent.class) public void onWebServerReady(WebServerInitializedEvent event){ System.out.println("当前 WebServer 实现类为 ;"+ event.getWebServer().getClass().getName()); } ``` 这样会稍微安全一点,因为避免了注入WebServerApplicationContext失败的原因 最后,比较主流的三个容器,tomcat,jetty,undertow都支持reative web容器,这个具体如何配置在P95有详细讲解。 | 容器 | Maven依赖 | WebServer实现类 | | :------: | :--------------------------: | :---------------: | | Tomcat | spring-boot-starter-tomcat | TomcatWebServer | | Jetty | spring-boot-starter-jetty | JettyWebServer | | Undertow | spring-boot-starter-undertow | UndertowWebServer | #### 自动装配 我们常见的自动装配类的方法 * xml元素的`` * 注解@Import * 注解@Configuration 前者需要,ClassPathXmlApplicationContext加载,后者需要AnnotationConfigApplicationContext注册。 @import的使用 ```java public class Dog{} public class Cat{} @ComponentScan /*把用到的资源导入到当前容器中*/ @Import({Dog.class, Cat.class}) public class App { public static void main(String[] args) throws Exception { ConfigurableApplicationContext context = SpringApplication.run(App.class, args); System.out.println(context.getBean(Dog.class)); System.out.println(context.getBean(Cat.class)); context.close(); } } ``` 会有结果。 当然你也可以导入一个配置类 ```java public class MyConfig { @Bean public Dog getDog(){ return new Dog(); } @Bean public Cat getCat(){ return new Cat(); } } //@SpringBootApplication @ComponentScan /*导入配置类就可以了*/ @Import(MyConfig.class) public class App { public static void main(String[] args) throws Exception { ConfigurableApplicationContext context = SpringApplication.run(App.class, args); System.out.println(context.getBean(Dog.class)); System.out.println(context.getBean(Cat.class)); context.close(); } } ``` 很显然,运行WebFlux和嵌入式Web容器均已自动装配,那么是否可以认为当前引导类 PopApplication充当了@Configuration类的角色呢,答案是肯定的。 原文中指出,SpringBootApplication等价于三种标签 @Configuration(标注为配置类)/@ComponentScan(激活@Component的扫描)/@EnableAutoConfiguartion(负责激活spring-boot自动装配功能) 我们将SpringBootApplication替换成以上三种依旧可以执行。 ```java //@SpringBootApplication @Configuration @ComponentScan @EnableAutoConfiguration public class PopApplication { //少一个都不行 public static void main(String[] args) { SpringApplication.run(PopApplication.class, args); } } ``` ```java @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 { ``` * 2.0的spring-boot中ComponetScan并非使用了默认值,而是添加了排除的TypeFilter实现。前者1.4开始支持,`为了查找BeanFactory中已经注册的TypeExcludeFilter Bean`,后者从1.5开始支持,用于排除同时标注了@Configuration和@EnableAutoConfiguration的类。 * spring1.4开始,@SpringBootApplication 注解不再是@Configuration而是@SpringBootConfiguration,这种类似对象之间的继承关系,作者称之为“多层次@Component的‘’派生性”。 ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { ``` 三者关系 * @Component * @Configuration * @SpringBootConfiguration 原文中,我们也可以用@EnableAutoConfiguration来代替@SpringBootApplication 那么两者有什么区别呢? 后者比前者多一个CGLIB提升的过程,即,使用SpringBootApplication修饰过。也就是"继承"了Configuration的类,装载时,将会使用cglib,也就是用@Bean修饰的 ```java //@Configuration // @EnableAutoConfiguration @SpringBootApplication public class WebConfiguration { @Bean public RouterFunction helloWorld(){ return route(RequestPredicates.GET("/hello-world"), request-> ServerResponse.ok().body(Mono.just("Hello,World"),String.class) ); } @Bean public ApplicationRunner runner(BeanFactory beanFactory){ return args->{ System.out.println(" hello World Bean 的实现类是 "+beanFactory.getBean("helloWorld").getClass().getName()); System.out.println(" WebConfiguration Bean 实现类为 "+beanFactory.getBean(WebConfiguration.class).getClass().getName()); }; } think.in.spring.boot.app.config.WebConfiguration */ } //@Configuration @EnableAutoConfiguration // @SpringBootApplication public class WebConfiguration { @Bean public RouterFunction helloWorld(){ return route(RequestPredicates.GET("/hello-world"), request-> ServerResponse.ok().body(Mono.just("Hello,World"),String.class) ); } @Bean public ApplicationRunner runner(BeanFactory beanFactory){ return args->{ System.out.println(" hello World Bean 的实现类是 "+beanFactory.getBean("helloWorld").getClass().getName()); System.out.println(" WebConfiguration Bean 实现类为 "+beanFactory.getBean(WebConfiguration.class).getClass().getName()); }; think. in.spring.boot.app.config.WebConfigurationguration */ } ``` 所以cglib提升是为了@Configuration修饰后的类准备的,而非@Bean ##### 理解自动配置机制 我们之前定义了WebConfiguration属于编码方式的导入编程,而非自动装配。相反,其它自动装配的Bean肯定由某种机制完成的,这种机制就是自动配置的机制。 这里概括起来,就是spring-boot1.0添加了约定配置,可以自动导入@Configuration类的方式。这些注解需要标注到Configuration'上 * @ConditionalOnClass 当且仅当目标类存在于ClassPath下才可以装配。 * @ConditionOnMissingBean 当@Conditional中可以修饰一个class类,然后可以去查找这个类的所在实现 [@Conditional详解文章](https://blog.csdn.net/xcy1193068639/article/details/81491071) **创建自动配置类** 在resource下面,新建META-INF/spring.factories ```properties # 自动装配 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ think.in.spring.boot.app.autoconfigure.WebAutoConfiguration ``` #### 理解Production-Ready特性 在之前,我们可以使用自动装配,并且也可以使用@Conditional注解,都可以让Bean无声无息的被实例化,让spring为我们接管,我们越来越不清楚里面的细节。 所以我们需要一套运维的方案,了解Bean的组装情况,甚至是其他应用相关的信息,同时为了支持以配置方式调整应用行为,如Web服务端口,Spring-boot提供了Production-Ready特性 metrics(指标)/health checks(健康检查)/externalized configuration(外部配置)均属于为生产准备的特性 引出Spring Boot Actuator特性,他是“Production-Ready”的具体化 他的作用 * 使用场景:监视和管理投入生产的应用 * 监管媒介:HTTP或JMX端点(Endpoints) * 端点类型:审计(Auditing)/健康 (Health )和指标收集(metrics gathering) * 基本特点:自动运用(automatically applied) 添加依赖 ```xml org.springframework.boot spring-boot-starter-actuator ``` 常用的Endpoints * beans:显示当前Spring 应用上下文的Spring Bean完整列表,包含所有的ApplicationContext层次 * conditions:显示当前应用所有配置类和自动装配类的条件评估结果,(包含匹配和未匹配结果) * env:暴露Spring ConfigurableEnvironment中的PropertySource属性 * health: 显示应用的健康信息(默认添加) * info:显示任意的应用信息。(默认添加) 如果我们想要暴露其它endpoint可以加入这样的参数 这里用beans作为例子 `mvn spring-boot:run -Dmanagement.endpoints.web.exposure.include=beans` 接着,我们可以从控制台可以看到默认的/actuator将会被映射,然后我们在url地址输入 `http://localhost:8080/actuator/beans` 就可以得到目前所有的自动装配的Beans列表了 ```json {"contexts":{"application":{"beans":{"org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration":{"aliases":[],"scope":"singleton","type":"org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration$$EnhancerBySpringCGLIB$$fdd433b2","resource":null,"dependencies":[]},"org.springframework.boot.actuate.autoconfigure.health.HealthEndpointWebExtensionConfiguration$ReactiveWebHealthConfiguration":{"aliases":[],"scope":"singleton","type":"org.springframework.boot.actuate.autoconfigure.health.HealthEndpointWebExtensionConfiguration$ReactiveWebHealthConfiguration$$EnhancerBySpringCGLIB$$38c4c3b9","resource":null,"dependencies":["reactiveHealthIndicatorRegistry"]},"endpointCachingOperationInvokerAdvisor":{"aliases":[],"scope":"singleton","type":"org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor","resource":"class path resource [org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.class]","dependencies":["environment"]},"org.springframework.boot.actuate.autoconfigure.web.reactive.ReactiveManagementContextAutoConfiguration":{"aliases":.... ``` 同时,如果你还想暴露更多的endpoint,需要在自动装配的类上加上参数 ```java @ConditionalOnWebApplication//<----这里 @Configuration @Indexed @Import(WebConfiguration.class) public class WebAutoConfiguration { /* * 由于注解编程简化了配置,但是解析是会有性能消耗的,所以 springframk5.0加入了@Inedexed * 它将会为@Component和派生注解添加索引,减少运行时性能消耗。 * */ } ``` 同时,我们将启动的参数再变化一下 `mvn spring-boot:run -Dmanagement.endpoints.web.exposure.include=beans,conditions,env` 改变参数为 `http://localhost:8080/actuator/conditions` 可以得到当前系统的环境参数情况 ##### 理解外部化配置 简单来说就是,我们可以通过新的语法来定义自己的配置,springboot中提供三种途径 * Bean 的 @Value * Spring Environment 读取 * @ConfigurationProperties绑定到结构化对象中 原文中有比较详细的外部配置的加载顺序P130中 ```java @Component public class MyBean{ @Value("${name}") private String name; } ``` 这个值会从application.properties中读取,但是如果你在`命名行`给他命名了,那么他就是使用在命令行中的参数,因为外部化配置顺序 4是Commad line arguments,而优先于15.Applicationproperties packaged inside your jar(application.properties and YAML variants) PropertySource就是“外部配置化”API的描述方式。 **总结** Spring-boot主要有五大特性 * SpringApplication * 自动装配 * 外部化配置 * Spring Boot Actuator * 嵌入式web容器 最后引出了微服务概念,spring-boot因为缺少构建微服务的能力, 所以在此基础上研发了Spring-Cloud,可以帮助开发者快速构建通同的分布式系统 核心特性如下 * Distributed/versioned configuration(分布式配置) * Service registration and dicovery (服务注册和发现) * Routing (路由) * Service-to-service calls (服务调用) * Load balancing (负载均衡) * Circuit Breakers (熔断机制) * Distributed messaging (分布式消息) 最后作者向读者推荐了两本关于Spring Cloud的书籍 《Spring Cloud 微服务实战》作者:翟永超 《Spring Cloud 与 Docker 微服务架构实战》作者:周立 #### 走向自动装配 ##### 走向注解驱动编程(Annotation-Driven) * SpringFramework 1.x 不支持注解,只支持xml配置 * SpringFramework 2.x 增加几个注解 * Bean相关的 @Required * 数据相关的@Repository * AOP的@Aspect * SpringFramework 2.5 是比较重大的更新,算是一个分水岭 * 依赖注入 @Autowired * 另外,依赖注入也是可以注入某种类型的集合的 * ```java @Component("nameRepositoryHolder") public class NameRepositoryHolder{ @Autowired private Collection repositories; } ``` * 当然,无论你autowired注入单个spring bean还是集合都是可以的,但这只局限于某一个class类,例如上面就是`NameRepository`的集合,如果你还想在细粒度的控制筛选,那么就需要@Qualifier * ```java @Component("nameRepositoryHolder") public class NameRepositoryHolder{ @Autowired @Qualifier("chinesNameRepository") private Collection repositories; } ``` * 依赖查找 @Qualifier * 组件声明 @Component @Service * SpringMVC Annotation @Controller @RequestMapping @ModeAttribute 等 * 支持 JSR-250 的 @Resource注入,包括PostConstruct和PreDestory * 前者可替代 或者 Spring InitializingBean接口回调 * 后者可代替或者 DisposableBean * 但是 2.x是个尴尬的版本,他提供了很多的核心的anontaion,但是,却还是无法直接注解驱动,我们还是需要xml配置来驱动spring容纳 PathClassXmlApplicationContext,就类似``和``前者启动注册Annoation处理器,后者负责扫描相对于classpath下面的指定java根包(base package),寻找spring模式注解标记的类class,将他们注册成spring bean * 在1.x版本里,多个spring bean是需要排序的,一般的做法是实现ordered接口,从2.0开始通过在@Component class中标注@Order方式进行替代。 * Spring Framework 3.x 注解黄金时代 * 从3.X开始,spring开始着重的要替换xml配置。@Configuration就是其中一个 * ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { @AliasFor(annotation = Component.class) String value() default ""; } ``` * 不过3.0的spring还是没找找到替换``的注解,而是选择了过渡的方案@ImportResource和@Import * @ImportResource允许导入遗留的XML文件,而@Import则允许导入一个或多个SpringBean * ```java @ImportResource(value = "classpath:/META-INF/spring/others.xml") @Configuration public class SpringContextConfiguration { } ``` * 所以3.0引入了新的驱动AnnotationConfigApplicationContext * ```java @ImportResource(value = "classpath:/META-INF/spring/others.xml") @Configuration public class SpringContextConfiguration { @Lazy @Primary//当多个想同类型的bean出现的时候,可以定义主要注入的为这个 @DependsOn("springContextConfiguration")//依赖springContextConfiguration @Bean(name = "user") //Bean的名称为user public User user(){ User user = new User(); user.setName("Pop"); return user; } } ``` * @Configuration把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean。 @Scope注解 作用域 @Lazy(true) 表示延迟初始化 @Service用于标注业务层组件、 @Controller用于标注控制层组件(如struts中的action) @Repository用于标注数据访问组件,即DAO组件。 @Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。 @Scope用于指定scope作用域的(用在类上) @PostConstruct用于指定初始化方法(用在方法上) @PreDestory用于指定销毁方法(用在方法上) @Resource 默认按名称装配,当找不到与名称匹配的bean才会按类型装配。 @DependsOn:定义Bean初始化及销毁时的顺序 @Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常 @Autowired 默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用 @Autowired @Qualifier(“personDaoBean”) 存在多个实例配合使用 * spring 3.1 开始引入了@ComponentScan去替换`` ​ springframework 3.1 抽象了一套统一配置属性的API,包括配属属性存储接口 Environment以及配置属性源抽象 PropertySource,这个两个核心Api奠定了SpringBoot外部化配置的基础,也是Spring Cloud分布式配置的基石。 但是需要掌握这种API,学习成本还是略大,因为你需要熟悉spring,并且对bean的生命周期了解。 所以提供了@PropertySource简化实现 * 缓存方面:API提供了 Cache 和缓存管理器 Cache Manager 配套注解 Caching 和 Cacheable 简化了数据缓存的开发 * 异步支持方面,引入了异步操作注解@Async,周期异步执行注解@Scheduled以及异步Web请求处理 DeferredResult * 校验方面;Spring 引入了校验注解@Validated不但整合了JSR-303,而且还适配了Spring早期的Validator抽象 Spring Framework 4.x 驱动完善的时代 * 条件化注解@Conditional被引入 详细总结请看P153表格 #### Spring 注解编程模型 主要讨论一下几点 * 元注解 (Meta-Annotation) * Spring 模式注解 (Stereotype Annotations) * Spring 组合注解 (Composed Annoations) * Spring 注解属性别名和覆盖(Attribute Alizses and Overrides) ##### 元注解 (Meta-Annotation) 表示能够标记上的注解上的注解,例如java中的@Documented还有@Inherited spring中的@Component,他可以注解到@Service和@Repository上,所以他可以算是元注解 ##### Spring 模式注解 (Stereotype Annotations) 作者解释道可以这样理解Spring 模式注解 `Spring注解即@Component“派生”注解` 由于Java语法规范规定,Annotation不允许继承,没有类派生子类的能力,因此Spring framework采用元标注的方式实现注解之间的派生 **自定义@Component“派生”注解** ```java @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface StringRepository { //自定义一个我们自己的 Repository /** * 属性名名称必须与Component value 一致 * @return */ String value() default ""; } ``` ```xml ``` 并创建引导类 ```java public class DerivedComponentAnnotationBootStrap { //自定义StringRepository的引导类 static { /** * 解决spring 2.5 不兼容 java 8 的问题 * 同时,请注意Java Seurity策略 ,必须具备 PropertyPermission */ System.setProperty("java.version","1.7.0"); } public static void main(String[] args) { //构建驱动上下文 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(); context.setConfigLocation("classpath:/META-INF/spring/context.xml"); //启动 context.refresh(); //获取实例 NameRepository nameRepository = (NameRepository) context.getBean("chineseNameRepository"); System.out.println(nameRepository.findAll()); } } ``` 成功打印 ```c .... 22:11:43.132 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor' 22:11:43.140 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'chineseNameRepository' [张三, 李四, 王五] ```