美文网首页
老生再谈 IoC

老生再谈 IoC

作者: 良许Linux | 来源:发表于2020-07-23 19:15 被阅读0次

    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开发学习路线图

    也希望有小伙伴能加入我,把这份电子书做得更完美!

    有收获?希望老铁们来个三连击,给更多的人看到这篇文章

    推荐阅读:

    相关文章

      网友评论

          本文标题:老生再谈 IoC

          本文链接:https://www.haomeiwen.com/subject/vyzhlktx.html