springboot教程(一)

本文深入探讨了Spring框架的核心概念,包括BeanFactory、ApplicationContext、Bean装配、依赖注入等,以及Spring Boot的自动配置机制、事件监听、扩展点等高级特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用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
    @Bean
    public 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
     */
    @Override
    public Jeep getObject() throws Exception {
        return new Jeep();
    }

    /**
     *
     * @return
     */
    @Override
    public Class<?> getObjectType() {
        return Jeep.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

在MyConfig.java中添加配置:

    @Bean
    public 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

    @Bean
    public Car createJeep(CarFactory carFactory){
        return carFactory.create();
    }

    @Bean
    public 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 {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("====afterPropertiesSet====");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("====destroy====");
    }
}

配置MyConfig.java


    @Bean
    public 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 {

    @PostConstruct
    public void initial(){
        System.out.println("fish init");
    }

    @PreDestroy
    public void close(){
        System.out.println("fish close");
    }

}

配置MyConfig.java

    @Bean
    public 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,

    @Bean
    public 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,createUser
	at 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;
    }
    @Override
    public 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 {

    @Autowired
    private UserService userService;

    @Override
    public String toString() {
        return "UserService{" +
                "userDao=" + userDao +
                '}';
    }

}

打印输出:

User{userService=UserService{userDao=com.edu.spring.UserDao@3bbc39f8}}

说明成功注入进去了。

如果继续在MyConfig.java中装配一个Bean:

    @Bean
    public 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,createUserDao
	at 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;

    @Override
    public String toString() {
        return "UserService{" +
                "userDao=" + userDao +
                '}';
    }
}

这样就知道用哪一个对象了。(注意要将其他所有用到UserDao的地方,都需要使用Qualifier指明到底用哪一个userdao)

方法二:使用Primary注释Bean

在MyConfig.java中:

    @Bean
    @Primary
    public 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 {

    @Autowired
    private UserService userService;

    // 使用JSR 250的注解
    @Resource
    private Car car;

    @Override
    public 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提供的注解
    @Autowired
    private UserService userService;

    // 使用JSR-250的注解
    @Resource
    private Car car;

    // 使用JSR-330的注解
    @Inject
    private Cat cat;

    @Override
    public 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' available
	at 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 {

    @Bean
    public 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 {

    @Autowired
    private 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;

    @Override
    public 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
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("====postProcessBeforeInitialization======" + bean.getClass());
        return bean;
    }
    
    // 在bean init方法执行之后触发
    @Nullable
    @Override
    public 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 {

    @Autowired
    private 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());
    }

    @Autowired
    public 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 {

    @Override
    public 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 {
    @Override
    public 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();
    }

    @Bean
    public 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 {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("====postProcessBeforeInitialization=====" + bean.getClass());
        return bean;
    }

    @Override
    public 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;

    @Override
    public 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 {
    @Override
    public 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());
        }
    }

    @Override
    public 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 {

    @Bean
    public 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 {

    @Bean
    public 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 {

    @Bean
    public 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' available
	at 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 {

    @Bean
    public 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=8080

name=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 {

    @Autowired
    private 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;

    @Autowired
    private 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;

    @Override
    public 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 {
    @Override
    public 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 {

    @Bean
    public 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 {

    @Bean
    public EncodingConvert createUTF8EncodingConvert(){
        return new UTF8EncodingConvert();
    }

    @Bean
    public 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 {

    @Override
    public 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 {

    @Override
    public 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;

    @Override
    public 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 {
    @Override
    public 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
    @Override
    public 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 {

    @Bean
    public Runnable createRunnable(){
        return () -> {};
    }

    @Bean
    public 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 {
    @Override
    public 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 {
    @Override
    public 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 {

    @Bean
    public 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 {

    @Bean
    public 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> {

    @Override
    public 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
     */
    @EventListener
    public 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
     */
    @EventListener
    public void event(MyApplicationEvent event){
        System.out.println("接受到事件" + event.getClass());
    }

    @EventListener
    public 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> {
    @Override
    public 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> {
    @Override
    public 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> {

    @Override
    public 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 {

    @Override
    public 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 {
    @Override
    public 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 {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("=========应用启动后的时间是:" + LocalDateTime.now().toString());
    }
}
@Order(2)
@Component
public class ServerSuccessReport implements CommandLineRunner {

    @Override
    public 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 {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("应用已经启动,参数为:" + Arrays.deepHashCode(args.getSourceArgs()));
    }
}

修改ServerSuccessReport.java的输出

    @Override
    public 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 {

    @Bean
    public Runnable createRunnable(){
        return ()->{};
    }

}

运行App.java输出结果如下:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.Runnable' available
	at 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

    @Bean
    public 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方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值