springboot教程(一)
擼了今年阿里、頭條和美團的面試,我有一個重要發現.......>>>
使用jdk:1.8、maven:3.3.3
spring獲取Bean的方式
pom.xml文件內容:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>cn.ac.iie</groupId><artifactId>spring-course</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.0.RELEASE</version></dependency></dependencies> </project>配置類MyConfig.java:
package com.edu.spring;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;/*** 配置類*/@Configuration public class MyConfig {// 配置一個bean@Beanpublic MyBean createMyBean(){return new MyBean();}}MyBean.java
package com.edu.spring;public class MyBean { }主函數:App.java
package com.edu.spring;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class App {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);// 從容器中獲取bean---從類型獲取System.out.println(context.getBean(MyBean.class));// 從容器中獲取bean---從名字獲取,默認名字是方法名System.out.println(context.getBean("createMyBean"));context.close();} }輸出結果如下:
com.edu.spring.MyBean@1445d7f
com.edu.spring.MyBean@1445d7f
如果需要指定bean名字,需要修改MyConfig.java:
@Configuration public class MyConfig {// 配置一個bean@Bean(name = "myBean")public MyBean createMyBean(){return new MyBean();}}然后在App.java指定Bean的名字:這樣就無法根據方法名獲取Bean了。
public class App {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);// 從容器中獲取bean---從類型獲取System.out.println(context.getBean(MyBean.class));// 從容器中獲取bean---從名字獲取,默認名字是方法名// System.out.println(context.getBean("createMyBean"));// 從容器中獲取bean---從指定名字獲取System.out.println(context.getBean("myBean"));context.close();} }Bean默認是單例
我們可以看到,Bean是單例的,兩次打印出的對象是一樣的。
如果我們想修改Bean的單例為多例,修改MyConfig.java如下(添加scope(prototype)):
@Configuration public class MyConfig {// 配置一個bean@Bean(name = "myBean")@Scope("prototype")public MyBean createMyBean(){return new MyBean();}}打印如下:
com.edu.spring.MyBean@10b48321
com.edu.spring.MyBean@6b67034
FactoryBean
新建方法JeepFactoryBean.java
package com.edu.spring;import org.springframework.beans.factory.FactoryBean;public class JeepFactoryBean implements FactoryBean<Jeep> {/*** 創建的實例對象* @return* @throws Exception*/@Overridepublic Jeep getObject() throws Exception {return new Jeep();}/**** @return*/@Overridepublic Class<?> getObjectType() {return Jeep.class;}@Overridepublic boolean isSingleton() {return true;} }在MyConfig.java中添加配置:
@Beanpublic JeepFactoryBean createJeepFactoryBean(){return new JeepFactoryBean();}可以在App.java中得到Jeep.class。
System.out.println(context.getBean(Jeep.class)); System.out.println(context.getBean("createJeepFactoryBean"));如果要獲取JeepFactoryBean本身,而不是工廠生產出的類,可以通過下面的兩種方式獲取:
System.out.println(context.getBean(JeepFactoryBean.class)); System.out.println(context.getBean("&createJeepFactoryBean"));目前有兩種方式進行Bean的裝配,一種是使用FactoryBean,一種是原始方式
使用第三種裝配
新建CarFactory.java
public class CarFactory {public Car create(){return new Car();} }新建Car.java
配置MyConfig.java
@Beanpublic Car createJeep(CarFactory carFactory){return carFactory.create();}@Beanpublic CarFactory createCarFactory(){return new CarFactory();}獲取對象:
System.out.println(context.getBean(Car.class));因為,在Bean的裝配過程中,需要參數的時候,spring會默認從當前容器中獲取到對應的參數,然后注入。
Bean初始化,在Bean初始化時,進行一些操作。
方式一:
創建Cat.java
package com.edu.spring;import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean;public class Cat implements InitializingBean, DisposableBean {@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("====afterPropertiesSet====");}@Overridepublic void destroy() throws Exception {System.out.println("====destroy====");} }配置MyConfig.java
@Beanpublic Cat createCat(){return new Cat();}獲得cat
System.out.println(context.getBean(Cat.class));控制臺打印:
====afterPropertiesSet==== com.edu.spring.MyBean@7fe8ea47 com.edu.spring.MyBean@226a82c4 com.edu.spring.JeepFactoryBean@731f8236 com.edu.spring.JeepFactoryBean@731f8236 com.edu.spring.Jeep@255b53dc com.edu.spring.Jeep@255b53dc com.edu.spring.Car@1dd92fe2 com.edu.spring.Cat@6b53e23f ====destroy====方法二:
創建Dog.java
package com.edu.spring;public class Dog {public void myInit(){System.out.println("init=====");}public void myDestory(){System.out.println("destory===");}}在配置MyConfig.java時指定初始化時執行和銷毀時執行的方法
@Bean(initMethod = "myInit", destroyMethod = "myDestory")public Dog createDog(){return new Dog();}方式三:
新建Fish.java
package com.edu.spring;import javax.annotation.PostConstruct; import javax.annotation.PreDestroy;public class Fish {@PostConstructpublic void initial(){System.out.println("fish init");}@PreDestroypublic void close(){System.out.println("fish close");}}配置MyConfig.java
@Beanpublic Fish createFish(){return new Fish();}Bean裝配
新建User.java
package com.edu.spring;import org.springframework.stereotype.Component;@Component public class User { }修改App.java,將User.class添加到容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class, User.class); System.out.println(context.getBean(User.class));但是使用@Component,則無法使用@Bean(initMethod = "myInit", destroyMethod = "myDestory")設置初始化和銷毀的方法
此外,默認的名字是類名。可以指定名字:
@Component("myUser")如果此時同樣在MyConfig.java中,配置一個Bean,
@Beanpublic User createUser(){return new User();}則會報錯:
Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.edu.spring.User' available: expected single matching bean but found 2: myUser,createUserat org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1034)at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340)at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:335)at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1101)at com.edu.spring.App.main(App.java:24)也就是說通過類型獲取User時找到了兩個 一個是myUser一個是createUser。如果我們通過名字來獲取就沒有問題了。
System.out.println(context.getBean("myUser"));如果想獲取,User類型的所有對象,可以使用getBeansOfType方法。
除了使用Component,還可以使用Repository來裝配類,一般用在數據訪問層,而Component一般用于沒有明確角色的時候。
新建UserDao.java
@Repository public class UserDao { }添加到AnnotationConfigApplicationContext中
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class, User.class, UserDao.class); System.out.println(context.getBean(UserDao.class));還可以使用Service來裝配,一般用在業務邏輯層
新建UserService.java,
// 業務邏輯層 @Service public class UserService { }添加到AnnotationConfigApplicationContext:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class, User.class, UserDao.class, UserService.class); System.out.println(context.getBean(UserService.class));使用Controller來裝配,一般用于展示層,
package com.edu.spring;import org.springframework.stereotype.Controller;// 用在展示層 @Controller public class UserController { }依賴注入
在User中,依賴UserService.java
User.java
package com.edu.spring;import org.springframework.stereotype.Component;@Component("myUser") public class User {private UserService userService;public UserService getUserService() {return userService;}public void setUserService(UserService userService) {this.userService = userService;}@Overridepublic String toString() {return "User{" +"userService=" + userService +'}';} }在App.java中調用show方法
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class, User.class, UserDao.class, UserService.class, UserController.class); User user = context.getBean("myUser", User.class); user.show(); context.close();輸出打印:
null說明UserService沒有注入進去,使用@AutoWired修飾UserService,便可以注入進去了,同時也不需要set get方法了。
package com.edu.spring;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component("myUser") public class User {@Autowiredprivate UserService userService;@Overridepublic String toString() {return "UserService{" +"userDao=" + userDao +'}';}}打印輸出:
User{userService=UserService{userDao=com.edu.spring.UserDao@3bbc39f8}}說明成功注入進去了。
如果繼續在MyConfig.java中裝配一個Bean:
@Beanpublic UserDao createUserDao(){return new UserDao();}那么這時,就會有兩個UserDao存在,就不知道用哪個對象,出現報錯:
Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.edu.spring.UserDao' available: expected single matching bean but found 2: userDao,createUserDaoat org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1034)at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340)at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:335)at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1101)at com.edu.spring.App.main(App.java:26)這時可以用兩個方法解決問題。
方法一:使用Qualifier注釋
@Service public class UserService {@Autowired@Qualifier("createUserDao")private UserDao userDao;@Overridepublic String toString() {return "UserService{" +"userDao=" + userDao +'}';} }這樣就知道用哪一個對象了。(注意要將其他所有用到UserDao的地方,都需要使用Qualifier指明到底用哪一個userdao)
方法二:使用Primary注釋Bean
在MyConfig.java中:
@Bean@Primarypublic UserDao createUserDao(){return new UserDao();}Primary用于有多個對象存在時,首先尋找標有Primary的對象,進行注入。
如果要注入其他類,比如Car.java,需要使用Resource注釋來注入。
package com.edu.spring;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;import javax.annotation.Resource;@Component("myUser") public class User {@Autowiredprivate UserService userService;// 使用JSR 250的注解@Resourceprivate Car car;@Overridepublic String toString() {return "User{" +"userService=" + userService +", car=" + car +'}';} }輸出:User{userService=UserService{userDao=com.edu.spring.UserDao@1b083826}, car=com.edu.spring.Car@55a1c291}
說明Car.java 也注入進來了。
此外還可以使用 JSR 330 的注解方式進行注入,但是這種方式需要添加依賴:
<dependency><groupId>javax.inject</groupId><artifactId>javax.inject</artifactId><version>1</version></dependency>嘗試將Cat.java注入進去User。使用Inject注釋。
package com.edu.spring;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;import javax.annotation.Resource; import javax.inject.Inject;@Component("myUser") public class User {// Spring提供的注解@Autowiredprivate UserService userService;// 使用JSR-250的注解@Resourceprivate Car car;// 使用JSR-330的注解@Injectprivate Cat cat;@Overridepublic String toString() {return "User{" +"userService=" + userService +", car=" + car +", cat=" + cat +'}';} }控制臺顯示輸出:信息: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring 說明JSR330 已經起作用了。
User{userService=UserService{userDao=com.edu.spring.UserDao@55ca8de8}, car=com.edu.spring.Car@5d740a0f, cat=com.edu.spring.Cat@214b199c}
說明cat也成功注入進去了。
我們發現AnnotationConfigApplicationContext很麻煩,每次都需要手動去添加新的類。
我們可以使用包掃描的方式進行類裝載,更加方便。
package com.edu.spring;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class AnnotationClient {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.edu.spring");System.out.println(context.getBean(Jeep.class));System.out.println(context.getBean("createJeepFactoryBean"));System.out.println(context.getBean(Car.class));System.out.println(context.getBean(Cat.class));System.out.println(context.getBean(Dog.class));System.out.println(context.getBean(Fish.class));System.out.println(context.getBean("myUser"));context.close();} }如果我們想排除某一些類,這些類不想被掃描到。方法如下:
新建AnnotationScan.java
package com.edu.spring;import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration;@ComponentScan("com.edu.spring") @Configuration public class AnnotationScan { }新建AnnotationClient2.java
package com.edu.spring;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class AnnotationClient2 {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationScan.class);context.close();} }這種方法同樣可以達到掃描包的效果。如果我們想排除Dog類,需要將MyConfig.java中的配置Dog的地方刪除,新建DogConfig.java
package com.edu.spring;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration public class DogConfig {@Bean(initMethod = "myInit", destroyMethod = "myDestory")public Dog createDog(){return new Dog();}}修改AnnotationScan.java
package com.edu.spring;import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType;@ComponentScan(basePackages = "com.edu.spring", excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = DogConfig.class)) @Configuration public class AnnotationScan { }這樣就將Dog類排除了,然后執行AnnotationClient2.java 就會報錯:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.edu.spring.Dog' availableat org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:348)at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:335)at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1101)at com.edu.spring.AnnotationClient2.main(AnnotationClient2.java:13)說明Dog已經排除成功了。同樣可以排除UserController.java
因為UserController上有注釋,可以直接排除。
package com.edu.spring;import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType;@ComponentScan(basePackages = "com.edu.spring", excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {DogConfig.class, UserController.class})) @Configuration public class AnnotationScan { }第二課
Spring有兩個核心接口:BeanFactory和ApplicationContext,其中ApplicationContext是BeanFactory的子接口。他們都可代表Spring容器,Spring容器是生成Bean實例的工廠,并且管理容器中的Bean。
BeanFactory負責配置、創建、管理Bean,他有一個子接口:ApplicationContext,因此也稱之為Spring上下文。Spring容器負責管理Bean與Bean之間的依賴關系。
BeanFactory接口包含以下幾個基本方法:
- Boolean containBean(String name):判斷Spring容器是否包含id為name的Bean實例。
- <T> getBean(Class<T> requiredTypr):獲取Spring容器中屬于requiredType類型的唯一的Bean實例。
- Object getBean(String name):返回Sprin容器中id為name的Bean實例。
- <T> T getBean(String name,Class requiredType):返回容器中id為name,并且類型為requiredType的Bean
- Class <?> getType(String name):返回容器中指定Bean實例的類型。?
?調用者只需使用getBean()方法即可獲得指定Bean的引用,無須關心Bean的實例化過程。即Bean實例的創建過程完全透明。
讓Bean獲取Spring容器
????在Spring中我們可以使用Spring容器中getBean()方法來獲取Spring容器中的Bean實例。在這樣的訪問模式下,程序中總是持有Spring容器的引用。但是在實際的應用中,Spring容器通常是采用聲明式方式配置產生:即開發者只要在web.xml文件中配置一個Listener,該Listener將會負責初始化Spring容器。在這種情況下,容器中Bean處于容器管理下,無須主動訪問容器,只需要接受容器的注入管理即可。同時Bean實例的依賴關系通常也是由容器自動注入,無須Bean實例主動請求。
如何注入ApplicationContext?
新建myConfig.java
package com.edu.spring;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration public class MyConfig {@Beanpublic User createUser(){return new User();}}新建User.java
package com.edu.spring;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext;public class User {@Autowiredprivate ApplicationContext applicationContext;public void show(){System.out.println("user:" + applicationContext.getClass());}}新建App.java
package com.edu.spring;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class App {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.edu.spring");System.out.println(context.getBean(User.class));System.out.println(context.getBean("createUser"));context.getBean(User.class).show();context.close();} }方法一:
在Use.java中使用AutoWired成功注入ApplicationContext。也可以使用JSR 250 和JSR330 方式注入
輸出如下:
com.edu.spring.User@6ee12bac com.edu.spring.User@6ee12bac user:class org.springframework.context.annotation.AnnotationConfigApplicationContext方法二:
新建Book.java
package com.edu.spring;import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component;@Component public class Book implements ApplicationContextAware {private ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}public void show(){System.out.println("book:" + applicationContext.getClass());}}方法三:
新鍵Bank.java
package com.edu.spring;import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component;@Component public class Bank {private ApplicationContext applicationContext;public Bank (ApplicationContext applicationContext){this.applicationContext = applicationContext;}public void show(){System.out.println("bank:" + applicationContext.getClass());}}這種方法同樣可以注入ApplicationContext。但是這個方法,構造函數只能有一個,如果有多個的話,就必須有一個無參的構造函數,此時,spring會調用無參的構造函數。構造函數的參數,必須都要在Spring容器中。
BeanPostProcessor
BeanPostProcessor會在每個bean初始化的時候,調用一次
新建EchoBeanPostProcessor.java
package com.edu.spring;import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component;/*** BeanPostProcessor會在每個bean初始化的時候,調用一次*/ @Component public class EchoBeanPostProcessor implements BeanPostProcessor {// 在bean依賴裝配(屬性設置完)完成之后觸發@Nullable@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("====postProcessBeforeInitialization======" + bean.getClass());return bean;}// 在bean init方法執行之后觸發@Nullable@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("====postProcessAfterInitialization======" + bean.getClass());return bean;} }打印輸出如下:
====postProcessBeforeInitialization======class org.springframework.context.event.EventListenerMethodProcessor ====postProcessAfterInitialization======class org.springframework.context.event.EventListenerMethodProcessor ====postProcessBeforeInitialization======class org.springframework.context.event.DefaultEventListenerFactory ====postProcessAfterInitialization======class org.springframework.context.event.DefaultEventListenerFactory ====postProcessBeforeInitialization======class com.edu.spring.Bank ====postProcessAfterInitialization======class com.edu.spring.Bank ====postProcessBeforeInitialization======class com.edu.spring.Book ====postProcessAfterInitialization======class com.edu.spring.Book ====postProcessBeforeInitialization======class com.edu.spring.MyConfig$$EnhancerBySpringCGLIB$$3a1c64d1 ====postProcessAfterInitialization======class com.edu.spring.MyConfig$$EnhancerBySpringCGLIB$$3a1c64d1 ====postProcessBeforeInitialization======class com.edu.spring.User ====postProcessAfterInitialization======class com.edu.spring.User com.edu.spring.User@482cd91f com.edu.spring.User@482cd91f user:class org.springframework.context.annotation.AnnotationConfigApplicationContext book:class org.springframework.context.annotation.AnnotationConfigApplicationContext bank:class org.springframework.context.annotation.AnnotationConfigApplicationContext在每個bean初始化之前和之后執行的方法。????
如果我們給User添加一個初始化方法init
user.java
package com.edu.spring;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext;public class User {@Autowiredprivate ApplicationContext applicationContext;public void init(){System.out.println("user init");}public void show(){System.out.println("user:" + applicationContext.getClass());}}在MyConfig.java中指定初始化方法:
package com.edu.spring;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration public class MyConfig {@Bean(initMethod = "init")public User createUser(){return new User();}}則輸出結果如下:
====postProcessBeforeInitialization======class org.springframework.context.event.EventListenerMethodProcessor ====postProcessAfterInitialization======class org.springframework.context.event.EventListenerMethodProcessor ====postProcessBeforeInitialization======class org.springframework.context.event.DefaultEventListenerFactory ====postProcessAfterInitialization======class org.springframework.context.event.DefaultEventListenerFactory ====postProcessBeforeInitialization======class com.edu.spring.Bank ====postProcessAfterInitialization======class com.edu.spring.Bank ====postProcessBeforeInitialization======class com.edu.spring.Book ====postProcessAfterInitialization======class com.edu.spring.Book ====postProcessBeforeInitialization======class com.edu.spring.MyConfig$$EnhancerBySpringCGLIB$$3a1c64d1 ====postProcessAfterInitialization======class com.edu.spring.MyConfig$$EnhancerBySpringCGLIB$$3a1c64d1 ====postProcessBeforeInitialization======class com.edu.spring.User user init ====postProcessAfterInitialization======class com.edu.spring.User com.edu.spring.User@482cd91f com.edu.spring.User@482cd91f user:class org.springframework.context.annotation.AnnotationConfigApplicationContext book:class org.springframework.context.annotation.AnnotationConfigApplicationContext bank:class org.springframework.context.annotation.AnnotationConfigApplicationContext說明postProcessBeforeInitialization方法是在bean init方法之前執行,postProcessAfterInitialization方法是在bean init方法之后執行。
修改User.java
package com.edu.spring;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext;public class User {private ApplicationContext applicationContext;public void init(){System.out.println("user init");}public void show(){System.out.println("user:" + applicationContext.getClass());}@Autowiredpublic void setApplicationContext(ApplicationContext applicationContext) {System.out.println("applicationContext set");this.applicationContext = applicationContext;} }Autowired既可以用在變量上,也可以用在方法上。
輸出結果如下:
====postProcessBeforeInitialization======class org.springframework.context.event.EventListenerMethodProcessor ====postProcessAfterInitialization======class org.springframework.context.event.EventListenerMethodProcessor ====postProcessBeforeInitialization======class org.springframework.context.event.DefaultEventListenerFactory ====postProcessAfterInitialization======class org.springframework.context.event.DefaultEventListenerFactory ====postProcessBeforeInitialization======class com.edu.spring.Bank ====postProcessAfterInitialization======class com.edu.spring.Bank ====postProcessBeforeInitialization======class com.edu.spring.Book ====postProcessAfterInitialization======class com.edu.spring.Book ====postProcessBeforeInitialization======class com.edu.spring.MyConfig$$EnhancerBySpringCGLIB$$50df4f2b ====postProcessAfterInitialization======class com.edu.spring.MyConfig$$EnhancerBySpringCGLIB$$50df4f2b applicationContext set ====postProcessBeforeInitialization======class com.edu.spring.User user init ====postProcessAfterInitialization======class com.edu.spring.User com.edu.spring.User@5b0abc94 com.edu.spring.User@5b0abc94 user:class org.springframework.context.annotation.AnnotationConfigApplicationContext book:class org.springframework.context.annotation.AnnotationConfigApplicationContext bank:class org.springframework.context.annotation.AnnotationConfigApplicationContext說明依賴都裝配完成之后出發postProcessBeforeInitialization
代理對象
新建LogUser.java
package com.edu.spring;public class LogUser extends User {@Overridepublic void show() {System.out.println("log start ...");super.show();System.out.println("log end ...");} }修改Use.java
public void show(){System.out.println("user:" + applicationContext);}修改EchoBeanPostProcessor.java
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("====postProcessBeforeInitialization======" + bean.getClass());if(bean instanceof User){return new LogUser();}return bean;}輸出結果如下:
====postProcessBeforeInitialization======class org.springframework.context.event.EventListenerMethodProcessor ====postProcessAfterInitialization======class org.springframework.context.event.EventListenerMethodProcessor ====postProcessBeforeInitialization======class org.springframework.context.event.DefaultEventListenerFactory ====postProcessAfterInitialization======class org.springframework.context.event.DefaultEventListenerFactory ====postProcessBeforeInitialization======class com.edu.spring.Bank ====postProcessAfterInitialization======class com.edu.spring.Bank ====postProcessBeforeInitialization======class com.edu.spring.Book ====postProcessAfterInitialization======class com.edu.spring.Book ====postProcessBeforeInitialization======class com.edu.spring.MyConfig$$EnhancerBySpringCGLIB$$50df4f2b ====postProcessAfterInitialization======class com.edu.spring.MyConfig$$EnhancerBySpringCGLIB$$50df4f2b applicationContext set ====postProcessBeforeInitialization======class com.edu.spring.User user init ====postProcessAfterInitialization======class com.edu.spring.LogUser com.edu.spring.LogUser@75c072cb com.edu.spring.LogUser@75c072cb log start ... user:null log end ... book:class org.springframework.context.annotation.AnnotationConfigApplicationContext bank:class org.springframework.context.annotation.AnnotationConfigApplicationContext可以對指定的Bean做一些處理,比如返回對象的代理對象。
spring擴展二
目前我們知道BeanPostProcessor是在某個bean初始化的時候,進行回調的,我們可以控制bean的初始化和其他的操作。如果我們想要對某個容器進行初始化回調,如何做?spring同樣留了接口, BeanFactoryPostProcessor,是在spring容器初始化之后進行的
新建App.java
package com.edu.spring;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class App {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.edu.spring");context.close();} }新建MyBeanFactoryPostProcessor.java
package com.edu.spring;import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.stereotype.Component;/*** BeanFactoryPostProcessor在spring容器的初始化之后出發,而且只會觸發一次* 觸發的時機比BeanPostProcessor早*/ @Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {System.out.println("============" + configurableListableBeanFactory.getBeanDefinitionCount());} }運行App.java 輸出結果如下:
============7說明即使我們什么都不做,spring容器還是有很多個bean的。
新建User.java
public class User {public void init(){System.out.println("user init");} }新建MyConfig.java
package com.edu.spring;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration public class MyConfig {@Bean(initMethod = "init")public User createUser(){return new User();}@Beanpublic User createUser2(){return new User();}}運行App.java,輸出結果如下:
============10 user init新建MyBeanPostProcessor.java,對比BeanPostProcessor和BeanFactoryPostProcessor執行順序是什么
package com.edu.spring;import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component;@Component public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("====postProcessBeforeInitialization=====" + bean.getClass());return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("====postProcessAfterInitialization=====" + bean.getClass());return bean;} }運行輸出結果:
============11 四月 22, 2019 3:47:16 下午 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor <init> 信息: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring ====postProcessBeforeInitialization=====class org.springframework.context.event.EventListenerMethodProcessor ====postProcessAfterInitialization=====class org.springframework.context.event.EventListenerMethodProcessor ====postProcessBeforeInitialization=====class org.springframework.context.event.DefaultEventListenerFactory ====postProcessAfterInitialization=====class org.springframework.context.event.DefaultEventListenerFactory ====postProcessBeforeInitialization=====class com.edu.spring.MyConfig$$EnhancerBySpringCGLIB$$a601222d ====postProcessAfterInitialization=====class com.edu.spring.MyConfig$$EnhancerBySpringCGLIB$$a601222d ====postProcessBeforeInitialization=====class com.edu.spring.User user init ====postProcessAfterInitialization=====class com.edu.spring.User ====postProcessBeforeInitialization=====class com.edu.spring.User ====postProcessAfterInitialization=====class com.edu.spring.User說明BeanFactoryPostProcessor最先執行,但只會執行一次,因為容器執行完成一次。
BeanDefinitionRegistry
我們使用Component注釋來注冊一個bean,但這是靜態的注冊,我們可以通過BeanDefinitionRegistry來動態注冊一個Bean。
新建一個要注入的類Person.java
package com.edu.spring;public class Person {private String name;@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +'}';}public String getName() {return name;}public void setName(String name) {this.name = name;} }新建MyBeanDefinitionRegistryPostProcessor.java,注入十個Person類,并給他們的屬性賦值。
package com.edu.spring;import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.stereotype.Component;/*** BeanDefinitionRegistryPostProcessor可以拿到BeanDefinitionRegistry,ConfigurableListableBeanFactory兩個對象* BeanDefinitionRegistry可以動態注入bean*/ @Component public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {for( int i = 0; i < 10; i++){BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(Person.class);// 往person內注入屬性,可以注入引用beanDefinitionBuilder.addPropertyValue("name", "admin" + i);beanDefinitionRegistry.registerBeanDefinition("person" + i, beanDefinitionBuilder.getBeanDefinition());}}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {} }修改App.java
package com.edu.spring;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class App {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.edu.spring");context.getBeansOfType(Person.class).values().forEach(person -> {System.out.println(person);});context.close();} }輸出結果:
============22 四月 22, 2019 4:38:56 下午 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor <init> 信息: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring ====postProcessBeforeInitialization=====class org.springframework.context.event.EventListenerMethodProcessor ====postProcessAfterInitialization=====class org.springframework.context.event.EventListenerMethodProcessor ====postProcessBeforeInitialization=====class org.springframework.context.event.DefaultEventListenerFactory ====postProcessAfterInitialization=====class org.springframework.context.event.DefaultEventListenerFactory ====postProcessBeforeInitialization=====class com.edu.spring.MyConfig$$EnhancerBySpringCGLIB$$bc5fcd78 ====postProcessAfterInitialization=====class com.edu.spring.MyConfig$$EnhancerBySpringCGLIB$$bc5fcd78 ====postProcessBeforeInitialization=====class com.edu.spring.User user init ====postProcessAfterInitialization=====class com.edu.spring.User ====postProcessBeforeInitialization=====class com.edu.spring.User ====postProcessAfterInitialization=====class com.edu.spring.User ====postProcessBeforeInitialization=====class com.edu.spring.Person ====postProcessAfterInitialization=====class com.edu.spring.Person ====postProcessBeforeInitialization=====class com.edu.spring.Person ====postProcessAfterInitialization=====class com.edu.spring.Person ====postProcessBeforeInitialization=====class com.edu.spring.Person ====postProcessAfterInitialization=====class com.edu.spring.Person ====postProcessBeforeInitialization=====class com.edu.spring.Person ====postProcessAfterInitialization=====class com.edu.spring.Person ====postProcessBeforeInitialization=====class com.edu.spring.Person ====postProcessAfterInitialization=====class com.edu.spring.Person ====postProcessBeforeInitialization=====class com.edu.spring.Person ====postProcessAfterInitialization=====class com.edu.spring.Person ====postProcessBeforeInitialization=====class com.edu.spring.Person ====postProcessAfterInitialization=====class com.edu.spring.Person ====postProcessBeforeInitialization=====class com.edu.spring.Person ====postProcessAfterInitialization=====class com.edu.spring.Person ====postProcessBeforeInitialization=====class com.edu.spring.Person ====postProcessAfterInitialization=====class com.edu.spring.Person ====postProcessBeforeInitialization=====class com.edu.spring.Person ====postProcessAfterInitialization=====class com.edu.spring.Person Person{name='admin0'} Person{name='admin1'} Person{name='admin2'} Person{name='admin3'} Person{name='admin4'} Person{name='admin5'} Person{name='admin6'} Person{name='admin7'} Person{name='admin8'} Person{name='admin9'}說明Person自動注入進去了。
除了這個方法,也可以使用
AnnotationConfigApplicationContext context.registerBeanDefinition(beanName, beanDefinition);springboot-1
新建pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>cn.ac.iie</groupId><artifactId>spring-course</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.4.RELEASE</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency></dependencies> </project>新建App.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean;@SpringBootApplication public class App {@Beanpublic Runnable createRunable(){return () -> {System.out.println("spring boot is running");};}public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App.class, args);context.getBean(Runnable.class).run();} }運行App.java 可以得到輸出結果如下:
. ____ _ __ _ _/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) )' |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot :: (v2.1.4.RELEASE)2019-04-22 22:52:03.868 INFO 11372 --- [ main] com.edu.spring.App : Starting App on duandingyangdeMacBook-Pro.local with PID 11372 (/Users/duandingyang/git-project/springcourse/target/classes started by duandingyang in /Users/duandingyang/git-project/springcourse) 2019-04-22 22:52:03.876 INFO 11372 --- [ main] com.edu.spring.App : No active profile set, falling back to default profiles: default 2019-04-22 22:52:04.651 INFO 11372 --- [ main] com.edu.spring.App : Started App in 1.432 seconds (JVM running for 2.166) spring boot is running這是一個最簡單的SpringBoot應用。
跟之前的spring使用方法差不多,只是main的入口變了。
在pom.xml文件中,如果我們不想使用parent依賴,應該怎么做?
新的pom.xml如下
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>cn.ac.iie</groupId><artifactId>spring-course</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.1.4.RELEASE</version><scope>import</scope><type>pom</type></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency></dependencies> </project>在App.java中,點擊查看@SpringBootApplication注釋,有三個重要的注釋,分別是@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan
目前的情況,我們可以直接使用@ComponentScan注釋就行
App.java如下:
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan;@ComponentScan public class App {@Beanpublic Runnable createRunable(){return () -> {System.out.println("spring boot is running");};}public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App.class, args);context.getBean(Runnable.class).run();} }運行結果跟之前是一樣的。
其實SpringApplication.run(App.class, args)中App.class一般就是入口,也就是配置類,里面可以配置各種Bean。
刪掉@ComponentScan 修改入口配置
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan;public class App {@Beanpublic Runnable createRunable(){return () -> {System.out.println("spring boot is running");};}public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App2.class, args);context.getBean(Runnable.class).run();} }新建App2.java
package com.edu.spring;import org.springframework.context.annotation.ComponentScan;@ComponentScan public class App2 {}這樣輸出結果如下:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.Runnable' availableat org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:343)at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:335)at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1123)at com.edu.spring.App.main(App.java:21)因為找不到bean,如果將App.java中的生命bean 的代碼剪切到App2.java中,那么程序正常運行。
也可以通過這個方式加載配置類:
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext;import java.util.HashSet; import java.util.Set;public class App {public static void main(String[] args) {SpringApplication application = new SpringApplication(App2.class);ConfigurableApplicationContext context = application.run(args);context.getBean(Runnable.class).run();}}新建MyConfig.java
package com.edu.spring;import org.springframework.boot.SpringBootConfiguration; import org.springframework.context.annotation.Bean;import java.util.ArrayList; import java.util.List;@SpringBootConfiguration public class MyConfig {@Beanpublic List<String> createList(){ArrayList arrayList = new ArrayList();arrayList.add("a");return arrayList;}}使用SpringBootConfiguration同樣可以裝配Bean
App.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext;import java.util.List;public class App {public static void main(String[] args) {SpringApplication application = new SpringApplication(App2.class);ConfigurableApplicationContext context = application.run(args);context.getBean(Runnable.class).run();System.out.println(context.getBean(List.class));}}Springboot配置文件
springboot默認的配置文件是application.properties.
新建application.properties,內容如下:
local.ip=192.168.1.1 local.port=8080name=springboot app.name=this is ${name}方法一 使用context.getEnvironment().getProperty("local.ip")
新建App.java,
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringBootConfiguration; import org.springframework.context.ConfigurableApplicationContext;@SpringBootConfiguration public class App {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App.class, args);System.out.println(context.getEnvironment().getProperty("local.ip"));context.close();}}輸出:
192.168.1.1方法二:
新建UserConfig.java
package com.edu.spring;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component;@Component public class UserConfig {@Autowiredprivate Environment environment;public void show(){System.out.println("local.ip=" + environment.getProperty("local.ip"));}}修改App.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication public class App {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App.class, args);context.getBean(UserConfig.class).show();context.close();}}同樣可以輸出local.ip=192.168.1.1
方法三:
修改UserConfig.java, 使用@Value("${local.port}")注釋,來獲取
package com.edu.spring;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component;@Component public class UserConfig {@Value("${local.port}")private String localPort;/*** @Value 默認要有配置項,配置項可以為空,如果沒有配置項,則可以給默認值*/@Value("${tomcat.port:9090}")private String tomcatPort;@Autowiredprivate Environment environment;public void show(){System.out.println("local.ip=" + environment.getProperty("local.ip"));System.out.println("local.port=" + localPort);System.out.println("name=" + environment.getProperty("name"));System.out.println("app.name=" + environment.getProperty("app.name"));System.out.println("tomcat.port=" + tomcatPort);}}使用配置文件引用其他變量
public void show(){System.out.println("local.ip=" + environment.getProperty("local.ip"));System.out.println("local.port=" + localPort);System.out.println("name=" + environment.getProperty("name"));System.out.println("app.name=" + environment.getProperty("app.name"));}application.properties的位置是resources目錄下(即classpath根目錄)或者是resources/config目錄(即classpath:/config目錄)下。
如果要改application.properties的名字和目錄如何做?
可以在intellij工具 Program argument添加參數 --spring.config.name=文件名 可以省略文件擴展名
如果修改目錄 添加參數 --spring.config.location=classpath:conf/app.properties,? 必須指定擴展名。還可以指定多個路徑,用逗號隔開。之間的指定方式有兩種1.classpath: 2.file:
如果要加載其他配置文件,如何做?
新建jdbc.properties
url=jdbc:mysql:///springboot driverClassName=com.mysql.jdbc.Driver新建FileConfig.java
package com.edu.spring;import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource;@Configuration @PropertySource("classpath:jdbc.properties") public class FileConfig { }新建JdbcConfig.java
package com.edu.spring;import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;@Component public class JdbcConfig {@Value("${url}")private String url;@Value("${driverClassName}")private String driverClassName;public void show(){System.out.println("url=" + url);System.out.println("driverClassName=" + driverClassName);}}在App.java中
context.getBean(JdbcConfig.class).show();可以正常輸出。
如果是多個路徑下的文件,修改FileConfig.java
package com.edu.spring;import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource;@Configuration @PropertySource("classpath:jdbc.properties") @PropertySource("file:/e/tmp/jdbc.properties") public class FileConfig { }或者使用
package com.edu.spring;import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySources;@Configuration @PropertySources({@PropertySource("classpath:jdbc.properties"),@PropertySource("file:/e/tmp/jdbc.properties")}) public class FileConfig { }新建DataSourceProperties.java
package com.edu.spring;import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component;@Component @ConfigurationProperties(prefix = "ds") @PropertySource("classpath:/ds.properties") public class DataSourceProperties {private String url;private String driverClassName;private String username;private String password;public void show(){System.out.println("url:" + url);System.out.println("driverClassName:" + driverClassName);System.out.println("username:" + username);System.out.println("password:" + password);}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getDriverClassName() {return driverClassName;}public void setDriverClassName(String driverClassName) {this.driverClassName = driverClassName;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;} }新建ds.properties
ds.url=jdbc:mysql:///springboot ds.driverClassName=com.mysql.jdbc.Driver ds.username=root ds.password=123456springboot 也可以使用application.yml作為配置文件,是使用縮進方式書寫。
從配置文件中,注入集合與數組
新建TomcatProperties.java
package com.edu.spring;import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component;import java.util.ArrayList; import java.util.Arrays; import java.util.List;@Component @ConfigurationProperties("ds") public class TomcatProperties {private List<String> hosts = new ArrayList<>();private String[] ports;@Overridepublic String toString() {return "TomcatProperties{" +"hosts=" + hosts +", ports=" + Arrays.toString(ports) +'}';}public String[] getPorts() {return ports;}public void setPorts(String[] ports) {this.ports = ports;}public List<String> getHosts() {return hosts;}public void setHosts(List<String> hosts) {this.hosts = hosts;} }applicatioin.properties
ds.hosts[0]=192.168.1.100 ds.hosts[1]=192.168.1.101 ds.hosts[2]=192.168.1.102 ds.ports[0]=8080 ds.ports[1]=8081 ds.ports[2]=8082App.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication public class App {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App.class, args);System.out.println(context.getBean(TomcatProperties.class));context.close();}}注入正常。輸出結果:
TomcatProperties{hosts=[192.168.1.100, 192.168.1.101, 192.168.1.102], ports=[8080, 8081, 8082]}動態引入配置文件,使用EnvironmentPostProcessor接口
新建MyEnvironmentPostProcessor.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.stereotype.Component;import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Properties;@Component public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {@Overridepublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {try(InputStream inputStream = new FileInputStream("F:/test/springboot.properties")){Properties properties = new Properties();properties.load(inputStream);PropertiesPropertySource propertySource = new PropertiesPropertySource("my", properties);environment.getPropertySources().addLast(propertySource);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}} }測試輸出
System.out.println(context.getEnvironment().getProperty("springboot.name"));輸出結果為Null
說明沒有正確注入,在resources目錄下新建META-INF/spring.factories,內容如下:
org.springframework.boot.env.EnvironmentPostProcessor=com.edu.spring.MyEnvironmentPostProcessor執行輸出結果正常。原因是EnvironmentPostProcessor接口不屬于spring的,而是屬于Springboot的,因此需要配置到spring.factories。
有了這一功能我們就可以隨意的增加一些配置了,任意讀取配置,即配置文件中心化。
profile,開發階段和上線測試階段是不同的,如何在不同階段載入不同配置
新建application-dev.properties,內容如下
jdbc.url=mysql:jdbc://127.0.0.1/db_springboot_dev新建application-test.properties,內容如下:
jdbc.url=mysql:jdbc://127.0.0.1/db_springboot_testApp.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication public class App {public static void main(String[] args) {SpringApplication application = new SpringApplication(App.class);//application.setAdditionalProfiles("dev");application.setAdditionalProfiles("test");ConfigurableApplicationContext context = application.run(args);System.out.println(context.getBean(TomcatProperties.class));System.out.println(context.getEnvironment().getProperty("springboot.name"));System.out.println(context.getEnvironment().getProperty("jdbc.url"));context.close();}}這樣就可以實現切換。默認的application.properties也會加進去。
通過啟動參數來控制生效的profile,--spring.profiles.active=test,dev,這樣test和dev便同時啟用了。
新建MyConfig.java
package com.edu.spring;import org.springframework.boot.SpringBootConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Profile;@SpringBootConfiguration public class MyConfig {@Beanpublic Runnable createRunable(){System.out.println("===1==");return () -> {};}/*** 當application-test.properties激活之后才裝配這個Bean* @return*/@Bean@Profile("test")public Runnable createRunable2(){System.out.println("===2==");return () -> {};}/*** 當application-dev.properties激活之后才裝配這個Bean* @return*/@Bean@Profile("dev")public Runnable createRunable3(){System.out.println("===3==");return () -> {};}}可以當具體properties激活時,裝配Bean。profile注釋也可以用在類上,表示當某個profile生效時,使用這個類。
spring boot自動配置
新建接口EncodingConvert.java
package com.edu.spring;public interface EncodingConvert {}新建實現類GBKEncodingConvert.java
public class GBKEncodingConvert implements EncodingConvert { }新建實現類UTF8EncodingConvert.java
package com.edu.spring;public class UTF8EncodingConvert implements EncodingConvert { }新建EncodingConvertConfig.java
package com.edu.spring;import org.springframework.boot.SpringBootConfiguration; import org.springframework.context.annotation.Bean;@SpringBootConfiguration public class EncodingConvertConfig {@Beanpublic EncodingConvert createUTF8EncodingConvert(){return new UTF8EncodingConvert();}@Beanpublic EncodingConvert createGBKEncodingConvert(){return new GBKEncodingConvert();}}新建App.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication public class App {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App.class, args);System.out.println(System.getProperty("file.encoding"));System.out.println(context.getBeansOfType(EncodingConvert.class));context.close();}}運行App.java,打印輸出結果如下:
{createUTF8EncodingConvert=com.edu.spring.UTF8EncodingConvert@c9d0d6, createGBKEncodingConvert=com.edu.spring.GBKEncodingConvert@6ccdb29f}兩個Bean都裝配成功。
新建GBKCondition.java
package com.edu.spring;import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata;public class GBKCondition implements Condition {@Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {String encoding = System.getProperty("file.encoding");if(encoding != null){return "gbk".equals(encoding.toLowerCase());}return false;} }新建UTF8Condition.java
package com.edu.spring;import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata;public class UTF8Condition implements Condition {@Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {String encoding = System.getProperty("file.encoding");if(encoding != null){return "utf-8".equals(encoding.toLowerCase());}return false;} }在intellij 的VMoption參數添加 -Dfile.encoding=GBK
打印輸出:
{createGBKEncodingConvert=com.edu.spring.GBKEncodingConvert@512baff6}@Conditional 是基于條件的自動配置,一般配合Condition接口一起使用,只有接口(一個或多個)實現類都返回true才裝配,否則不裝配。
它可以用在方法上,則只對該方法起作用,還可以用在類上,則對該類的所有方法起作用。
此外,@Conditional({UTF8Condition.class, GBKCondition.class}),表示兩個條件都返回true才生效。
@ConditionalOnProperty,表示某個屬性等于某個值時。
新建UserConfiguration.java
package com.edu.spring;import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean;@SpringBootConfiguration public class UserConfiguration {@Bean@ConditionalOnProperty(name = "runnable.enabled", havingValue = "true")public Runnable createRunnable(){return ()->{};}}當application.properties中的屬性runnable.enabled=true時,這個Bean才能夠裝配。否則裝配不成功。matchIfMissing表示當這個配置不存在的時候,也為true。
修改UserConfiguration.java
package com.edu.spring;import com.google.gson.Gson; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean;@SpringBootConfiguration public class UserConfiguration {@Bean@ConditionalOnProperty(name = "runnable.enabled", havingValue = "true", matchIfMissing = true)public Runnable createRunnable() {return () -> {};}@Bean@ConditionalOnClass(name = "com.google.gson.Gson")public Runnable createGsonRunnable() {return () -> {};}}修改pom.xml,添加gson依賴
<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.1</version></dependency>@ConditionalOnClass表示當存在這個“com.google.gson.Gson”類的時候,就裝配這個Bean。
@ConditionalOnMissingClass表示當不存在這個“com.google.gson.Gson”類的時候,就裝配這個Bean。
打印輸出:
{createRunnable=com.edu.spring.UserConfiguration$$Lambda$137/800735172@335b5620, createGsonRunnable=com.edu.spring.UserConfiguration$$Lambda$138/478489615@29a0cdb}去掉pom.xml中的依賴是,就不會裝配這個bean。
@ConditionalOnBean??根據容器中存在某個bean來進行裝配
修改UserConfiguration.java
@Bean@ConditionalOnBean(name="user")public Runnable createBeanRunnable() {return () -> {};}運行時輸出:
{createRunnable=com.edu.spring.UserConfiguration$$Lambda$137/698741991@22356acd, createGsonRunnable=com.edu.spring.UserConfiguration$$Lambda$138/669284403@386f0da3}沒有輸出createBeanRunnable這個Bean,添加User.java
package com.edu.spring;import org.springframework.stereotype.Component;@Component public class User { }然后運行輸出:
{createRunnable=com.edu.spring.UserConfiguration$$Lambda$138/1934932165@27d4a09, createGsonRunnable=com.edu.spring.UserConfiguration$$Lambda$139/1508038883@7e4204e2, createBeanRunnable=com.edu.spring.UserConfiguration$$Lambda$140/728943498@b7c4869}說明成功配置了createBeanRunnable這個bean了。
@ConditionalOnMissiongBean??根據容器中不存在某個bean來進行裝配
Spring Boot @Enable*注解工作原理
新建TomcatProperties.java
package com.edu.spring;import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component;@Component @ConfigurationProperties(prefix = "tomcat") public class TomcatProperties {private String host;private String port;@Overridepublic String toString() {return "TomcatProperties{" +"host='" + host + '\'' +", port='" + port + '\'' +'}';}public String getHost() {return host;}public void setHost(String host) {this.host = host;}public String getPort() {return port;}public void setPort(String port) {this.port = port;} }application.properties
tomcat.host=192.168.1.100 tomcat.port=8080新建App.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication public class App {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App.class, args);System.out.println(context.getBean(TomcatProperties.class));context.close();}}運行輸出結果:
TomcatProperties{host='192.168.1.100', port='8080'}當我們修改App.java的注解時,
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan;/*** @EnableConfiguratinProperties是用來啟用一個特性的,這個特性可以把配置文件的屬性注入到bean里面去*/ @EnableConfigurationProperties @ComponentScan public class App {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App.class, args);System.out.println(context.getBean(TomcatProperties.class));context.close();}}程序也可以正常運行,但是當我們將注解@EnableConfiguratinProperties刪掉后,程序就獲取不到properties的內容了。說明是@EnableConfiguratinProperties起了作用。
如何在Springboot中啟動異步?
新建Jeep.java
package com.edu.spring;import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;@Component public class Jeep implements Runnable {@Overridepublic void run() {try {for(int i = 1; i<= 10; i++){System.out.println("============" + i);TimeUnit.SECONDS.sleep(1);}}catch (Exception e){e.printStackTrace();}} }修改App.java
context.getBean(Runnable.class).run(); System.out.println("----end-----");輸出結果如下:
TomcatProperties{host='192.168.1.100', port='8080'} ============1 ============2 ============3 ============4 ============5 ============6 ============7 ============8 ============9 ============10 ----end-----說明當run方法執行完之后,才輸出end。如何實現異步?
在App.java上啟動異步,添加注釋:
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableAsync;/*** @EnableConfiguratinProperties是用來啟用一個特性的,這個特性可以把配置文件的屬性注入到bean里面去,一般是和@ConfigurationProperties一起使用* @EnableAsync 啟用異步,一般是和@Async一起使用*/ @EnableConfigurationProperties @EnableAsync @ComponentScan public class App {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App.class, args);System.out.println(context.getBean(TomcatProperties.class));context.getBean(Runnable.class).run();System.out.println("----end-----");context.close();}}在Jeep.java的run方法上添加@Async注釋
package com.edu.spring;import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;@Component public class Jeep implements Runnable {@Async@Overridepublic void run() {try {for(int i = 1; i<= 10; i++){System.out.println("============" + i);TimeUnit.SECONDS.sleep(1);}}catch (Exception e){e.printStackTrace();}} }運行輸出結果如下:
----end----- ============1 ============2 ============3 ============4 ============5 ============6 ============7 ============8 ============9 ============10說明已經成功異步了。
新建User.java
package com.edu.spring;public class User { }新建Role.java
package com.edu.spring;public class Role { }新建App2.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan;@ComponentScan public class App2 {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App2.class, args);context.close();}}此時App2.java中無法獲取User的Bean和Role的Bean,想獲取的方式是:有很多,之前有講過,例如:@Component。今天講另一種方式:
修改App2.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import;@ComponentScan @Import(User.class) public class App2 {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App2.class, args);System.out.println(context.getBean(User.class));context.close();}}添加Import注釋,這樣就可以使用User的Bean了。
@Import注釋也可以導入一個數組,例如:@Import({User.class, Role.class})
此外,@Import還可以到如配置類
新建MyConfiguration.java
package com.edu.spring;import org.springframework.context.annotation.Bean;public class MyConfiguration {@Beanpublic Runnable createRunnable(){return () -> {};}@Beanpublic Runnable createRunnable2(){return () -> {};}}修改App2.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import;/*** @Import 用來導入一個或多個類(Bean會被Spring容器托管),或者配置類(配置類里面的bean會被Spring容器托管)*/ @ComponentScan @Import({User.class, Role.class, MyConfiguration.class}) public class App2 {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App2.class, args);System.out.println(context.getBean(User.class));System.out.println(context.getBeansOfType(Runnable.class));context.close();} }使用Import導入MyConfiguration,輸出結果:
com.edu.spring.User@6f27a732 {jeep=com.edu.spring.Jeep@6c779568, createRunnable=com.edu.spring.MyConfiguration$$Lambda$93/1730704097@f381794, createRunnable2=com.edu.spring.MyConfiguration$$Lambda$94/726379593@2cdd0d4b}說明成功導入了配置類。
ImportSelector是什么?
新建MyImportSelector.java
package com.edu.spring;import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata;/*** ImportSelector的方法的返回值,必須是一個class(全稱),該class會被spring容器托管起來。*/ public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{"com.edu.spring.User",Role.class.getName(), MyConfiguration.class.getName()};} }修改App2.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import;/*** @Import 用來導入一個或多個類(Bean會被Spring容器托管),或者配置類(配置類里面的bean會被Spring容器托管)*/ @ComponentScan //@Import({User.class, Role.class, MyConfiguration.class}) @Import(MyImportSelector.class) public class App2 {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App2.class, args);System.out.println(context.getBean(User.class));System.out.println(context.getBean(Role.class));System.out.println(context.getBeansOfType(Runnable.class));context.close();}}同樣可以輸出User的Bean,Role的Bean以及Runnable的Bean,說明裝配成功。
新建EnableLog.java
package com.edu.spring;import org.springframework.context.annotation.Import;import java.lang.annotation.*;@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MyImportSelector.class) public @interface EnableLog {String name(); }在MyImportSelector.java中修改:
package com.edu.spring;import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata;/*** ImportSelector的方法的返回值,必須是一個class(全稱),該class會被spring容器托管起來。*/ public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {System.out.println(importingClassMetadata.getAllAnnotationAttributes(EnableLog.class.getName()));/*** 這里可以獲取到注解的詳細信息。然后根據信息去動態的返回需要被spring容器管理的bean*/return new String[]{"com.edu.spring.User",Role.class.getName(), MyConfiguration.class.getName()};} }可以輸出注釋的信息。
修改APP2.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import;/*** @Import 用來導入一個或多個類(Bean會被Spring容器托管),或者配置類(配置類里面的bean會被Spring容器托管)*/ @ComponentScan //@Import({User.class, Role.class, MyConfiguration.class}) @Import(MyImportSelector.class) @EnableLog(name="my springboot") public class App2 {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App2.class, args);System.out.println(context.getBean(User.class));System.out.println(context.getBean(Role.class));System.out.println(context.getBeansOfType(Runnable.class));context.close();}}輸出結果可以輸出{name=[my springboot]},可以獲取到注解的詳細信息
@EnableAutoConfiguration深入分析
新建App.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication public class App {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App.class, args);System.out.println(context.getBean(Runnable.class));context.close();} }運行報錯,因為沒有Runnable這個bean。
新建項目:corebean
pom.xml文件:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.edu.core</groupId><artifactId>core-bean</artifactId><version>1.0.0</version><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.6.RELEASE</version></dependency></dependencies></project>新建RunnableConfiguration.java
package com.edu.core.bean;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration public class RunnableConfiguration {@Beanpublic Runnable createRunnable(){return () -> {};}}在springcourse項目的pom.xml中添加corebean項目依賴:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>cn.ac.iie</groupId><artifactId>spring-course</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.1.4.RELEASE</version><scope>import</scope><type>pom</type></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>com.edu.core</groupId><artifactId>core-bean</artifactId><version>1.0.0</version></dependency></dependencies> </project>在次運行App.java
同樣顯示沒有找到Runnable的bean類。
如果spring只支持當前項目中加載配置,那么他的擴展性太不好了,如何解決這個第三方jar的配置?
使用EnableAutoConfiguration注解
修改App.java,我們只用@EnableAutoConfiguration和@ComponentScan注釋
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan;/*** EnableAutoConfiguration 作用:從classpath中搜索所有META-INF/spring.factories配置文件,* 然后,將其中org.springframework.boot.autoconfigure.EnableAutoConfiguration key對應的配置項加載到spring容器中*/ @EnableAutoConfiguration @ComponentScan public class App {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App.class, args);System.out.println(context.getBean(Runnable.class));context.close();} }在resources目錄下創建META-INF/spring.factories,內容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.edu.core.bean.RunnableConfiguration運行App.java,輸出如下:
com.edu.core.bean.RunnableConfiguration$$Lambda$134/549293029@398dada8成功注入進去了Runnable的Bean。
如何配置多個?
在core-bean中新建User.java和Role.java以及一個配置類UserConfiguration.java內容分別如下:
package com.edu.core.bean;public class User { } package com.edu.core.bean;public class Role { } package com.edu.core.bean;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration public class UserConfiguration {@Beanpublic User createUser(){return new User();} }然后在springcourse的spring.factories中內容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.edu.core.bean.RunnableConfiguration,com.edu.core.bean.UserConfiguration,com.edu.core.bean.Role同樣成功注入進去了。這樣就實現了添加多個配置類。
只有在application.properties 文件中,spring.boot.enableautoconfiguration為true(默認為true)時,才啟用自動配置。默認不用配置時為true
其內部實現的關鍵點有:
?1. ImportSelector 該接口的方法的額返回值都會被納入到spring容器管理中
?2. SpringFactoriesLoader 該類可以從classpath中搜索所有META-INF/spring.factories配置文件,并讀取配置
?如果想要排除某些類,應該如何做?
方式一:通過class來排除
使用exclude
使用@EnableAutoConfiguration(exclude = UserConfiguration.class) 就將User的bean排除在外了。
方式二:通過類名來排除
使用@EnableAutoConfiguration(excludeName = "com.edu.core.bean.Role")
Springboot 事件監聽
事件流程:
? ? 1. 自定義事件,一般是繼承ApplicationEvent抽象類
? ? 2. 定義時間監聽器,一般是實現ApplicationListener接口
? ? 3. 啟動的時候,需要把監聽器加入到spring容器中
? ? 4. 發布事件,使用ApplicationContext的publishEvent發布事件
? ? 5. 配置監聽器
????? ? a. SpringApplication.addListeners 添加監聽器
????? ? b. 把監聽器納入到spring容器中管理 @Component
????? ? c. 可以使用配置項,在application.properties中配置context.listener.classes=com.edu.spring.MyApplicationListener? ?詳細內容參數:DelegatingApplicationListener
定義事件,新建MyApplicationEvent.java
package com.edu.spring;import org.springframework.context.ApplicationEvent;/*** 定義事件*/ public class MyApplicationEvent extends ApplicationEvent {public MyApplicationEvent(Object source) {super(source);} }定義監聽器,MyApplicationListener.java
package com.edu.spring;import org.springframework.context.ApplicationListener;public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> {@Overridepublic void onApplicationEvent(MyApplicationEvent event) {System.out.println("接收到事件:" + event.getClass());} }新建App.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan;@EnableAutoConfiguration(excludeName = "com.edu.core.bean.Role") @ComponentScan public class App {public static void main(String[] args) {SpringApplication application = new SpringApplication(App.class);application.addListeners(new MyApplicationListener());ConfigurableApplicationContext context = application.run(args);//發布事件context.publishEvent(new MyApplicationEvent(new Object()));context.close();} }運行結果:
接收到事件:class com.edu.spring.MyApplicationEvent說明監聽成功。
除了使用application.addListeners(new MyApplicationListener());這種方式添加監聽器,還有什么方式?
可以使用@Component注釋給MyApplicationListener.java。這樣也可以。
另一種方式配置監聽器:新建MyEventHandler.java
package com.edu.spring;import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component;@Component public class MyEventHandle {/*** 參數一定要是ApplicationEvent,或者其子類* @param event*/@EventListenerpublic void event(MyApplicationEvent event){System.out.println("接受到事件" + event.getClass());}}使用注解@EventListener也可以配置監聽器,且該類需要納入到spring容器重管理(詳細內容參照EventListenerFactory和EventListenerMethodProcessor),不用其他的配置了。如果參數設置成public void event(Object event) 那么所有的事件都能夠接收到。?
spring或者Springboot內部有哪些已經定義好的事件?
修改MyEventHandle.java
package com.edu.spring;import org.springframework.context.event.ContextStoppedEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component;@Component public class MyEventHandle {/*** 參數一定要是ApplicationEvent,或者其子類* @param event*/@EventListenerpublic void event(MyApplicationEvent event){System.out.println("接受到事件" + event.getClass());}@EventListenerpublic void event2(ContextStoppedEvent event){System.out.println("應用停止事件:" + event);}}在App.java中修改:
context.publishEvent(new MyApplicationEvent(new Object()));輸出結果:
接受到事件class com.edu.spring.MyApplicationEvent 應用停止事件:org.springframework.context.event.ContextStoppedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@314c508a, started on Thu Apr 25 17:07:00 CST 2019]監聽器起作用了。
spring boot擴展分析
新建MyApplicationContextInitializer.java
package com.edu.spring;import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext;public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println("bean count: " + applicationContext.getBeanDefinitionCount());} }新建MyApplicationContextInitializer2.java
package com.edu.spring;import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext;public class MyApplicationContextInitializer2 implements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println("app name: " + applicationContext.getDisplayName());} }新建App.java
package com.edu.spring;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication public class App {public static void main(String[] args) {SpringApplication application = new SpringApplication(App.class);application.addInitializers(new MyApplicationContextInitializer());ConfigurableApplicationContext context = application.run(args);context.stop();context.close();} }在application.properties中內容如下:
context.initializer.classes=com.edu.spring.MyApplicationContextInitializer, com.edu.spring.MyApplicationContextInitializer2運行App.java
輸出結果如下:
bean count: 5 app name: org.springframework.context.annotation.AnnotationConfigApplicationContext@1a052a00ApplicationContextInitializer 接口是在spring容器執行refreshed之前的一個回調
?使用步驟:
? ? ?1. 寫一個類,實現ApplicationContextInitializer接口
? ? ?2. 注冊ApplicationContextInitializer
?注冊方法:
? ? ?1. SpringApplication.addInitializers()
? ? ?2. 通過application.properties 配置context.initializer.classes=com.edu.spring.MyApplicationContextInitializer來進行,可以指定多個,多個用逗號隔開
? ? ?3. 通過spring.factories機制,(注冊listener監聽器也可以使用這種方式)
下面詳細說明如何通過spring.factories實現。
新建項目initializer。
其中pom.xml內容如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.edu.spring</groupId><artifactId>initializer</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.1.4.RELEASE</version><scope>import</scope><type>pom</type></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency></dependencies></project>新建EchoApplicationContextInitializer.java
package com.edu.spring.initializer;import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext;public class EchoApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic void initialize(ConfigurableApplicationContext configurableApplicationContext) {System.out.println("===EchoApplicationContextInitializer====");} }在resources下新建META-INF/spring.factories,內容如下:
org.springframework.context.ApplicationContextInitializer=com.edu.spring.initializer.EchoApplicationContextInitializer然后在springcourse項目的pom中導入Initializer項目
<dependency><groupId>com.edu.spring</groupId><artifactId>initializer</artifactId><version>1.0-SNAPSHOT</version></dependency>運行App.java,執行結果如下:
bean count: 5 app name: org.springframework.context.annotation.AnnotationConfigApplicationContext@ca263c2 ===EchoApplicationContextInitializer====說明成功注入。
CommandLineRunner接口回調
在springboot項目中,新建ServerSuccessReport.java
package com.edu.spring;import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component;@Component public class ServerSuccessReport implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("=====應用已經成功啟動=====");} }運行結果如下:
bean count: 5 app name: org.springframework.context.annotation.AnnotationConfigApplicationContext@27c6e487 ===EchoApplicationContextInitializer==== 2019-04-26 15:33:13.441 INFO 1396 --- [ main] com.edu.spring.App : Starting App on DESKTOP-AQM2529 with PID 1396 (E:\git\springcourse\target\classes started by vincent in E:\git\springcourse) 2019-04-26 15:33:13.443 INFO 1396 --- [ main] com.edu.spring.App : No active profile set, falling back to default profiles: default 2019-04-26 15:33:14.150 INFO 1396 --- [ main] com.edu.spring.App : Started App in 1.013 seconds (JVM running for 1.577) =====應用已經成功啟動=====從輸出結果中可以看到,在Started App啟動之后,輸出CommandLineRunner接口方法。
CommandLineRunner ApplicationRunner接口是在容器啟動成功后的最后一步的回調。類似開機自啟動
使用步驟:
? ? 1. 寫一個類,實現CommandLineRunner接口
? ? 2. 把該類納入到Spring容器中
? ? 3. 可以通過@Order注解或者Ordered接口來控制執行順序
如果有多個類實現了CommandLineRunner接口,如何保證執行順序?
新建ServerStartedReport.java
package com.edu.spring;import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component;import java.time.LocalDateTime;@Component public class ServerStartedReport implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("=========應用啟動后的時間是:" + LocalDateTime.now().toString());} }運行App.java,結果如下:
bean count: 5 app name: org.springframework.context.annotation.AnnotationConfigApplicationContext@7ff2a664 ===EchoApplicationContextInitializer==== 2019-04-26 15:44:03.081 INFO 2656 --- [ main] com.edu.spring.App : Starting App on DESKTOP-AQM2529 with PID 2656 (E:\git\springcourse\target\classes started by vincent in E:\git\springcourse) 2019-04-26 15:44:03.084 INFO 2656 --- [ main] com.edu.spring.App : No active profile set, falling back to default profiles: default 2019-04-26 15:44:03.775 INFO 2656 --- [ main] com.edu.spring.App : Started App in 1.0 seconds (JVM running for 1.426) =========應用啟動后的時間是:2019-04-26T15:44:03.784 =====應用已經成功啟動=====如果我們想要ServerSuccessReport類先執行,可以使用@Order注釋。
@Order(3) @Component public class ServerStartedReport implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("=========應用啟動后的時間是:" + LocalDateTime.now().toString());} } @Order(2) @Component public class ServerSuccessReport implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("=====應用已經成功啟動=====");} }只要Order括號內的數字越小,則越先執行。
新建StartedApplicationRunner.java
package com.edu.spring;import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component;import java.util.Arrays;@Component public class StartedApplicationRunner implements ApplicationRunner {@Overridepublic void run(ApplicationArguments args) throws Exception {System.out.println("應用已經啟動,參數為:" + Arrays.deepHashCode(args.getSourceArgs()));} }修改ServerSuccessReport.java的輸出
@Overridepublic void run(String... args) throws Exception {System.out.println("=====應用已經成功啟動=====" + Arrays.asList(args));}App.java添加參數:
@SpringBootApplication public class App {public static void main(String[] args) {SpringApplication application = new SpringApplication(App.class);//application.addInitializers(new MyApplicationContextInitializer());ConfigurableApplicationContext context = application.run("aa", "bb");context.stop();context.close();} }運行App.java,結果如下:
bean count: 5 app name: org.springframework.context.annotation.AnnotationConfigApplicationContext@78a2da20 ===EchoApplicationContextInitializer==== 2019-04-26 16:04:07.136 INFO 1684 --- [ main] com.edu.spring.App : Starting App on DESKTOP-AQM2529 with PID 1684 (E:\git\springcourse\target\classes started by vincent in E:\git\springcourse) 2019-04-26 16:04:07.138 INFO 1684 --- [ main] com.edu.spring.App : No active profile set, falling back to default profiles: default 2019-04-26 16:04:07.832 INFO 1684 --- [ main] com.edu.spring.App : Started App in 0.994 seconds (JVM running for 1.458) =====應用已經成功啟動=====[aa, bb] =========應用啟動后的時間是:2019-04-26T16:04:07.844 應用已經啟動,參數為:[aa, bb]CommandLineRunner ApplicationRunner區別:
區別在于方法的參數不一樣,CommandLineRunner的參數為最原始參數,沒有做任何處理,ApplicationRunner的參數是ApplicationArguments,是對原始參數做了進一步的封裝。
進一步說明ApplicationArguments的作用。
修改App.java
@SpringBootApplication public class App {public static void main(String[] args) {SpringApplication application = new SpringApplication(App.class);//application.addInitializers(new MyApplicationContextInitializer());ConfigurableApplicationContext context = application.run(args);ApplicationArguments arguments = context.getBean(ApplicationArguments.class);System.out.println(arguments.getSourceArgs().length);System.out.println(arguments.getOptionNames());System.out.println(arguments.getOptionValues("myname"));context.stop();context.close();} }修改Intellij 的運行參數,Program arguments 為 --myname admin,運行輸出結果如下:
bean count: 5 app name: org.springframework.context.annotation.AnnotationConfigApplicationContext@6a79c292 ===EchoApplicationContextInitializer==== 2019-04-26 16:19:12.343 INFO 8216 --- [ main] com.edu.spring.App : Starting App on DESKTOP-AQM2529 with PID 8216 (E:\git\springcourse\target\classes started by vincent in E:\git\springcourse) 2019-04-26 16:19:12.346 INFO 8216 --- [ main] com.edu.spring.App : No active profile set, falling back to default profiles: default 2019-04-26 16:19:13.001 INFO 8216 --- [ main] com.edu.spring.App : Started App in 0.959 seconds (JVM running for 1.401) =====應用已經成功啟動=====[--myname, admin] =========應用啟動后的時間是:2019-04-26T16:19:13.010 應用已經啟動,參數為:[--myname, admin] 2 [myname] []修改Intellij 的運行參數,Program arguments 為 --myname=admin,運行輸出結果如下:
bean count: 5 app name: org.springframework.context.annotation.AnnotationConfigApplicationContext@78a2da20 ===EchoApplicationContextInitializer==== 2019-04-26 16:20:08.312 INFO 11652 --- [ main] com.edu.spring.App : Starting App on DESKTOP-AQM2529 with PID 11652 (E:\git\springcourse\target\classes started by vincent in E:\git\springcourse) 2019-04-26 16:20:08.315 INFO 11652 --- [ main] com.edu.spring.App : No active profile set, falling back to default profiles: default 2019-04-26 16:20:08.956 INFO 11652 --- [ main] com.edu.spring.App : Started App in 0.939 seconds (JVM running for 1.469) =====應用已經成功啟動=====[--myname=admin] =========應用啟動后的時間是:2019-04-26T16:20:08.961 應用已經啟動,參數為:[--myname=admin] 1 [myname] [admin]ApplicationArguments是對參數(main方法),做了進一步處理
可以解析 --name=value的,我們就可以通過name來獲取value。如果我們用原始的方法進行獲取參數時,還得需要對參數進行分割。
Springboot補充講解
我們知道@SpringBootApplication注解有三個注解所組成的,分別是:@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan
在com.edu.spring.springboot包下面新建App.java
package com.edu.spring.springboot;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication public class App {public static void main(String[] args) {SpringApplication application = new SpringApplication(App.class);ConfigurableApplicationContext context = application.run(args);System.out.println(context.getBean(Runnable.class));context.close();} }在com.edu.spring.bean包下新建RunnableConfiguration.java
package com.edu.spring.bean;import org.springframework.boot.SpringBootConfiguration; import org.springframework.context.annotation.Bean;@SpringBootConfiguration public class RunnableConfiguration {@Beanpublic Runnable createRunnable(){return ()->{};}}運行App.java輸出結果如下:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.Runnable' availableat org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:343)at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:335)at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1123)at com.edu.spring.springboot.App.main(App.java:13)說明沒有成功注入。原因是@SpringBootApplication掃描的是當前包和子包下面的所有類。但是同一級的包是無法掃描到的??梢酝ㄟ^basePackage來指定。修改App.java如下:
package com.edu.spring.springboot;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication(scanBasePackages = "com.edu.spring") public class App {public static void main(String[] args) {SpringApplication application = new SpringApplication(App.class);ConfigurableApplicationContext context = application.run(args);System.out.println(context.getBean(Runnable.class));context.close();} }這樣就成功注入了。
排除某些類,方法如下:
修改pom.xml,添加gson依賴。
<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId></dependency>因為gson依賴,已經在springboot父類中定義,所以不需要指定version。
修改RunnableConfiguration.java
@Beanpublic Gson createGson(){return new Gson();}修改App.java
System.out.println(context.getBean(Gson.class));輸出結果正常。說明成功注入進去。
排除Gson的bean。修改App.java
@SpringBootApplication(scanBasePackages = "com.edu.spring", exclude = GsonAutoConfiguration.class)沒有排除掉,目前這有問題。
排除指定類、配置類,exclude:根據class來排除,excludeName:根據class name來排除。
自定義Banner方法
我們每次輸出的時候都會輸出spring的Banner:
. ____ _ __ _ _/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) )' |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/如何控制不輸出呢?
修改App.java如下:
SpringApplication application = new SpringApplication(App.class); application.setBannerMode(Banner.Mode.OFF);這樣就不輸出Spring的Banner了。
還可以自定義Banner,在resources目錄下新建banner.txt
然后就可以執行成功了。注意不要把Banner.Mode.OFF開啟。
第二種方式自定義Banner的方法是,在application.properties文件中寫文件路徑。支持圖片的Banner,圖片的格式支持jpg,png,gif
spring.banner.location=banner2.txt spring.banner.image.location=banner2.jpg讀取application.properties時如果沒有key則獲取默認的value,方法如下:
方法一:在@value注釋上使用默認的value
@Value(("${server.host:localhost}"))private String serverHosts;方法二:使用getProperty方法:
System.out.println(context.getEnvironment().getProperty("server.host2","localhost2"));spring boot 運行流程分析
spingboot的執行入口有兩個分別是:
// 實例化SpringApplication對象,然后調用run方法 SpringApplication application = new SpringApplication(App.class); ConfigurableApplicationContext context = application.run(args); // 直接調用靜態run方法(內部轉換成第一種調用方式) ConfigurableApplicationContext context = SpringApplication.run(App.class, args);? 運行流程
? ?1. 判斷是否是web環境
? ?2. 加載所有classpath下面的META-INF/spring.factories, ?ApplicationContextInitializerr
? ?3. 加載所有classpath下面的META-INF/spring.factories, ApplicationListener
? ?4. 推斷main方法所在的類
? ?5. 開始執行run方法
? ?6. 設置java.awt.headless系統變量
? ?7. 加載所有classpath下面的META-INF/spring.factories SpringApplicationRunListener
? ?8. 執行所有SpringApplicationRunListener的started方法
? ?9. 實例化ApplicationArguments對象
? ?10. 創建Environment
? ?11. 配置Environment,主要是把run方法的參數配置到Environment
? ?12. 執行所有SpringApplicationRunListener的environment.prepared方法
? ?13. 如果不是web環境,但是是web的Environment,則把web的Environment轉換成標準的Environment
? ?14. 輸出Banner
? ?15. 初始化applicationContext,如果是web環境,則實例化AnnotationConfigEmbeddedWebApplicationContext對象,否則實例化AnnotationConfigApplicationContext對象
? ?16. 如果beanNameGenerator不為空,就把beanNameGenerator對象注入Context里面去
? ?17. 回調所有的ApplicationContextInitializer方法
? ?18. 執行所有SpringApplicationRunListener的contextPrepared方法
? ?19. 依次往Spring容器中注入:ApplicationArguments, Banner
? ?20. 加載所有的源到context里面去。
? ?21. 執行所有SpringApplicationRunListener的contextLoaded方法
? ?22. 執行context的refresh方法,并且調用context的registerShutdownHook方法
? ?23. 回調,獲取容器中所有的ApplicationRunner, CommandLineRunner接口,然后排序,依次調用
? ?24. 執行所有SpringApplicationRunListener的finished的finished方法
總結
以上是生活随笔為你收集整理的springboot教程(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: docker下部署Tomcat运行war
- 下一篇: springboot教程-web(二)