SpringBoot(一)-全注解下的IOC

注:“SpringBoot入门笔记”系列接下来的内容大部分将来自杨开振所著人民邮电出版社所出版的《深入浅出SpringBoot》一书,源码均经过本人测试,侵删。


IoC是一种通过描述来生成或者获取对象的技术,这个技术不是Spring甚至不是Java所独有的,它意味着通过描述来创建对象。SpringBoot并不建议使用XML,而是通过注解来生成对象。
对象之间并不只是孤立的,它们之间还可能存在依赖的关系。为此Spring提供了依赖注入的功能,使得我们能够通过描述来管理各个对象之间的关系。
在Spring中把 每一个需要管理的对象称之为SpringBean,而Spring管理这些Bean的容器,被我们称为SpringIoC容器。
IoC容器需要具备两个基本功能:

  • 通过描述管理Bean
  • 通过描述完成Bean的依赖关系

IoC容器

简介

SpringIoC容器是管理Bean的容器,在Spring的定义中,它要求所有IoC容器都实现BeanFactory接口。
这个接口源码如下所示:
看不懂也没关系,只需要知道,IoC容器需要实现以下几个方法:
1.允许在其它IoC中使用类型或者名称获取Bean;
2.判断Bean在IoC中是否为单例
3.判断Bean是否为原型
等等,即可。

package org.springframework.beans.factory;
/**
spring ioc 的核心接口,从配置资源(eg: XML)中加载Bean定义并提供对Bean操作的基础方法
Bean:由一个String类型字符串唯一标识的java对象
*/
public interface BeanFactory {
/**
    获取类型为FactoryBean的对象时,在BeanName前加上&会获取到该对象本身的引用,
    否则会获取调用该对象的getObject方法
*/
    String FACTORY_BEAN_PREFIX = "&";

/**
    返回指定BeanName(或别名)的Bean
*/
    Object getBean(String name) throws BeansException;

/**
    返回同时匹配指定BeanName(或别名)和类型的Bean
*/
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;

/**
    返回指定BeanName(或别名)的Bean,并把所提供的参数作为构造函数参数或工厂方法参数初始化Bean
*/
    Object getBean(String name, Object... args) throws BeansException;

/**
    返回指定类型的Bean,如果存在多个bean定义有相同的类型,则抛NoUniqueBeanDefinitionException异常
*/
    <T> T getBean(Class<T> requiredType) throws BeansException;

/**
    返回指定类型的Bean,并把所提供的参数作为构造函数参数或工厂方法参数初始化Bean
*/
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

/**
    返回一个ObjectProvider<T>该类型允许对Bean处于not available情况和not-unique情况时做处理
*/
    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);

/**
    作用同上
*/
    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

/**
    根据指定的BeanName或别名判断factory是否包含bean定义(concrete
     or abstract, lazy or eager, in scope or not)或由外部注册的单例,
    返回true时并不保证一定能通过getBean获取相应实例(eg: 抽象Bean)
*/
    boolean containsBean(String name);

/**
    根据指定的BeanName或别名判断factory中该Bean的scope是否是Singleton
    返回false时不代表scope是prototype(使用自定义的scope时)
*/
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

/**
    根据指定的BeanName或别名判断factory中该Bean的scope是否是Prototype
    返回false时不代表scope是Singleton(使用自定义的scope时)
*/
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

/**
    根据指定的BeanName或别名判断factory中该Bean的类型是否与提供的类型匹配
*/
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

/**
    根据指定的BeanName或别名判断factory中该Bean的类型是否与提供的类型匹配
*/
    boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

/**
    根据指定的BeanName或别名获取factory中该Bean的类型,对于FactoryBean类型,将调用它的
    getObjectType()
*/
    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;

/**
    根据指定的BeanName或别名返回factory中该Bean的别名,
    如果提供的时别名将返回BeanName和其他别名的数组,BeanName
    置与第一个元素
*/
    String[] getAliases(String name);

}

由于BeanFactory的功能还不够强大,因此,Spring在BeanFactory的基础上,还设计了更为高级的接口Application,它是BeanFactory的子接口之一。这两个接口是Spring体系中最为重要的接口设计。

案例——IoC容器装配Bean

我们学习SpringBoot装配和获取Bean的方法从IoC容器AnnotationConfigApplicationContext开始。
首先定义一个Java对象User.java

package springboot.chapter3.pojo;

public class User {
    private Long id;
    private String userName;
    private String note;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

然后定义java配置文件

package springboot.chapter3.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// @Configuration代表这是一个Java配置文件,Spring容器会根据这个文件去装配Bean
@Configuration
public class AppConfig {
    //@Bean代表将initUser方法返回的POJO装配到IoC容器中
    @Bean(name="user")
    public User initUser(){
        User user = new User();
        user.setId(1L);
        user.setUserName("user_name_1");
        user.setNote("note_1");
        return user;
    }
}

入口函数

package springboot.chapter3.config;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import org.apache.log4j.Logger;


public class IoCTeat {
    private static Logger log = Logger.getLogger(IoCTeat.class);

    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        User user = ctx.getBean(User.class);
        log.info(user.getId());
    }
}

不要忘了maven加入一个依赖

<dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
</dependency>

运行之后,名称为user的Bean就被装配到IoC容器中了。
我们可以通过getBean()方法获取对应的Bean,并且将Bean的属性信息输出出来
如果实操一下上面的代码的话,逻辑是十分清楚的:
定义一个对象–>config文件实例化bean并且装配–>主函数加载config并且使用bean

装配你的Bean

扫描来装配Bean

使用@Bean装配每一个Bean,是一件十分麻烦的事情。
幸好,Spring允许我们扫描来装配Bean
可以使用@Component和@ComponentScan两个注解扫描装配Bean。
@Component标明哪个类被扫描进入,@ComponentScan则标明何种策略去扫描装配Bean。
把User.class放在config包中,如下面代码所示

package springboot.chapter3.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("user")
//这个类将作为“user”被IoC扫描装配

public class User {
//    @Value制定了具体的值
    @Value("1")
    private Long id;
    @Value("user_name_1")
    private String userName;
    @Value("note_1")
    private String note;
/*setter and getter*/
}

Java配置文件中加入@ComponentScan,如下所示

package springboot.chapter3.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

// @Configuration代表这是一个Java配置文件,Spring容器会根据这个文件去装配Bean
@Configuration
@ComponentScan
//扫描也仅仅配置类所在的包
public class AppConfig {
}

入口函数main不变,这样运行仍然可以装配我们想要配置的Bean。
细心的人会发现,我们将User类放在了config包,这样就不太合理了。
实际上,@ComponentScan还允许我们自定义扫描配置项。
首先,可以通过配置项basePackages定义扫描的包名,在没有定义的情况下,它只会扫描当前包和其子包下的路径;还可以通过basePackageClasses定义扫描的类;其中还有includeFilters和excludeFilters,前者是定义满足过滤器条件的Bean才去扫描,后者则是派出过滤器条件的Bean,它们都需要通过一个注解@Filter去定义。
我们把AppConfig中的注解修改为:
@ComponentScan(“springboot.chapter3.")
或者@ComponentScan(basePackages={"springboot.chapter3.
”})
无论采用何种方式都能使得IoC容器扫描User类,而包名可以通过正则式匹配。

扫描装配排除项

现在,假设我们有一个UserService类,为了标注它为服务类,将类标注@Service,这个标准注入了@component,所以会被Spring扫描
这时我们可以修改注解为:
@ComponentScan(“springboot.chapter3.*”,
excludeFilters={@Filter(classes = {UserService.class})})

自定义第三方Bean

现实中的java应用往往需要引入第三方的包,并且很有可以希望把第三方包的类对象也放入到SpringIoC容器中,这时候@Bean注解就发挥作用了.
例如对于DBCP数据源的引用:
加入依赖

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

然后直接配置即可(在AppConfig中加入):

    @Bean(name = "dataSource")
    public DataSource getDataSource() {
        Properties props = new Properties();
        props.setProperty("driver", "com.mysql.jdbc.Driver");
        props.setProperty("url", "jdbc:mysql");
        props.setProperty("usename", "root");
        props.setProperty("password", "123456");
        DataSource dataSource = null;
        try {
            dataSource = (DataSource) BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
            return dataSource;
    }

依赖注入

在Spring框架下,当Bean实例 A运行过程中需要引用另外一个Bean实例B时,Spring框架会创建Bean的实例B,并将实例B通过实例A的构造函数、set方法、自动装配和注解方式注入到实例A,这种注入实例Bean到另外一个实例Bean的过程称为依赖注入。
为了说明依赖注入在Spring中的用法,书上给出了这么一个例子:
人类依赖于动物
首先定义两个接口:

package springboot.chapter3.pojo.definition;

public interface Animal {
    
    public void use();
    
}
package springboot.chapter3.pojo.definition;

public interface Person {
//    使用动物服务
    public void service();
//    设置动物
    public void setAnimal(Animal animal);
    
}

两个实现类:

package springboot.chapter3.pojo;

import org.springframework.beans.factory.annotation.Autowired;
import springboot.chapter3.pojo.definition.Animal;
import springboot.chapter3.pojo.definition.Person;

public class BussinessPerson implements Person {
    @Autowired
    private Animal animal = null;

    @Override
    public void service() {
        this.animal.use();
    }

    @Override
    public void setAnimal(Animal animal) {
        this.animal = animal;
    }
}
package springboot.chapter3.pojo;

import springboot.chapter3.pojo.definition.Animal;

public class Dog implements Animal {
    @Override
    public void use() {
//        class.getSimpleName()用于得到类的简写名称
        System.out.println("狗"+Dog.class.getSimpleName()+"是看门用的");
    }
}

注意这里的注解 @Autowired,它会根据属性的类型找到相应的Bean进行注入,因为Dog类继承了animal,所以会把Dog实例注入到BussinessPerson中.
同样可以使用:

ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        Person user = ctx.getBean(BussinessPerson.class);
//        log.info(user.getId());
        user.service();

进行测试
(如果你不是很懂得话,没关系,可以继续往下看,因为我看到这儿也不是很懂)

实现依赖注入的注解@Autowired

@Autowired是我们使用最多的注解之一。
我们很容易想到一个问题,如果Animal有两个继承类(假设另一个是Cat),那么Autowired怎么确定哪个注入呢?
事实上,这种情况会报错。
我们可以使用:

@Autowired
private Animal dog = null;

来进行匹配。
除了能够标注属性之外,@Autowired还可以标注方法。

消除歧义——@Primary和Qualifier

使用上面所述的方法消除bug会导致代码耦合,
@Primary可以标识两个类实例中更优先的实例
当要选择多个标识@Primary的类中的一个类时,可以使用:

@Autowired
@Qualifier("dog")
private Animal dog = null;
带参数的构造方法类的装配

如下所示,参数之前加入@Autowired即可

public BussinessPerson(@Autowired @Qualifier("dog") Animal animal){
this.animal = animal;
}

####生命周期
Bean的生命周期大致分为:
Bean定义、Bean初始化、Bean的生存期和Bean的销毁四个部分。
其中Bean定义过程大致如下:

  • Spring通过我们的配置,如通过@ComponentScan定义的路径扫描找到带有@Component的类,这是一个资源定位的过程
  • 一旦找到了资源,它就开始解析,并且将定义的信息保存起来
  • 然后将Bean定义发布到SpringIoC容器中,此时,Ioc容器只有Bean的定义,还是没有实例生成
    以上是Bean的发布过程,当我们取出来时才做初始化和依赖注入等操作。

ComponentScan中还有一个配置项lazyInit,只可以配置Boolean值,且默认值为false,也就是默认不进行延迟初始化,因此在默认情况下Spring会对Bean进行实例化和依赖注入对应的属性值

下面这张图说明了SpringBean的生命周期和可以实现的接口:
在这里插入图片描述
在这里插入图片描述
根据这张图,我们还可以使用@PostConstruct标识自定义初始化方法
使用@PreDestroy标志销毁方法
还可以定义所有bean的后置处理器。

使用属性文件

加入依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

之后,就可以直接使用属性文件application.properties为你工作了

加入以下内容:

database.driverName=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/chapter3
database.username=root
database.password=123456

我们可以通过@value注解,使用${……}这样的占位符读取配置在属性文件里的内容,例如:

@value("${database.drivername}")
private String driverName = null;

为了减少注解,我们可以(在类名称之前)使用@ConfigurationProperties(“database”),database将与pojo的属性名称组成属性的全限定名去配置文件中查找。

于此同时,我们可以注意到,如果只有一个配置文件,那么这个文件就会过长而不便于更改。

我们可以使用@PropertySource去定义对应的属性文件,把它加载到Spring的上下文中。

例如:
@PropertySource(value={“classpath:jdbc.properties”},ignoreResourceNotFound=true)

ignoreResourceNotFound的值为true,意味着如果找不到这个配置文件的话就忽略这一条语句;为false的话,找不到救会报错。

条件装配Bean

设想一种场景,如果在数据库连接池的配置中漏掉一些配置,这时候,如果Ioc还在进行数据源的装配,则系统将会抛出异常,所以这时候我们反而更希望IoC不去装配数据源。
为了处理这种场景,Spring提供了@Conditional注解帮助我们,它需要配合另一个接口Condition来完成对应的功能。

(这个地方我不太理解,并且代码编译没有通过,请参考其它地方,十分抱歉)

Bean的作用域

我们根据BeanFactory(IoC容器顶级接口)的源码,可以看到isSingleton和isPrototype两个方法,
其中,isSingleton方法如果返回true,则bean在IoC中以单例存在;如果isPrototype方法返回true,则当我们每次获取bean时,都会创建一个新的bean,这显然存在很大不同,这便是SpringBean作用域的问题。
我们都知道javaweb四种作用域:page、request、session、application(不知道的百度),因为page是针对于jsp页面的作用域,所以spring无法支持。

Bean作用域
作用域类型 使用范围 描述
singleton 所有Spring应用 默认值,IoC容器只存在单例
prototype 所有Spring应用 每当从IoC容器中取出一个Bean,则创建一个新的Bean
session Springweb应用 http会话
application Springweb应用 web工程生命周期
request Springweb应用 web工程单次请求
globalSession springweb应用 在一个全局的HttpSession中,一个bean对应一个实例

怎么用呢?一个示例如下所示:

package springboot.chapter3.pojo;
/*imports*/
import org.springframework.beans.factory.config.ConfigurableBeanFactory;import org.springframework.stereotype.Component;
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ScopeBean{
}

这里的ConfigurableBeanFactory只能提供单例和原型两种作用域,如果是SpringMVC环境中,还可以使用WebApplicationContext去定义其它作用域

使用@Profile

可以使用这个注解配置两个bean,在没有修改spring参数的情况下,profile机制将不会被启动
启用后,可以很方便在各个环境中切换

发布了165 篇原创文章 · 获赞 24 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/treblez/article/details/104665456
今日推荐