乐趣区

关于linux:老生再谈-IoC

IoC,Spring 的核心理念之一,的确这是一个陈词滥调的货色。然而明天呢!又从新复习之后,想再说说本人对 IOC 的一些想法。

IoC——Inversion of Control,管制反转。要想了解 IoC 还是要从其自身登程,首先就 管制 而言,管制是对谁的管制——是对象的管制。其次,反转是什么的反转或者说为什么要称做反转——是对象控制权反转。

对象管制,传统的形式就是程序员通过 new 关键字的形式来生成一个对象,而后由程序员依据程序逻辑人为地管制对象的应用。从这里登程,就能够很好地了解什么是管制反转了。

所谓管制反转,就是将本来在程序员手中的对象创立和治理的权限交给了 Spring IoC 容器。也就是说,管制反转就是要转移程序员对对象的控制权,而在 Spring 当中的实现就是 Spring IoC 容器通过 Xml 或注解的形容生成或者获取对象,再由 IoC 容器对这些 Bean 进行治理。

所以,了解 IoC(管制反转),就只须要记住,控制权由谁反转给了谁。

IoC 容器

顶级 IoC 容器接口—BeanFactory

对于BeanFactory,它的重要性源自于所有 IoC 容器都是间接或者间接派生自它。尽管,它的性能不是很弱小,然而从其源码当中却能够看出很多端倪。

public interface BeanFactory {
    /**
    工厂 Bean 的前缀,用于判断获取的是 FactoryBean 还是 FactoryBean 所产生的实例      上面会有具体解释
    **/
    String FACTORY_BEAN_PREFIX = "&";
    
    /** 通过 name 获取 Bean**/
    Object getBean(String name) throws BeansException;
    /** 通过 name 和 Class 类型 获取 Bean**/
    <T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
    /** 通过 name 和结构参数,也就是能够指定调用某个构造方法 获取 Bean**/
    Object getBean(String name, Object... args) throws BeansException;
    /** 通过 Class 类型 获取 Bean**/
    <T> T getBean(Class<T> requiredType) throws BeansException;
    /** 通过 Class 类型和结构参数,同样能够指定调用某个构造方法 获取 Bean**/
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

    /** 返回一个被 ObjectProvider 包装的 Bean**/
    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
    
    /** 通过 name 判断是否在容器中有这个 Bean**/
    boolean containsBean(String name);

    /** 是否为单例 **/
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    /** 是否为原型 **/
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    /** 类型匹配否 **/
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

    /** 依据 name 找到 Bean 的 Class 类型 **/
    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    /** 获取此 Bean 之外的别名 **/
    String[] getAliases(String name);

}
  • FACTORY_BEAN_PREFIX 在 Spring 当中,有一个叫做 FactoryBean 的接口,这个类有一个 T getObject() throws Exception; 这样的办法,这个办法会返回一个对象实例。对于这个接口的实现类而言,通过 BeanFactorygetBean()返回的 Bean 是实现类自身的实例,还是 getObject() 的返回实例就在于有没有前缀。有,返回 FactoryBean;没有,返回getObject() 的返回实例。
/** 举个简略的例子
  实现这样一个 FactoryBean
  用这样一个 FactoryBean 来创立一个咱们须要的 User
**/
@Component("user")
public class UserFactoryBean implements FactoryBean<User> {

    @Autowired
    private User user;

    @Override
    public User getObject() throws Exception {return user;}

    @Override
    public Class<?> getObjectType() {return user.getClass();
    }
}
// 测试方法
public static void test1(){ApplicationContext ctx = new AnnotationConfigApplicationContext(UserConfig.class);
    // 没有前缀
    // 失去的是 User getObject() throws Exception 的返回值
    User user = (User) ctx.getBean("user");
    System.out.println(user);
    
    // 有前缀
    // 失去的是 UserFactoryBean 的实例
    UserFactoryBean userFactoryBean =
        (UserFactoryBean) ctx.getBean("&user");
    System.out.println(userFactoryBean);
}

这里只是简略的例子,用来阐明 FACTORY_Bean_PREFIX 的作用,FactoryBean 更具体的用法,能够参考工厂模式当中工厂的作用。

  • ObjectProvider 这是在 spring4.3 之后才呈现的一个接口,它次要作用是解决注入时 Bean 不存在或者 Bean 存在多个时呈现的异常情况。
//getIfAvailable()能够解决容器中没有 userDao 时的异样
public class UserService{
    private UserDao userDao;
    public UserService(ObjectProvider<UserDao> dao){userDao = dao.getIfAvailable();
    }
}

//5.1 之后能够通过流式解决来解决容器中存在多个 userDao 状况
public class UserService{
    private UserDao userDao;
    public UserService(ObjectProvider<UserDao> dao){userDao = dao.orderedStream()
                        .findFirst()
                        .orElse(null)
    }
}
外围容器—ApplicationContext

学习过 Spring 的人,对 ApplicationContext 都不会生疏。它是 BeanFactory 的子(精确的说应该是孙子)接口之一,而咱们所应用到的大部分 Spring IoC 容器都是 ApplicationContext 的实现类。

Spring 的源码很宏大,也很简单,所以倡议学习的时候,从某几个重点类开始,剖析其继承、扩大关系,以此横向开展对 Spring 的意识。

这里也就不再对 ApplicationContext 的各个继承接口一一解释了,API 文档外面都有:ApplicationContext。对于 ApplicationContext 这个容器更多的是侧重于对它的利用介绍,就是如何通过这个容器来获取 Bean。

通过一个简略的例子来理解一下:

// 一般的 JavaBean
public class User {
    private Long id;
    private String name;
    private int age;
    /**getter,setter,toString**/
}
// 配置类,采纳注解的模式来配置 Bean
@Configuration
public class UserConfig {@Bean(name="user")
    public User getBeanUser(){User user = new User();
        user.setId(1L);
        user.setName("klasdq1");
        user.setAge(18);
        return user;
    }
}
// 测试类
public class IocTest {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(UserConfig.class);
        User user = ctx.getBean("user");//
        System.out.println(user);
    }
}
  • @Configuration @Configuration这个注解的作用就在于它标示的类领有一个或多个 @Bean 润饰的办法,这些办法会被 Spring 容器解决,而后用于生成 Bean 或者服务申请。
//@Configuration 的源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(annotation = Component.class)
    String value() default "";

    boolean proxyBeanMethods() default true;}

从注解的源码当中能够看出,它有两个;一是 value,用于为配置类申明一个具体的 Bean name。二是proxyBeanMethods,用于指定@Bean 润饰的办法是否被代理。

  • @Bean 这个注解只用在办法下面,用于 Spring 容器治理生成 Bean。
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {@AliasFor("name")
    String[] value() default {};

    @AliasFor("value")
    String[] name() default {};

    /** @deprecated */
    @Deprecated
    Autowire autowire() default Autowire.NO;

    boolean autowireCandidate() default true;

    String initMethod() default "";

    String destroyMethod() default "(inferred)";
}

@Bean的参数中重要的就是 name(value),其含意在于为 Bean 申明具体的名称,一个 Bean 能够有多个名称,这也是为什么 BeanFactory 中有一个 getAliases() 办法。其余参数,看名字就晓得什么用意,就不再多解释了。

  • AnnotationConfigApplicationContext 这是 ApplicationContext 类的具体实现类之一,用于注解模式的 Bean 的生成。与之绝对应的还有 ClassPathXmlApplicationContext 从 XML 文件中获取 Bean。

Bean 的拆卸

在 Spring 当中对于 Bean 的拆卸容许咱们通过 XML 或者配置文件拆卸 Bean,但在 Spring Boot 中罕用注解的模式,为了不便 Spring Boot 开发的须要,就不再应用 XML 的模式了。

间接看例子:

// 配置 JavaBean
@Component("klasdq2")
public class User {@Value("2")
    private Long id;
    @Value("klasdq2")
    private String name;
    @Value("19")
    private int age;
    /**getter,setter,toString**/
}
// 配置类扫描拆卸 Bean
@Configuration
@ComponentScan
public class UserConfig {}
// 测试类
public class IocTest {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(UserConfig.class);
        User user = (User) ctx.getBean("klasdq2");
        System.out.println(user);
}
  • @Component

    @Component的源码很简略:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Indexed
    public @interface Component {String value() default "";
    }

    参数当中只有一个value,用于申明 Bean 的名字(标识)。这里又呈现一个新的注解@Indexed,顾名思义这个注解就是减少一个索引,这是因为 Spring Boot 当中大量采纳扫描的模式来拆卸 Bean 之后,扫描的 Bean 越多,解析工夫就越长,为了进步性能,在 5.0 版本的时候就引入了这样一个注解。

  • @Value
@Target({ElementType.FIELD,
         ElementType.METHOD, 
         ElementType.PARAMETER, 
         ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {String value();
}

这个注解能够用在字段、办法、办法参数、注解上,通过一个表达式或者具体字符串为其传入相应的值。@Value是一个性能十分弱小的注解,倡议对其多做理解。

其性能次要包含以下几种:

  1. 注入一般字符串
  2. 书写 SpEL 表达式,如:@Value(“#{person.name}”),能够从配置文件、Bean 属性、调用办法等等失去数据。
  3. 注入 Resource,如:@Value("classpath:com/demo/config.txt") 应用 Resource 类型接管
  4. 注入 URL 资源,如:@Value("http://www.baidu.com") 应用 Resource 类型接管
  • @ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    /**
    * 这个参数是 ComponetScan 注解最罕用的,其作用就是申明扫描哪些包,* 通过扫描,将含有 @Componet 注解的 Bean 装入 Spring 容器中。* value 和 basePackages 成果一样,其默认值为配置类所在包及其子包。**/
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    /** 扫描哪些类 **/
    Class<?>[] basePackageClasses() default {};

    /**Bean Name 生成器:自定义 bean 的命名生成规定 **/
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

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

    /** 作用域代理 **/
    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

    /** 资源的匹配模式,默认就是.Class**/
    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 {};}
}

例如:

@ComponetScan(basePackages="com.klasdq.sb.service.*"
,excludeFilters=(@Filter(classes="UtilService.Class")))

这样的一个例子中,basePcakages指定了扫描 service 包下所有具体 @Component 注解的 Service Bean(@Service蕴含了 @Component)。而excludeFilters 定义应用 @Filter 过滤掉UtilService.Class。其余的参数应用,能够参数 API 文档中的介绍,大同小异。

  • @ComponetScans 这个注解也能够用于扫描组件,能够定义@ComponetScan,如:
@ComponentScans(value = { @ComponentScan(value = "com.klasdq.sb.service.*"),
                         @ComponentScan(value = "com.klasdq.sb.dao.*", excludeFilters=(@Filter(classes="UtilDao.Class")) })

通过这样一种形式来定义多个扫描组件,使得扫描更加准确。因为 @ComponentScan(value="com.klasdq.sb.*") 全包扫描的形式尽管写起来简略,然而消耗的工夫代价却是极大的。


最初,最近很多小伙伴找我要Linux 学习路线图,于是我依据本人的教训,利用业余时间熬夜肝了一个月,整顿了一份电子书。无论你是面试还是自我晋升,置信都会对你有帮忙!

收费送给大家,只求大家金指给我点个赞!

电子书 | Linux 开发学习路线图

也心愿有小伙伴能退出我,把这份电子书做得更完满!

有播种?心愿老铁们来个三连击,给更多的人看到这篇文章

举荐浏览:

  • 干货 | 程序员进阶架构师必备资源免费送
  • 神器 | 反对搜寻的资源网站
退出移动版