Springboot全注解下的IOC

Spring的两个核心理念:

  • 一个是控制反转(Inversion of Control, IoC)
  • 另一个是面向切面编程(Aspect Oriented Programming, AOP)

IoC是一和通过描述生成或者获取对象的技术。Springboot不建议使用XML,而是通过注解的描述生成对象。

IoC容器需要具备两个基本功能:

  • 通过描述管理Bean,包括发布和获取Bean
  • 通过描述完成Bean之间的依赖关系。

IoC容器简介

Sping IoC是管理Bean的容器。Spring要求所有的IoC容器都需要实现BeanFactory。

源码BeanFactory接口中,可以知道,Spring IoC容器中,允许我们按类型或者名称获取Bean。

其中,isSingleton方法判断Bean是否在Spring IoC中为单例的。默认司下,Bean 都是单例存在的,也主是使得getBean方法返回都是同一个对象。

与isSingleton方法相反的是isProtorype方法,如果它返回的是ture,那么getBean方法获取Bean是,容器将创建一个新的Bean返回给调用者。



public class AppConfig {

    @Bean(name = "user")
    public User initUser() {
        User user = new User();
        user.setId(1L);
        user.setUserName("user_name_1");
        user.setNote("note_1");
        return user;
    }
}

@Configuration 代表这是一个Java配置文件,Spring的容器会根据它来生成IoC容器去装配 Bean;@Bean 代表将initUser方法返回的POJO装配到IoC容器中,而其属性name定义这个Bean名称,如果没有配置它,则将方法名称"initUser" 作为Bean的名称保存到Spring IoC容器中。

装配你的Bean

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.context.annotation

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
// 在一个类中可重复定义
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
   // 定义扫描的包
    @AliasFor("basePackages")
    String[] value() default {};
    
    // 定义扫描的包
    @AliasFor("value")
    String[] basePackages() default {};
    
    // 定义扫描的类
    Class<?>[] basePackageClasses() default {};
  
    // Bean name 生成器
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    // 作用域解析器
    Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

    // 作用代理模式
    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

    // 资源匹配模式
    String resourcePattern() default "**/*.class";

    // 是否启动默认过滤器
    boolean useDefaultFilters() default true;

   // 当满足过滤器条件时扫描
    ComponentScan.Filter[] includeFilters() default {};

    // 当不满足过滤器条件时扫描
    ComponentScan.Filter[] excludeFilters() default {};
    
    // 是否延迟初始化
    boolean lazyInit() default false;
    
    // 定义过滤器
    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Filter {
        FilterType type() default FilterType.ANNOTATION;

        @AliasFor("classes")
        Class<?>[] value() default {};

        @AliasFor("value")
        Class<?>[] classes() default {};

        String[] pattern() default {};
    }
}

  • 可以通过配置项basePackages定义扫描包名,在没有定义的情况下,它只会扫描当前包和其子包下的路径
  • 可以通过basePackageClasses定义扫描的类
  • 还有includeFilters 和 excludeFilters
    • includeFilters是定义满足过滤器(Filter)条件的Bean才去扫描
    • excludeFilters是排除过滤器条件的Bean。
    • 它们都需要通过@Filter去定义,它有一个type类型,这里可以定义为注解或者正则式等类型。
@Component("user")
public class User {

    @Value("1")
    private Long id;
    @Value("user_name_1")
    private String userName;
    @Value("note_1")
    private String note;
}

@ComponentScan("com.springboot.chapter3.*")
@ComponentScan(basePackages = {"com.springboot.chapter3.pojo"})
@ComponentScan(basePackageClasses = {User.class})
public class AppConfig {
}

上述三种方式都能够使用IoC容器去扫描User类,而包名可以采用正则式去匹配。

@ComponentScan(basePackages = “com.springboot.chapter3.*”, excludeFilters = {@ComponentScan.Filter(classes = {Service.class})})

@excludeFilters的配置,使标注了@Service的类将不在被IoC容器扫描注入。

SpringBootApplication 也注入了 @ComponentScan。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
// 自定义排除扫描类
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)

public @interface SpringBootApplication
  // 自定义排除扫描类
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};
    
   // 通过名称排除自动配置为
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    //定义扫描包
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    // 定义扫描的类
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
    
      @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

通过它可以定义扫描哪些包。它所提供的exclude 和 excludeName两个方法是对其内部自动配置类才会生效的。

扫描User而不扫描UserService

@SpringBootApplication
@ComponentScan(basePackages = "com.springboot.chapter3.*", excludeFilters = {@ComponentScan.Filter(classes = {Service.class})})

自定义第三方Bean

Spring会将"dataSource"保存在IoC容器中。


@Configuration

public class AppConfig {

    @Bean(name = "dataSource")
    public DataSource getDataSource(){
        Properties props = new Properties();
        props.setProperty("driver", "com.mysql.jdbc.Driver");
        props.setProperty("url","jdbc:mysql://localhost:3306/chapter3");
        props.setProperty("username", "root");
        props.setProperty("password", "root");

        DataSource dataSource = null;

        try {
            dataSource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return dataSource;
    }
}

依赖注入

人类(Person) 有时候利用一些动物 (Animal) 去完成一些事情,比方说狗(Dog) 看门, 猫 (Cat) 爬老鼠, 鹦鹉(Parrot)迎客…

// 人和动物的接口
package com.springboot.chapter3.pojo.definition;

public interface Animal {

    public void use();

}

public interface Person {

    public void service();

    public void setAnimal(Animal animal);
}

//人类实现类
package com.springboot.chapter3.pojo;


@Component
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;
    }
}

// 动物实现类
@Component
public class Dog implements Animal {
    @Override
    public void use() {
        System.out.println("狗【" + Dog.class.getSimpleName() + "】是看门用的");
    }
}

package com.springboot.chapter3.config;


public class IoCTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

        Person person = ctx.getBean(BussinessPerson.class);
        person.service();
    }
}

输出:狗【Dog】是看门用的

@Autowrited 它会根据属性的类型(by type) 找到对应的Bean进行注入。这里的Dog是动物的一种,所以SpingIoC会把Dog的实例注入BussinessPerson中。

@Autowired它注入的机制最基本的一条是根据类型(by type),IoC容器顶级接口BeanFacory,可以知道IoC容器通过getBean方法获取对应Bean的,而 getBean又支持根据类型或根据名称。

创建一个猫类:

@Component
public class Cat implements Animal {
    @Override
    public void use() {
        System.out.println("猫【" + Cat.class.getSimpleName() + "】是抓老鼠");
    }
}

异常:
 expected single matching bean but found 2: cat,dog

从这里可以看出,Spring IoC并不能判断是要注入什么动物,给BussinessPerson类对象,从而引起错误的发生。

这里假设我们目前是需要狗提供服务,那么可以属性名转为dog

@Autowired
private Animal animal = null;

修改为

@Autowired
private Animal dog = null;

@Autowired提供这样的规则,首先它会根据类型找到对应的Bean,如果对应的类型Bean不是唯一的,那么会根据其属性名称进行匹配。匹配不上则抛出异常。

@Autowired 是一个默认必须找到对应Bean的注解,如果不能确定其标注属性一定会存在并且允许这个被标注的属性为null。那么可以配置属性required 为 false;

@Autowired(required = false)

消除歧义性-- @Primary 和 @Quelifier

上述出院歧义我和将 animal修改为了dog。产生注入失败根本问题是按类型查找(by type)查找。

@Primary,它是一个修改优先权的注解(优先及by name by type之上)。

@Component
@Primary
public class Cat implements Animal {}

@Primary的含义是告诉Spring IoC容器,当发现多个Bean时优先使用我注入。如果存在多个 @Primary 还是会引发歧义。

@Qulifier和@Autowired组合在一起,通过类型和名称一起找到Bean。

<T> T getBean(String name, Class<T> requiredType) throws BeansException。

假设猫已经标注@Primary我们需要狗提供服务。

@Autowired
@Qualifier("dog")
private Animal animal = null;

带参数的构造方法类装配

有些类只有带构造参数的构造方法,于是上述的方式都失效了。修改BussinePerson满足这个功能


@Component
public class BussinessPerson implements Person {

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

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

    @Override
    public void setAnimal(Animal animal) {
        this.animal = animal;
    }
}

生命周期

Bean的生命周期过程,它大致分为Bean定义、Bean的初始化、Bean的生存期和Bean的销毁。

  • Spring通过我们的配置,如@ComponentScan定义的扫描路径去找到带有@Component的类,这个过程就是一个资源定位的过程。
  • 一旦找到资源,它就开始解析,并且将定义的信息保存起来。注意,此时还没有初始化Bean,也没有Bean的实例,它有的仅仅是Bean的定义
  • 然后会把Bean定义发布到Spring IoC容器中。此时IoC容器中,也只有Bean的定义。

默认情况下Spring会继续完成Bean的实例化和依赖注入,这样从IoC容器中就可以得到一个依赖注入完成的Bean。这是我们希望,Bean只是将定义发布到IoC容器而不做实例化和依赖注入,当我们取出来的时候才做初始化和依赖流入等操作。

Spring初始化流程

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

@ComponentScan(basePackages = "com.springboot.chapter3.*",
        excludeFilters = {@ComponentScan.Filter(classes = {Service.class})},
        lazyInit = true)

Spring在完成依赖注入后,还提供了一系列的接口和配置来完成Bean初始化过程。图中描述的是整个IoC容器初始化Bean的流程

对于那么没有实现ApplicationContext的接口容器,在生命周期对应ApplicationContextAware定义的方法是不会被调用的。

实现BeanPostProcessor这个Bean后置处理器将对所有的Bean有效,而其它的接口都是对于单个Bean起作用。

自定义初始化和销毁方法

@Bean(initMethod = "init" , destroyMethod = "destroy")

使用属性文件

可以采用application.properties,也可以定义配置文件。

SpringBoot中,可以在Maven中添加依赖,SpringBoot将创建读取属性文件上下文。

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

配置属性:

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

SpringBoot会通过其机制读取到上下文,可以引用它。

第一种:Spring表达式。

@Component
@ConfigurationProperties("database")
public class DataBaseProperties {
   @Value("{${database.driverName}")
    private String driverName = null;
.....
   
   @Value("${database.username}")
   public void setUsername(String username) {
       this.username = username;
   }
}

@ConfigurationProperties

@Component
@ConfigurationProperties("database")
public class DataBaseProperties {
    private String driverName = null;
    private String url = null;
    private String username = null;
    private String password = null;

    public void setDriverName(String driverName) {
        System.out.println(driverName);
        this.driverName = driverName;
    }

    public void setUrl(String url) {
        System.out.println(url);
        this.url = url;
    }

    public void setUsername(String username) {
        System.out.println(username);
        this.username = username;
    }

    public void setPassword(String password) {
        System.out.println(password);
        this.password = password;
    }
}
 ... GET方法

@ConfigurationProperties 中配置的字符串 database ,将与POJO 的属性名组成属性的全限定名去配置文件里查找。


@SpringBootApplication
@ComponentScan(basePackages = {"com.springboot.chapter3"})

@PropertySource(value = {"classpath:jdbc.properties"}, ignoreResourceNotFound = true)
public class Chapter3Application {

    public static void main(String[] args) {
        SpringApplication.run(Chapter3Application.class, args);
    }
}

@PropertySource 定义对应的属性文件将它加载到Spring上下文中,
ignoreResourceNotFound 则是忽略配置文件找不到问题。 其默认值为false,找不到就抛出异常。

条件装配

某些客观因素会例一些Bean无法进行初始化,例如:错误的配置导入数据源不能连接上。这种情况下,如果IoC还进行数据源装配则系统抛出异常,导致应用无法继续。

@Bean(name = "dataSource", destroyMethod = "close")
@Conditional(DatabaseCondition.class)
public DataSource getDataSource(  @Value("${database.driverName}") String driver,
        @Value("${database.url}") String url,
        @Value("${database.username}") String username,
        @Value("${database.password}") String password){
    Properties props = new Properties();
    props.setProperty("driver", driver);
    props.setProperty("url", url);
    props.setProperty("username", username);
    props.setProperty("password", password);
    DataSource dataSource = null;
    try {
        dataSource = BasicDataSourceFactory.createDataSource(props);
    } catch (Exception e) {e.printStackTrace();}
    return dataSource;
}
/**
  数据库装配条件 
  context 上下文
  metadata 注释 类型元数据
  
  return ture 表示装配Bean
*/

public class DatabaseCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment env = conditionContext.getEnvironment();
        return env.containsProperty("database.driverName")
                && env.containsProperty("database.url")
                && env.containsProperty("database.username")
                && env.containsProperty("database.password");
    }
}

@Conditional注解,可以实现按条件装配。 matched方法首先读取上下文环境,然后判定是否已经配置了对应的数据库信息。

Bean作用域

单例(Singleton) 和 原型(Prototype):

isSingleton方法如果返回ture,则Bean在IoC容器中以单例存在,这是Spring IoC容器默认值;如果方法返回true,则当我们每次获取Bean的时候,IoC容器都会创建一个新的Bean。

Web容器中,存在page,request,session,application四种作用域。page是针对jsp当前页面的作用域,所以Spring是无法支持的。

configurableBeanFactory 提供 单例SCOPE_SINGLETON和原型 SCOPE_PROTOTYPE两个种

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ScopeBean {

}

可以使用 WebApplicationContext去定义其他作用域

@Component

@Scope(WebApplicationContext.SCOPE_REQUEST)
public class ScopeBean {

}

使用@Profile

此注解应用于 开发环境、测试环境、生产环境多个环境中的数据库资源切换

 @Bean(name = "dataSource", destroyMethod = "close")
@Profile("dev")
public DataSource getDevDataSource(){
    System.out.println("我是开发环境");
    Properties props = new Properties();
    props.setProperty("driver", "com.mysql.jdbc.Driver");
    props.setProperty("url", "jdbc:mysql://localhost:3306/springboot_dev");
    props.setProperty("username", "root");
    props.setProperty("password", "123");
    DataSource dataSource = null;
    try {
        dataSource = BasicDataSourceFactory.createDataSource(props);
    } catch (Exception e) {e.printStackTrace();}
    return dataSource;
}

@Bean(name = "dataSource", destroyMethod = "close")
@Profile("test")
public DataSource getTestDataSource(){
    System.out.println("我是测试环境");
    Properties props = new Properties();
    props.setProperty("driver", "com.mysql.jdbc.Driver");
    props.setProperty("url", "jdbc:mysql://localhost:3306/springboot_test");
    props.setProperty("username", "root");
    props.setProperty("password", "123456");
    DataSource dataSource = null;
    try {
        dataSource = BasicDataSourceFactory.createDataSource(props);
    } catch (Exception e) {e.printStackTrace();}
    return dataSource;
}

启动时,我们只需要如下配置profile就可以完成数据库资源切换:

JAVA_OPTS="-Dspring.profiles.active=dev"

Spring 常用EL

@Value 中的 #{…}代表占位符,它会读取上下文中的属性值装配到属性中

@Value("${database.driverName")
String driver

#{…}代表启动Spring表达式,它将具体运算功能, T(…)代表的是引入类。 System是java.lang.*包的类, 是java默认加载的包,因此可以不必定全限定名。

@Value("#{T(System) .currentTimeMillis() }")
private Long initTime = null;

@Value("#{9.3E3}")
private double d;

//获取其他的Spring Bean 的属性来给当前的Bean属性
@Value("#{beanName.str}"")
private String otherBeanProp = null;

//判断当前属性是否为空,如果不为空则执行toUpperCase()方法。
@Value("#{beanName.str?.toUpperCase()}"")
private String otherBeanProp = null;

@Value("#{dog.username?.toUpperCase()}")
private String otherBeanProp = null;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值