老生再谈 IoC

2024-01-01 17:38
文章标签 ioc 再谈 老生

本文主要是介绍老生再谈 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类型**/@NullableClass<?> 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> {@Autowiredprivate User user;@Overridepublic User getObject() throws Exception {return user;}@Overridepublic 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 */@DeprecatedAutowire 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.*")全包扫描的方式虽然写起来简单,但是耗费的时间代价却是极大的。

推荐阅读
你亲手写的代码,正在出卖你
深夜,聊聊架构设计深夜,分享一个Git小技巧编程·思维·职场
欢迎扫码关注

这篇关于老生再谈 IoC的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/560028

相关文章

Spring6详细学习笔记(IOC+AOP)

一、Spring系统架构介绍 1.1、定义 Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。Spring官网 Spring是一款主流的Java EE 轻量级开源框架,目的是用于简化Java企业级引用的开发难度和开发周期。从简单性、可测试性和松耦合度的角度而言,任何Java应用都可以从Spring中受益。Spring框架提供自己提供功能外,还提供整合其他技术和框架

spring ioc容器的原理

1. IoC理论的背景 我们都知道,在采用面向对象方法设计的软件系统中,它的底层实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑。 图1:软件系统中耦合的对象 如果我们打开机械式手表的后盖,就会看到与上面类似的情形,各个齿轮分别带动时针、分针和秒针顺时针旋转,从而在表盘上产生正确的时间。图1中描述的就是这样的一个齿轮组,它拥有多个独立的齿轮,这些齿轮相互啮合在一起,协

SpringIOC源码分析(六)__IOC容器的初始化__BeanDefinition载入注册

一、前情提要 上一篇提到了IOC容器初始化的流程,及资源定位,本篇主要分析BeanDefinition的载入过程 二、BeanDefiniton载入解析 BeanDefinition是IOC容器对Bean的数据承载模型,IOC容器对Bean的管理和依赖注入的实现,是通过对BeanDefinition的各种操作来实现的,IOC容器通过一个HashMap来保持和维护这些BeanDefinit

SpringIOC源码分析(五)__IOC容器的初始化__资源加载

一、IOC容器初始化过程 IOC容器初始化是由如上篇所讲到的refresh(),方法来启动的,启动包括BeanDefinition的Resource定位,载入和注册这个主要过程。 过程一:resource定位,是由resourceLoader通过getResource(String location) 的接口来完成的。过程二:将用户定义的Bean表示为IOC容器的内部数据结构BeanDef

Spring-容器:IOC-基于XML管理Bean

目录 一、概述1.1、定义1.2、简介1.3、作用1.4依赖注入1.4.1定义1.4.2作用1.4.3实现方式1.5实现 二、基于XML管理Bean(了解)2.1、获取Bean的方式2.2、依赖注入2.2.1、根据setter注入2.2.2、根据构造器注入 2.3、特殊值处理2.3.1、字面量赋值2.3.2、null值2.3.3、xml实体2.3.4、CDATA节 2.4、为对象类型属性赋

再谈协议--定制协议

目录 1 协议 2 自定义协议 3 常用的序列化和反序列化方法 1 协议 协议是一种约定,这是我们前面的理解。 在我们之前使用 socket 进行 udp或者tcp通信时,我们默认都是按照字符串或者说字节流的方式来发送和读取的,可是如果我们要传输一些结构化的数据,该怎么办呢? 就比如我们使用qq进行聊天,可能我们发送一条消息,实际上发送到网络中的不止这条消息本身,还有

【Spring基础1】- Spring 启示录-理解IoC控制反转

目录 1-1 通过代码引出Spring分析上述程序问题 1-2 OCP 开闭原则1-3 依赖倒置原则(DIP原则)1-4 控制反转IoC1-5 Spring框架 1-1 通过代码引出Spring 假设有 MVC 三层模式下,有 Dao、Service、Web层 Dao 实现了 UserDao、UserDaoImplForMySQLServic 实现了 UserServic

Spring(三):IoC容器装配Bean(xml配置方式和注解方式)

XML配置方式 一、三种实例化Bean的方式 1.使用类构造器实例化(默认无参数) <bean id=“personService" class="cn.itcast.bean.impl.PersonServiceImpl"/> 2.使用静态工厂方法实例化 <bean id="personService"          class="com.itcast.factory.PersonSe

Spring(二):IOC和DI的理解

一、IOC和DI的理解         在平时的java应用开发中,我们要实现某一个功能或者说是完成某个业务逻辑时至少需要两个或以上的对象来协作完成,在没有使用Spring的时候,每个对象在需要使用他的合作对象时,自己均要使用像new object() 这样的语法来将合作对象创建出来,这个合作对象是由自己主动创建出来的,创建合作对象的主动权在自己手上,自己需要哪个合作对象,就主动去创建,创建合作

Spring6学习笔记2:容器IoC

文章目录 3 容器:IoC3.1 IoC容器3.1.2 依赖注入3.1.3 IoC容器在Spring的实现 3.2 基于XML管理Bean3.2.1 搭建子模块spring6-ioc-xml3.2.2 实验一:获取bean①方式一:根据id获取②方式二:根据类型获取③方式三:根据id和类型④注意的地方⑤扩展知识:一个接口实现类获取过程 3.2.3 实验二:依赖注入之setter注入3.2.4