日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

springboot教程(一)

發布時間:2024/9/16 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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=123456

springboot 也可以使用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]=8082

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{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_test

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.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@1a052a00

ApplicationContextInitializer 接口是在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教程(一)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

中国一级片视频 | 国产精品九九九九九 | 91麻豆精品久久久久久 | 中文字幕成人网 | 婷香五月 | 亚洲成人999 | 欧洲一区二区三区精品 | 91免费在线视频 | 香蕉久久久久 | 免费日韩 精品中文字幕视频在线 | 成年性视频 | 91成人精品观看 | 少妇搡bbbb搡bbb搡aa | 66av99精品福利视频在线 | 国产精品国产三级国产aⅴ入口 | 日韩精品久久久久久久电影竹菊 | 欧美日产一区 | 亚洲日本国产精品 | 午夜少妇一区二区三区 | 色七七亚洲影院 | 久久久国产精品人人片99精片欧美一 | 日韩黄色av网站 | 奇米网网址 | 婷婷5月激情5月 | 久久久久免费网站 | 高潮毛片无遮挡高清免费 | 久久人人爽人人 | 欧美午夜久久久 | 四虎国产精品成人免费4hu | 麻豆成人精品 | 亚洲午夜剧场 | 精品 激情 | 91精品国产综合久久久久久久 | 久久久久久久综合色一本 | 亚洲视频在线看 | 99久久精品免费看国产麻豆 | www五月天| 日韩亚洲国产精品 | 国产精品永久久久久久久www | 亚洲天堂网视频在线观看 | 国产一区二三区好的 | 亚洲精品国偷自产在线91正片 | 丝袜美腿亚洲 | 黄色网在线免费观看 | 日韩免费看片 | 久久久久久综合网天天 | 在线免费高清一区二区三区 | 美女国内精品自产拍在线播放 | 国产乱对白刺激视频在线观看女王 | 国产精品免费视频观看 | 欧美韩国日本在线 | 97超碰国产精品女人人人爽 | 成年人视频免费在线播放 | 伊人国产在线观看 | 欧美黑吊大战白妞欧美 | 91一区二区三区久久久久国产乱 | 成全在线视频免费观看 | 8x成人在线 | 黄色日本片 | 久久夜色电影 | 九九九电影免费看 | 国产精品国产三级国产aⅴ入口 | 国产日韩精品一区二区三区在线 | www.久草视频 | 欧美成年黄网站色视频 | 欧美久久久 | 久久午夜电影网 | 中文字幕一区二区三区精华液 | 91在线观看高清 | 亚洲精品久久久久58 | 日本免费久久高清视频 | 亚洲日本欧美 | 国产精品永久免费视频 | www.久久色.com | 亚洲美女视频在线观看 | 久久久久一区二区三区四区 | 日韩精品欧美专区 | 亚洲精品国产成人 | 欧美一级久久久久 | 日韩视频1 | 欧美福利网址 | 成年人在线 | 五月婷婷综合久久 | 亚洲综合欧美日韩狠狠色 | 国产成人av网址 | 精品国产99国产精品 | 免费a级黄色毛片 | 国产视 | 婷婷中文在线 | 黄色成人av网址 | 国产精品久久久久久久久久免费看 | 国产91小视频 | 狠狠躁日日躁狂躁夜夜躁av | 丁香激情网| 亚洲亚洲精品在线观看 | 又爽又黄又无遮挡网站动态图 | 日本韩国在线不卡 | 国产欧美日韩精品一区二区免费 | 久草在线资源网 | 亚州欧美精品 | 中文字幕免费观看 | 色婷婷天天干 | 国产精品99在线播放 | 成人国产一区二区 | 国产免费久久 | 天天干天天做 | 国内精品久久久久影院优 | 日韩电影在线一区 | 久久久久久黄色 | 国产成人一二三 | 久久男人影院 | 在线精品观看 | 日韩在线视频不卡 | 亚洲精品国产精品久久99 | 91麻豆精品国产91久久久无限制版 | 午夜视频在线观看一区二区三区 | 91视频这里只有精品 | 亚洲激情p | 二区三区中文字幕 | 国产成人精品一区二区 | 免费日韩一区二区三区 | 国产在线观看免费 | 国产精品美女视频 | 草久热| 久久九九久久精品 | 午夜日b视频| 久久视频网 | 天堂av观看| 一区二区免费不卡在线 | 日本大尺码专区mv | 久久爱资源网 | 国产福利精品在线观看 | 亚洲视频免费视频 | 亚洲精品九九 | 亚洲在线高清 | 最新国产精品拍自在线播放 | 天天摸天天操天天舔 | 私人av| 中文字幕字幕中文 | 成人免费av电影 | 玖玖在线资源 | 国产欧美在线一区 | 四虎国产视频 | 久综合网 | 天天综合人人 | www.狠狠操.com | 天天激情天天干 | www亚洲国产 | 婷婷丁香狠狠爱 | 国产精品久久三 | 久久久久久国产精品免费 | 97热视频 | 热久久精品在线 | 最新国产在线视频 | 麻豆视频免费看 | 中国一级特黄毛片大片久久 | 亚洲欧美观看 | 久久久久福利视频 | 国产亚洲精品久久久久久无几年桃 | 深夜免费网站 | 国产天天综合 | 毛片网站免费在线观看 | 最新中文字幕视频 | 日韩中文字幕亚洲一区二区va在线 | av免费观看网站 | 99久久久久久国产精品 | 亚洲精品国产精品乱码不99热 | 97视频免费在线观看 | 美女黄濒 | 日韩91精品 | 51久久夜色精品国产麻豆 | 国色天香av | 欧美va天堂va视频va在线 | 国产精品久久久久永久免费看 | 日韩一二三 | 欧美另类sm图片 | 欧美激情综合五月色丁香小说 | 免费在线观看av片 | 日韩欧美v | 久久久天天操 | 午夜久久久久久久久久久 | 精品久久中文 | av网站大全免费 | 免费在线黄网 | 中文字幕在线观看视频免费 | 国产在线精品区 | 经典三级一区 | 婷婷深爱网 | 婷婷色综 | 一本—道久久a久久精品蜜桃 | 在线成人免费电影 | www..com毛片| 天天干天天拍天天操天天拍 | 五月婷婷综合激情网 | 欧美精品色 | 久久av在线 | 99这里只有久久精品视频 | 国语久久 | 少妇bbb搡bbbb搡bbbb′ | 人人干人人干人人干 | 久操视频在线观看 | 超碰97人人在线 | 日韩欧美综合精品 | 国产在线色站 | www.xxxx变态.com | 91探花视频 | 中文字幕亚洲欧美日韩 | 亚洲欧洲av | 亚洲最新av| 久久久国产电影 | 日韩精品一区二区三区水蜜桃 | 久久久99精品免费观看app | 天天操操操操操操 | 亚洲视频在线观看免费 | 在线精品视频免费播放 | 国内精品视频在线 | 在线观看视频一区二区 | 视频在线播放国产 | 四川bbb搡bbb爽爽视频 | 美女久久99 | 狠狠干2018| 九色在线 | 亚洲aⅴ免费在线观看 | 日韩av一区二区在线影视 | 亚洲成a人片在线观看中文 中文字幕在线视频第一页 狠狠色丁香婷婷综合 | 婷婷干五月 | 在线观看黄色av | 狠狠操狠狠干天天操 | 国内精品在线一区 | 99精品偷拍视频一区二区三区 | 亚洲最新av网址 | 啪啪精品 | 91av99| 在线国产一区二区三区 | 中文一区在线 | 亚洲不卡av一区二区三区 | 国产精品久久久久久久婷婷 | 在线观看成人av | 草久草久 | 国产一区二区久久精品 | 十八岁免进欧美 | 日韩欧美在线免费观看 | 国产高清成人 | 精品一区精品二区高清 | 免费看的黄色小视频 | 日韩91在线| 黄色片网站免费 | 国产视频网站在线观看 | 激情综合电影网 | www.黄色片网站 | 成人97人人超碰人人99 | 欧美成人精品欧美一级乱黄 | 又长又大又黑又粗欧美 | 99精品视频免费观看视频 | 国产 中文 日韩 欧美 | 91精品在线麻豆 | 久久精品国产久精国产 | 久久 国产一区 | 久久国产一区 | 国产成人免费网站 | 国产中的精品av小宝探花 | 成人在线一区二区 | 亚洲精品自在在线观看 | 97色se | 狠狠干狠狠色 | 99在线精品免费视频九九视 | 久久精精品 | 日韩丝袜在线观看 | 久久av伊人 | 五月天电影免费在线观看一区 | 欧美最猛性xxxxx(亚洲精品) | 久久99精品久久久久久久久久久久 | 国产精品久久久久久爽爽爽 | 91看片黄色 | 久久国产视屏 | 久久国产免 | 狂野欧美激情性xxxx | 国产精品精品久久久久久 | 国产日韩精品一区二区三区在线 | 在线播放亚洲激情 | 99国产精品久久久久久久久久 | 亚洲一区二区精品3399 | 日韩av网站在线播放 | 久久综合视频网 | 美女久久一区 | 97av精品| 国产精品第52页 | 麻豆一级视频 | 国产精品日韩欧美一区二区 | 99热这里只有精品国产首页 | 久久艹国产视频 | 免费看三级黄色片 | 九九九在线观看 | 日韩欧美国产精品 | 欧美亚洲国产日韩 | 视频一区亚洲 | 亚洲精品在线观看的 | 亚洲国产视频在线 | 精品国产乱码久久久久久天美 | 亚洲丁香日韩 | 精品免费一区二区三区 | 国产高清综合 | 国产精品欧美久久久久久 | 欧美一级久久 | 国产精品入口66mio女同 | 啪啪免费视频网站 | 丁香婷婷激情 | 国产高清黄 | 成人久久久久久久久久 | 欧美成年人在线视频 | 色在线亚洲 | 国产黄色精品视频 | 免费中文字幕在线观看 | 一区二区三区动漫 | 久草久草视频 | 高清精品久久 | 午夜av在线电影 | 91最新视频| 久久国产精品久久国产精品 | 免费在线观看中文字幕 | 国产精品18久久久久久久网站 | 一级黄色片在线免费看 | 成人高清在线观看 | 2024av| 人人玩人人添人人澡97 | 国产在线观看中文字幕 | 国产免码va在线观看免费 | 狠狠激情中文字幕 | 亚洲精品88欧美一区二区 | 亚洲精品国产精品国产 | 在线成人一区二区 | 亚洲精品视频免费 | 国产福利中文字幕 | 人人插人人插 | 色婷五月| 亚洲国产欧美一区二区三区丁香婷 | 欧美性生交大片免网 | 三级av小说| 亚洲国产精品视频在线观看 | 香蕉网在线播放 | 免费在线黄色av | 国产高清在线观看 | 91看毛片 | 日韩黄色免费看 | 91丨九色丨国产在线 | 日日干日日| 怡春院av | 成人久久综合 | 久久久免费电影 | 欧美精品你懂的 | 国产对白av | 成人在线免费小视频 | 午夜久久久久久久久久久 | 精品久久久久久久久久岛国gif | 日韩中文字幕在线不卡 | 亚洲片在线资源 | 国产 日韩 欧美 在线 | 探花视频在线观看免费版 | 视频在线在亚洲 | 丝袜网站在线观看 | 中文字幕在线播放视频 | 久久久亚洲国产精品麻豆综合天堂 | www久久九 | 国内精品免费 | 亚洲国产高清视频 | 亚洲精品xxxx| 免费色av| 色综合天天视频在线观看 | 99在线热播精品免费99热 | 中文字幕免费高清在线 | 人人爽人人爽人人爽 | 丁香六月综合网 | 天天爱天天干天天爽 | 婷婷看片 | 99久久精品国产亚洲 | 免费精品国产va自在自线 | www.xxxx欧美| 奇米先锋 | 在线免费高清 | 在线影院 国内精品 | 国内毛片毛片 | 最近中文字幕免费视频 | 国产亚洲精品久久久久久 | 五月婷婷久久丁香 | 国产综合片 | 久艹视频在线观看 | 人人爱人人舔 | 日韩欧美在线免费观看 | 99热这里| 视频三区在线 | 黄污网站在线 | 免费精品国产 | www.色午夜.com | 天天操狠狠操网站 | 日本不卡123| 日本精油按摩3 | 久久久黄色| 亚洲激情p| 亚洲在线精品视频 | 91麻豆文化传媒在线观看 | 色黄久久久久久 | 免费观看黄色12片一级视频 | 免费成人av电影 | 亚洲综合婷婷 | 免费高清在线视频一区· | 8x8x在线观看视频 | 久久精品一二区 | 精品一区中文字幕 | zzijzzij日本成熟少妇 | 9992tv成人免费看片 | 中文字幕在线网址 | 久久免费成人 | 韩国av不卡 | 日韩在线观看三区 | 91日韩免费 | av888av.com | 天天操人人要 | 免费网址在线播放 | 久久人人插 | 午夜av在线电影 | 久久久受www免费人成 | 亚洲无吗视频在线 | 亚洲黄色小说网 | 免费日韩av片 | 久久私人影院 | 亚洲另类xxxx| 亚洲欧美日韩精品久久奇米一区 | 亚洲国产精品久久久久 | 手机在线视频福利 | 国产色女人 | 日韩在线视 | 日韩一二区在线 | 色丁香婷婷 | 天天射,天天干 | 国产中文字幕在线视频 | 国产精品久久久一区二区 | 日韩在线短视频 | 青青啪| 激情婷婷在线 | 超碰av在线 | 69av网| 亚洲国产精品500在线观看 | 国产精品福利午夜在线观看 | japanesefreesexvideo高潮 | 天天操天天摸天天干 | 久久国产精品免费一区二区三区 | 国产视频资源在线观看 | 色视频在线 | 91女人18片女毛片60分钟 | 在线看片中文字幕 | 国产a精品 | av丝袜制服| 干亚洲少妇 | 9在线观看免费高清完整版在线观看明 | 久久五月婷婷综合 | 久久亚洲热| 免费网站黄 | 天天拍天天干 | 中文字幕传媒 | 公与妇乱理三级xxx 在线观看视频在线观看 | av中文字幕在线播放 | 亚洲黄色免费在线看 | 91在线一区 | 99欧美| 在线免费观看黄网站 | 欧美午夜精品久久久久久孕妇 | 亚洲免费av在线播放 | 日韩中文字幕电影 | 激情欧美国产 | 久久黄页| 97人人看| 亚州视频在线 | 久久这里有精品 | 中文字幕精品一区二区精品 | 国产在线观看免费av | 精品国产一区在线观看 | 色婷婷国产 | jizz18欧美18 | 亚洲国产福利视频 | 色天堂在线视频 | 成人av av在线 | 黄色avwww| 久草免费在线观看视频 | 麻豆免费视频 | 91在线免费播放视频 | 久久久2o19精品 | 最新真实国产在线视频 | 国产破处视频在线播放 | 国产精品久免费的黄网站 | 激情综合色综合久久综合 | 热精品 | 亚洲国产精品va在线 | 激情亚洲综合在线 | 婷婷综合成人 | 麻豆精品视频在线 | 久久免费视频精品 | 国产精品视频999 | 久久久久久久久久久久av | 在线观看91av | 国产a国产a国产a | 天天综合区 | 国产一区不卡在线 | 一区二区三区电影在线播 | 91在线精品秘密一区二区 | 国产97在线播放 | 十八岁以下禁止观看的1000个网站 | 国产黄色片一级三级 | 色吊丝在线永久观看最新版本 | 久久天天操| 国产精品久久久久aaaa九色 | 免费一级日韩欧美性大片 | 国偷自产中文字幕亚洲手机在线 | 精品一区 在线 | 久久久久久国产精品999 | 五月天激情综合 | 成人av高清在线观看 | 亚洲视频 视频在线 | 成人资源站 | 久久久久久久久亚洲精品 | 91视频高清 | 免费电影一区二区三区 | 夜夜澡人模人人添人人看 | 亚洲高清视频一区二区三区 | 日韩av网址在线 | 国产精品视频久久久 | 精品视频一区在线 | 蜜桃视频在线观看一区 | 精品亚洲视频在线观看 | 国语自产偷拍精品视频偷 | 综合婷婷丁香 | 国产精品免费视频网站 | 欧美性生活免费 | 欧美日韩视频观看 | 在线性视频日韩欧美 | 日本久久久久久久久久久 | 久久精品3| 五月婷香蕉久色在线看 | 亚洲免费永久精品国产 | 亚洲国产精品一区二区尤物区 | 国产小视频你懂的在线 | 国产精品va最新国产精品视频 | 欧美性一级观看 | 国产一级二级视频 | 日韩一区正在播放 | 超碰99在线 | 草久久久久| 国产中文字幕亚洲 | 91视频麻豆 | 97理论片 | 911香蕉视频 | 久久国产视频网站 | 激情图片区 | 中文字幕av最新 | 国产精品久久一区二区三区不卡 | 久久一本综合 | 国产精品婷婷午夜在线观看 | 日本三级不卡 | 色婷婷福利 | 婷婷在线视频观看 | 国产精品久免费的黄网站 | 中文字幕在线有码 | 久久区二区 | 国产精品手机看片 | 日韩电影在线观看一区二区三区 | 在线免费色视频 | 亚洲综合视频在线播放 | 深夜免费福利在线 | 人人插人人草 | 亚洲国产午夜 | 日韩激情av在线 | 国产麻豆传媒 | a级片网站 | 国产麻豆成人传媒免费观看 | 综合色中色 | 91男人影院 | 亚洲成人午夜在线 | 激情综合色综合久久综合 | 高清不卡一区二区在线 | 午夜视频在线观看一区二区三区 | 免费日韩一区 | 午夜影视剧场 | 国产一区二区三区久久久 | 天天干天天操人体 | 久久久免费精品 | 国产福利在线免费观看 | 国产亚洲va综合人人澡精品 | 免费a视频在线 | 亚洲国产高清在线观看视频 | 欧美日韩国产高清视频 | 欧美日韩免费视频 | 亚洲在线资源 | 亚洲成av | 黄色aa久久 | 亚洲在线视频播放 | 99久在线精品99re8热视频 | 国产99久久久精品视频 | www.在线观看av| 狠狠插天天干 | av色综合网| 黄色电影在线免费观看 | 国产精品毛片久久久久久 | 干综合网 | 婷婷丁香激情网 | 国产一级大片在线观看 | 又黄又爽又刺激视频 | 一级理论片在线观看 | 欧美精品一区二区免费 | 亚洲黄色影院 | 五月天久久久久 | 中文字幕日本在线观看 | 精品久久久精品 | 色视频在线免费观看 | 国产打女人屁股调教97 | 午夜精品久久一牛影视 | 久久夜视频 | 99免费看片 | 国产免费影院 | 丁香花在线观看免费完整版视频 | 在线观看国产亚洲 | 久久精品96 | 日韩欧美精品在线 | 亚洲精品国偷拍自产在线观看 | 国内精品久久天天躁人人爽 | 久草精品在线观看 | 欧美男同视频网站 | 亚洲精品日韩在线观看 | 有码中文字幕在线观看 | 人人要人人澡人人爽人人dvd | 在线看国产视频 | 美女久久99 | 久久精品一| 97夜夜澡人人爽人人免费 | 91精品久久久久 | 欧美小视频在线 | 精品视频久久久 | 高清一区二区 | 久久视精品 | 亚洲欧美日韩中文在线 | 日本色小说视频 | 日本三级在线观看中文字 | 91禁在线看 | 久草久草视频 | 亚洲人人精品 | 亚洲国产电影在线观看 | 国产一级黄色片免费看 | 国产成人综 | 日韩av片无码一区二区不卡电影 | 在线国产99 | 国产视频丨精品|在线观看 国产精品久久久久久久久久久久午夜 | 天堂在线一区二区 | 久久综合九色综合97_ 久久久 | 国产精品久久久999 国产91九色视频 | 九九热精品在线 | 亚洲成年片 | 色婷婷狠狠五月综合天色拍 | 久草在线资源免费 | 国产黄色一级片在线 | 国产精品麻豆99久久久久久 | 久久99精品久久久久久秒播蜜臀 | 亚洲精品国产精品国自产观看浪潮 | 亚洲va在线va天堂va偷拍 | 综合网天天色 | 久久精品8| 欧美精品久| 欧美精品一二 | 色婷婷av一区 | 懂色av懂色av粉嫩av分享吧 | 国产在线观看h | 不卡电影免费在线播放一区 | 国产剧情一区在线 | 2024av在线播放| 91成人精品国产刺激国语对白 | 狠狠狠狠狠狠干 | 亚洲午夜av久久乱码 | 日韩动态视频 | 日韩国产精品久久久久久亚洲 | 人人揉人人揉人人揉人人揉97 | 狠狠操狠狠干天天操 | 国产中文字幕大全 | 国产麻豆视频免费观看 | 国产精品igao视频网入口 | 久久久蜜桃一区二区 | 中文字幕一区二区三区在线观看 | 国精产品一二三线999 | 国产一区二区三区四区在线 | 丁香婷婷深情五月亚洲 | 在线日韩三级 | 日韩免费在线播放 | 久草在线免费在线观看 | 少妇自拍av| 天天干天天摸 | av福利在线免费观看 | 在线观看国产高清视频 | 婷婷开心久久网 | 99电影456麻豆 | 99欧美| 免费网站观看www在线观看 | 国产一级黄色免费看 | 中文字幕人成人 | 久草网视频在线观看 | 国产欧美精品在线观看 | 中文字幕免费久久 | 欧美国产日韩一区二区 | 500部大龄熟乱视频 欧美日本三级 | 激情在线五月天 | 日韩欧美高清在线 | 久久精品激情 | 国产午夜精品免费一区二区三区视频 | 亚洲精品乱码久久久久v最新版 | 最近中文字幕大全中文字幕免费 | 五月在线视频 | 又黄又爽又色无遮挡免费 | av高清在线观看 | 中文字幕丝袜 | 丁香高清视频在线看看 | 日韩黄色一级电影 | 国产情侣一区 | 免费黄在线观看 | 久久久久国产一区二区三区四区 | 久久精品这里精品 | 欧美国产视频在线 | 狠狠躁夜夜躁人人爽视频 | 国产精品久久99综合免费观看尤物 | 国产99在线播放 | 国产精品一区二区免费在线观看 | 午夜在线观看一区 | 久久精彩免费视频 | 成人精品影视 | 99精品国产免费久久久久久下载 | 91九色蝌蚪在线 | 91av超碰| 国产黄色精品在线 | 国产成人黄色av | 免费韩国av | 人人爽人人爽人人爽 | 操老逼免费视频 | 久久99久久99精品免观看粉嫩 | 成人手机在线视频 | 2024av| 精品视频成人 | 九九国产精品视频 | 欧美日韩二区在线 | 美女搞黄国产视频网站 | 免费a级毛片在线看 | 日韩av图片 | 色综合久久综合中文综合网 | 九九视频在线播放 | 精品久久久久久久 | 精品日韩视频 | 国产高清小视频 | a√资源在线 | 综合网在线视频 | 天天干天天做天天爱 | 亚洲日日射 | 9ⅰ精品久久久久久久久中文字幕 | 在线视频 你懂得 | 999久久久久久久久久久 | 又黄又爽的免费高潮视频 | 中文免费观看 | 精品久久久久久亚洲综合网站 | 久久精品欧美视频 | 人人插人人插 | 黄色av电影免费观看 | 精品国产乱码久久久久久1区2匹 | 天天鲁一鲁摸一摸爽一爽 | 在线免费黄网站 | 成人禁用看黄a在线 | 中国一级片在线播放 | 91精品一区在线观看 | 精品国内自产拍在线观看视频 | 国产一区二区视频在线 | 日韩激情小视频 | 91麻豆网 | 啪啪免费观看网站 | 黄色大片视频网站 | 99草视频 | 天天综合区 | 五月天综合 | 亚洲精品毛片一级91精品 | 亚洲深爱激情 | 国产精品久久久久久久久久 | 亚洲最新视频在线 | 最近日本字幕mv免费观看在线 | 黄色片亚洲 | 国产精品第一页在线 | 欧美日韩在线免费观看 | 精品国产精品国产偷麻豆 | 九九热视频在线 | 久久欧美视频 | 99久久精品国产免费看不卡 | 黄色av网站在线免费观看 | a极黄色片| 三级黄色a | 久久精品aaa| 欧美超碰在线 | 九九九热精品 | 九九免费在线观看视频 | 国产精品久久久久免费a∨ 欧美一级性生活片 | 久久99在线观看 | 中文字幕电影高清在线观看 | 九七视频在线 | 久久久久久蜜av免费网站 | 99久久久国产精品 | 日韩免费在线观看 | 成人99免费视频 | 久久av一区二区三区亚洲 | 午夜丁香视频在线观看 | 亚洲国产剧情av | 国产精品午夜av | 日韩欧美高清在线 | 午夜影视一区 | 国产精品男女 | 成人午夜在线电影 | 日韩美视频 | 黄色免费在线看 | 免费看的国产视频网站 | 蜜桃视频成人在线观看 | 国产精品一区二区麻豆 | 成人永久在线 | 欧美性大战久久久久 | 欧美性极品xxxx做受 | 黄色福利网 | 久久久久亚洲国产 | 国产视频99 | av超碰免费在线 | 国产精品成人一区二区三区吃奶 | 在线成人中文字幕 | 亚洲国产日韩欧美在线 | 精品理论片 | 中文字幕一区二区三区四区在线视频 | 午夜丁香视频在线观看 | 在线观看香蕉视频 | 国产一区视频在线 | 在线观看国产成人av片 | 日韩高清精品免费观看 | 又黄又爽免费视频 | 在线观看一区二区视频 | 国产破处在线播放 | 婷婷丁香色 | 亚洲欧洲国产日韩精品 | 美女很黄免费网站 | 国产尤物在线观看 | 97成人在线免费视频 | 久久99热这里只有精品国产 | 国产日韩欧美在线播放 | 久久久久久国产精品久久 | 国产亚洲在线视频 | 99精品视频免费看 | 亚洲综合激情网 | 99久热在线精品视频 | 国产98色在线 | 日韩 | 久草精品网| 欧美精品你懂的 | 成人丁香花 | 日韩影视在线 | 亚洲高清在线 | 一级电影免费在线观看 | 日日操天天爽 | 欧美精品亚洲精品日韩精品 | 欧美成人h版在线观看 | 国产精品久久久久久久久久ktv | 成人动漫一区二区三区 | 四虎国产精品成人免费影视 | 91在线观看黄 | 婷婷色网站 | 天天操天天拍 | 色综合久久综合 | 美女视频免费精品 | 91网址在线看 | 国产亚洲欧美在线视频 | 日韩高清无线码2023 | 超碰97人人爱 | 成人高清在线观看 | 亚洲天天在线 | 天无日天天操天天干 | 激情视频免费在线 | 97碰碰精品嫩模在线播放 | 十八岁以下禁止观看的1000个网站 | 69亚洲乱| 欧美日韩国产一二 | 欧美日韩国产一区二区三区在线观看 | 在线观看免费版高清版 | 青草视频在线免费 | 国产福利精品一区二区 | 久草在线视频网 | 日韩免费一区二区 | 夜夜夜夜夜夜操 | 国产明星视频三级a三级点| 中文字幕在线观看免费高清完整版 | 91麻豆精品国产午夜天堂 | 少妇自拍av | 久久视频免费在线观看 | 波多野结衣一区 | 久久伊人八月婷婷综合激情 | 天天操操操操操 | 99草在线视频 | 久久tv| 久久资源在线 | 国产美女免费视频 | 超碰人人在线 | 成人小视频在线播放 | 欧美性猛片 | 日韩午夜视频在线观看 | 国产视频资源在线观看 | 久久视频国产精品免费视频在线 | 亚洲国产精品女人久久久 | 91漂亮少妇露脸在线播放 | 久久综合狠狠综合久久综合88 | 日韩精品一区二区三区丰满 | 天天射天天爱天天干 | 亚洲精品永久免费视频 | 91麻豆视频 | 久久久国产影院 | 一区二区激情视频 | 国产又粗又猛又黄又爽的视频 | 99热这里只有精品在线观看 | 国产福利免费在线观看 | 操高跟美女 | 久香蕉 | av在线免费观看不卡 | 一区二区三区久久精品 | 午夜视频在线观看一区二区 | 亚洲黄色小说网 | 一区二区三区在线观看中文字幕 | 免费看黄在线观看 | 97免费在线观看视频 | 成全免费观看视频 | 久久色在线播放 | 久久观看最新视频 | 一区二区三区在线免费播放 | 色婷婷五 | 波多野结衣亚洲一区二区 | 嫩嫩影院理论片 | 九九热免费在线视频 | 国内综合精品午夜久久资源 | 久久免费国产视频 | 国产电影黄色av | 国内久久视频 | 成人精品影视 | 一区在线免费观看 | 国产精品一区专区欧美日韩 | 亚洲精品午夜久久久久久久久久久 | 国产精彩视频一区 | 91黄色小网站 | 亚洲 欧美日韩 国产 中文 | 香蕉视频亚洲 | 精品久久久久久久久久久久 | 成人a级黄色片 | 国产剧情在线一区 | 亚洲免费婷婷 | 久久久福利视频 | 日本在线观看中文字幕无线观看 | 精品国产精品国产偷麻豆 | 欧美尹人 | 狠狠色丁婷婷日日 | 亚洲在线高清 | 白丝av在线| 天天综合网在线观看 | 97爱 | 伊人激情综合 | www色| 99久久精品免费看国产四区 | a级国产乱理伦片在线观看 亚洲3级 | 麻豆视频在线免费观看 | 亚洲乱码精品久久久 | 国产黄色片一级三级 | 国产成人不卡 | 国产精品黄网站在线观看 | 91精品视频在线观看免费 | 天天干天天操天天爱 | 激情久久伊人 | 日韩av三区 | a√资源在线 | 自拍超碰在线 | 久久精品视频在线观看免费 | 欧美在线一二区 | 国产麻豆成人传媒免费观看 | 99视频在线免费看 | 久草香蕉在线 | aaaaaa毛片| 久久免费一级片 | 韩国av三级| 国产精品第十页 | 欧美日韩视频网站 | 麻豆视频在线播放 | 国产九九精品 | 麻豆视频在线免费 | 国产福利91精品张津瑜 | 久久久久这里只有精品 | 欧美日韩伦理在线 | 超碰在线免费97 | 中文字幕亚洲综合久久五月天色无吗'' | 久久婷婷色综合 | 在线观看片| 日韩大片在线看 | 91av原创 | 久久久夜色| 蜜臀aⅴ精品一区二区三区 久久视屏网 |