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;这样的方法,这个方法会返回一个对象实例。对于这个接口的实现类而言,通过BeanFactory的getBean()返回的Bean是实现类本身的实例,还是getObject()的返回实例就在于有没有前缀。有,返回FactoryBean;没有,返回getObject()的返回实例。

/**举个简单的例子

实现这样一个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();
}

}

//测试方法

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 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

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是一个功能非常强大的注解,建议对其多做了解。

其功能主要包括以下几种:

注入普通字符串

书写SpEL表达式,如:@Value("#{person.name}"),可以从配置文件、Bean属性、调用方法等等得到数据。

注入Resource,如:@Value("classpath:com/demo/config.txt") 使用Resource类型接收

注入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.*")全包扫描的方式虽然写起来简单,但是耗费的时间代价却是极大的。

老生再谈 IoC的更多相关文章

  1. [转载]再谈百度:KPI、无人机,以及一个必须给父母看的案例

    [转载]再谈百度:KPI.无人机,以及一个必须给父母看的案例 发表于 2016-03-15   |   0 Comments   |   阅读次数 33 原文: 再谈百度:KPI.无人机,以及一个必须 ...

  2. Support Vector Machine (3) : 再谈泛化误差(Generalization Error)

    目录 Support Vector Machine (1) : 简单SVM原理 Support Vector Machine (2) : Sequential Minimal Optimization ...

  3. Unity教程之再谈Unity中的优化技术

    这是从 Unity教程之再谈Unity中的优化技术 这篇文章里提取出来的一部分,这篇文章让我学到了挺多可能我应该知道却还没知道的知识,写的挺好的 优化几何体   这一步主要是为了针对性能瓶颈中的”顶点 ...

  4. 浅谈HTTP中Get与Post的区别/HTTP协议与HTML表单(再谈GET与POST的区别)

    HTTP协议与HTML表单(再谈GET与POST的区别) GET方式在request-line中传送数据:POST方式在request-line及request-body中均可以传送数据. http: ...

  5. Another Look at Events(再谈Events)

    转载:http://www.qtcn.org/bbs/simple/?t31383.html Another Look at Events(再谈Events) 最近在学习Qt事件处理的时候发现一篇很不 ...

  6. C++ Primer 学习笔记_32_STL实践与分析(6) --再谈string类型(下)

    STL实践与分析 --再谈string类型(下) 四.string类型的查找操作 string类型提供了6种查找函数,每种函数以不同形式的find命名.这些操作所有返回string::size_typ ...

  7. 再谈JSON -json定义及数据类型

    再谈json 近期在项目中使用到了highcharts ,highstock做了一些统计分析.使用jQuery ajax那就不得不使用json, 可是在使用过程中也出现了非常多的疑惑,比方说,什么情况 ...

  8. C++ Primer 学习笔记_44_STL实践与分析(18)--再谈迭代器【下】

    STL实践与分析 --再谈迭代器[下] 三.反向迭代器[续:习题] //P355 习题11.19 int main() { vector<int> iVec; for (vector< ...

  9. C++ Primer 学习笔记_43_STL实践与分析(17)--再谈迭代器【中】

    STL实践与分析 --再谈迭代器[中] 二.iostream迭代[续] 3.ostream_iterator对象和ostream_iterator对象的使用 能够使用ostream_iterator对 ...

  10. 再谈IE的浏览器模式和文档模式

    原文:再谈IE的浏览器模式和文档模式 以前在 “IE8兼容视图(IE7 mode)与独立IE7的区别”一文中曾经涉及过浏览器模式和文档模式,但二者的区别却不甚了了,现在有了新的认识,再补充一下. 1. ...

随机推荐

  1. MySQL---索引-性能-配置参数优化

    一般来说,要保证数据库的效率,要做好以下四个方面的工作:数 据库设计.sql语句优化.数据库参数配置.恰当的硬件资源和操作系统,这个顺序也表现了这四个工作对性能影响的大小.下面我们逐个阐明: 1.设计 ...

  2. MySQL如果数据存在则更新,不存在则插入

    如果数据存在则更新,不存在则插入,MySQL有duplicate.replace into.replace三种方式如何更新数据? insert ignore into 又是如何插入数据的呢? 准备表和 ...

  3. Codeforces Round 968 (Div. 2)

    题目链接:Codeforces Round 968 (Div. 2) - Codeforces 总结:C题想到了,但是写成shi了,出得有点慢. A. Turtle and Good String t ...

  4. 一、CAN协议基础知识

    一.CAN总线基础知识 注:参考江科大教程,瑞萨电子<CAN入门教程>. CAN (Controller Area Network),是ISO国际标准化的串行通信协议.CAN协议经过ISO ...

  5. C笔记---01基础篇

    一.C语言内存分区 1.程序代码区:存放 CPU 执行的机器指令. 2.数据区   2.1常量区:字符串.数字等常量存放在常量区,const修饰的全局变量存放在常量区:常量区的内存是只读的,程序结束后 ...

  6. Windows中GNURadio的安装

    对于一个常常使用Python的人来讲(此处指我),conda环境是必不可少的,(Anaconda或Miniconda). 在Windows中且已经安装过conda环境的情况下,安装GNURadio特别 ...

  7. Q:oracle解锁用户

    怎么查看oracle用户是否被锁 1.一般oracle数据库默认是10次尝试失败后锁住用户 1.查看FAILED_LOGIN_ATTEMPTS的值 select * from dba_profiles ...

  8. 项目中途引入Mybatis-plus后报错,报错Caused by: java.lang.ClassNotFoundException: org.mybatis.logging.LoggerFactory

    一.报错原因 mybatis-plus和pagehelper jar包冲突,注释mybatis-spring和pagehelper插件即可 <!-- SpringBoot集成mybatis框架 ...

  9. 【ABP】项目示例(2)——聚合根和实体

    聚合根和实体 在上一章节中,已经完成了项目搭建的前置准备,在这一章节中,实现领域层的聚合根和实体 创建名称为General.Backend.Domain的标准类库,分别新建名称为Entities.Se ...

  10. linux的使用(2)

    1,覆盖 > cat 文件名a>文件名b:将文档a覆盖文档b 2,追加 >> cat 文档名a>>文档名b:将文档a追加到文档b后 追加错误 上图所示:尽量使用字母 ...