专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

老生再谈 IoC

**IoC**,Spring的核心理念之一,确实这是一个老生常谈的东西。但是今天呢!又重新温习之后,想再说说自己对**IOC**的一些想法。

IoC——Inversion of Control,控制反转。要想理解IoC还是要从其本身出发,首先就**控制**而言,控制是对谁的控制——是对象的控制。其次,反转是什么的反转或者说为什么要称做反转——是对象控制权反转。

对象控制,传统的方式就是程序员通过`new`关键字的方式来生成一个对象,然后由程序员根据程序逻辑人为地控制对象的使用。从这里出发,就可以很好地理解什么是控制反转了。

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

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

## IoC容器

##### 顶级IoC容器接口—BeanFactory

对于`BeanFactory`,它的重要性源自于所有IoC容器都是直接或者间接派生自它。虽然,它的功能不是很强大,但是从其源码当中却可以看出很多端倪。

```java
public interface BeanFactory {
/**
工厂Bean的前缀,
用于判断获取的是FactoryBean还是FactoryBean所产生的实例 下面会有详细解释
**/
String FACTORY_BEAN_PREFIX = “&”;

/**通过name 获取Bean**/
Object getBean(String name) throws BeansException;
/**通过name和Class类型 获取Bean**/
T getBean(String name, @Nullable Class requiredType) throws BeansException;
/**通过name和构造参数,也就是可以指定调用某个构造方法 获取Bean**/
Object getBean(String name, Object… args) throws BeansException;
/**通过Class类型 获取Bean**/
T getBean(Class requiredType) throws BeansException;
/**通过Class类型和构造参数,同样可以指定调用某个构造方法 获取Bean**/
T getBean(Class requiredType, Object… args) throws BeansException;

/**返回一个被ObjectProvider包装的Bean**/
ObjectProvider getBeanProvider(Class requiredType);
ObjectProvider 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][Nullable]
Class getType(String name) throws NoSuchBeanDefinitionException;

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

}
```

+ **FACTORY_BEAN_PREFIX** 在Spring当中,有一个叫做`FactoryBean`的接口,这个类有一个`T getObject() throws Exception;`这样的方法,这个方法会返回一个对象实例。对于这个接口的实现类而言,通过`BeanFactory`的`getBean()`返回的`Bean`是实现类本身的实例,还是`getObject()`的返回实例就在于有没有前缀。有,返回`FactoryBean`;没有,返回`getObject()`的返回实例。

```java
/**举个简单的例子
实现这样一个FactoryBean
用这样一个FactoryBean来创建一个我们需要的User
**/
@Component(“user”)
public class UserFactoryBean implements FactoryBean {

@Autowired
private User user;

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

@Override
public Class getObjectType() \{
return user.getClass();
\}
\}
\`\`\`

\`\`\`java
//测试方法
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存在多个时出现的异常情况。

\`\`\`java
//getIfAvailable()可以解决容器中没有userDao时的异常
public class UserService\{
private UserDao userDao;
public UserService(ObjectProvider dao)\{
userDao = dao.getIfAvailable();
\}
\}

//5、1之后可以通过流式处理来解决容器中存在多个userDao情况
public class UserService\{
private UserDao userDao;
public UserService(ObjectProvider dao)\{
userDao = dao.orderedStream()
.findFirst()
.orElse(null)
\}
\}
\`\`\`

#\#\#\#\# 核心容器—ApplicationContext

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

!\[IoC Container.png\](https://i.loli.net/2020/06/03/4RrOIMGcy9Pu5wt.png)

\*Spring的源码很庞大,也很复杂,所以建议学习的时候,从某几个重点类开始,分析其继承、扩展关系,以此横向展开对Spring的认识。\*

这里也就不再对\`ApplicationContext\`的各个继承接口一一解释了,API文档里面都有: \[ApplicationContext\](https://www.apiref.com/spring5/org/springframework/context/ApplicationContext.html)。对于\`ApplicationContext\`这个容器更多的是侧重于对它的应用介绍,就是如何通过这个容器来获取Bean。

通过一个简单的例子来了解一下:

\`\`\`java
//普通的JavaBean
public class User \{
private Long id;
private String name;
private int age;
/\*\*getter,setter,toString\*\*/
\}
\`\`\`

\`\`\`java
//配置类,采用注解的形式来配置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;
\}
\}
\`\`\`

\`\`\`java
//测试类
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\](https://www.apiref.com/spring5/org/springframework/context/annotation/Configuration.html) \*\*\`@Configuration\`这个注解的作用就在于它标示的类拥有一个或多个\`@Bean\`修饰的方法,这些方法会被Spring容器处理,然后用于生成Bean或者服务请求。

\`\`\`java
//@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\](https://www.apiref.com/spring5/org/springframework/context/annotation/Bean.html)\*\* 这个注解只用在方法上面,用于Spring容器管理生成Bean。

\`\`\`java
@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的形式了。

直接看例子:

\`\`\`java
//配置JavaBean
@Component(“klasdq2”)
public class User \{
@Value(“2”)
private Long id;
@Value(“klasdq2”)
private String name;
@Value(“19”)
private int age;
/\*\*getter,setter,toString\*\*/
\}
\`\`\`

\`\`\`java
//配置类扫描装配Bean
@Configuration
@ComponentScan
public class UserConfig \{
\}
\`\`\`

\`\`\`java
//测试类
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\`的源码很简单:

\`\`\`java
@Target(\{ElementType.TYPE\})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component \{
String value() default “”;
\}
\`\`\`

参数当中只有一个\`value\`,用于声明Bean的名字(标识)。这里又出现一个新的注解\`@Indexed\`,顾名思义这个注解就是增加一个索引,这是因为Spring Boot当中大量采用扫描的形式来装配Bean之后,扫描的Bean越多,解析时间就越长,为了提高性能,在5.0版本的时候就引入了这样一个注解。

\+ \*\*@Value\*\*

\`\`\`java
@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\*\*

\`\`\`java
@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 nameGenerator() default BeanNameGenerator.class;

/\*\*作用域解析器\*\*/
Class 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 {};
}
}
```

例如:

```java
@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`,如:

```java
@ComponentScans(value = { @ComponentScan(value = “com.klasdq.sb.service.*”),
@ComponentScan(value = “com.klasdq.sb.dao.*”, excludeFilters=(@Filter(classes=”UtilDao.Class”)) })
```

通过这样一种方式来定义多个扫描组件,使得扫描更加精确。因为`@ComponentScan(value=”com.klasdq.sb.*”)`全包扫描的方式虽然写起来简单,但是耗费的时间代价却是极大的。

文章永久链接:https://tech.souyunku.com/24595

未经允许不得转载:搜云库技术团队 » 老生再谈 IoC

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们