IoC,Spring的核心理念之一,确实这是一个老生常谈的东西。但是今天呢!又重新温习之后,想再说说自己对 IOC 的一些想法。
IoC——Inversion of Control,控制反转。要想理解IoC还是要从其本身出发,首先就 控制 而言,控制是对谁的控制——是对象的控制。其次,反转是什么的反转或者说为什么要称做反转——是对象控制权反转。
对象控制,传统的方式就是程序员通过 new
关键字的方式来生成一个对象,然后由程序员根据程序逻辑人为地控制对象的使用。从这里出发,就可以很好地理解什么是控制反转了。
所谓控制反转,就是将原本在程序员手中的对象创建和管理的权限交给了Spring IoC容器。也就是说,控制反转就是要转移程序员对对象的控制权,而在Spring当中的实现就是Spring IoC容器通过Xml或注解的描述生成或者获取对象,再由IoC容器对这些Bean进行管理。
所以,理解IoC(控制反转),就只需要记住,控制权由谁反转给了谁。
对于 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); }
FactoryBean
的接口,这个类有一个 T getObject() throws Exception;
这样的方法,这个方法会返回一个对象实例。对于这个接口的实现类而言,通过 BeanFactory
的 getBean()
返回的 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更具体的用法,可以参考工厂模式当中工厂的作用。
//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) } }
学习过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
这个注解的作用就在于它标示的类拥有一个或多个 @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
修饰的方法能否被代理。
@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()
方法。其他参数,看名字就知道什么意图,就不再多解释了。
ApplicationContext
类的具体实现类之一,用于注解形式的Bean的生成。与之相对应的还有 ClassPathXmlApplicationContext
从XML文件中获取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
的源码很简单:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Indexed public @interface Component { String value() default ""; }
参数当中只有一个 value
,用于声明Bean的名字(标识)。这里又出现一个新的注解 @Indexed
,顾名思义这个注解就是增加一个索引,这是因为Spring Boot当中大量采用扫描的形式来装配Bean之后,扫描的Bean越多,解析时间就越长,为了提高性能,在5.0版本的时候就引入了这样一个注解。
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Value { String value(); }
这个注解可以用在字段、方法、方法参数、注解上,通过一个表达式或者具体字符串为其传入相应的值。 @Value
是一个功能非常强大的注解,建议对其多做了解。
其功能主要包括以下几种:
@Value("classpath:com/demo/config.txt") @Value("http://www.baidu.com")
@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文档中的介绍,大同小异。
@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开发学习路线图
也希望有小伙伴能加入我,把这份电子书做得更完美!
很遗憾的说,推酷将在这个月底关闭。人生海海,几度秋凉,感谢那些有你的时光。
原文 https://segmentfault.com/a/1190000023354397