01、IOC容器设计理念与核心注解的使用

一、Spring框架功能整体介绍

1、SpringCoreContainer

模块作用: Core 和 Beans 模块是框架的基础部分,提供 IoC (转控制)和依赖注入特性。 这里的基础 概念是 BeanFactory,它提供对 Factory 模式的经典实现来消除对程序性单例模式的需要,并真 正地允许你从程序逻辑中分离出依赖关系和配置。

1.1)Core模块

主要包含 Spring 框架基本的核心工具类, Spring 的其他组件都要用到这个包 里的类, Core模块是其他组件的基 本核心。

1.2)Beans (BeanFacotry的作用)

它包含访问配直文件、创建和管理 bean 以及进行 Inversion of Control I Dependency Injection (IoC/DI)操作相关的所有类。

1.3)Context(处理BeanFactory,一下还是ApplicationContext的作用)

模构建于 Core 和 Beans 模块基础之上,提供了一种类似JNDI 注册器的框 架式的对象访问方法。 Context 模块继承了 Beans 的特性,为 Spring 核心提供了大量 扩展,添加了对 国际化(例如资源绑定)、事件传播、资源加载和对 Context 的透明创 建的支持。 Context 模块同时 也支持 J2EE 的一些特性, ApplicationContext 接口是 Context 模块的关键
本质区别:使用BeanFacotry的bean是延时加载的,ApplicationContext是非延时加载的

1.4)Expression Language

模块提供了强大的表达式语言,用于在运行时查询和操纵对象。 它是 JSP 2.1 规范中定义的 unifedexpression language 的扩展。 该语言支持设直/获取属 性的值,属性的分配,方法的调用,访问数组上下文( accessiong the context of arrays )、 容器和索引器、逻辑和算术运算符、命名变量以及从Spring的 IoC 容器中根据名称检 索对象。 它也支持 list 投影、选择和一般的 list 聚合.

2、Spring Data Access/Integration

2.1) JDBC

模块提供了一个 JDBC 抽象层,它可以消除冗长的 JDBC 编码和解析数据库厂 商特有的错误代码。这个模块包含了 Spring 对 JDBC 数据访问进行封装的所有类。

2.2) ORM 模块为流行的对象-关系映射 API

如 JPA、 JDO、 Hibernate、 iBatis 等,提供了 一个交互层。 利用 ORM 封装包,可以混合使用所有 Spring 提供的特性进行 O/R 映射, 如前边提到的简单声 明性事务管理。

2.3) OXM 模块提供了一个对 ObjecνXML 映射实现的抽象层

Object/XML 映射实现包括 JAXB、 Castor、 XMLBeans、 JiBX 和 XStrearn

2.4) JMS ( Java Messaging Service)

模块主要包含了一些制造和消 费消息的特性。

2.5) Transaction

支持编程和声明性的事务管理,这些事务类必须实现特定的接口,并 且对所有的 POJO 都适用

3、Spring Web

Web 模块:提供了基础的面向 Web 的集成特性c 例如,多文件上传、使用 servlet listeners 初始化
IoC 容器以及一个面向 Web 的应用上下文。 它还包含 Spring 远程支持中 Web 的相关部分。

4、Spring Aop

  1. Aspects 模块提供了对 AspectJ 的集成支持。
  2. Instrumentation 模块提供了 class instrumentation 支持和 classloader 实现,使得可以在特
    定的应用服务器上使用

5、Test

Test 模块支持使用 JUnit 和 TestNG 对 Spring 组件进行测试

6、Spring 容器继承图:

7、依赖注入控制反转

7.1) 什么是控制反转?我觉得有必要先了解软件设计的一个重要思想:依赖倒置原则(Dependency Inversion Principle )

①:什么是依赖倒置原则? 假设我们设计一辆汽车:先设计轮子,然后根据轮子大小设计底盘,接着根据底盘设计车身,最后根据车身设计好整个汽车。这里就出现了一个“依赖”关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子。

上图看上去没有什么毛病?但是 万一轮胎尺寸改了,那么地盘需要改,地盘改了,车身也改了,让后整个汽车构造都改了.
然后汽车公司倒闭了!!!
....................................................................................................................................................
董事长依赖总经理争取,总经理依赖部门经理挣钱,部门经理依赖员工争取,那么员工离职了怎么
办???
....................................................................................................................................................
反过来:假如汽车公司决定修改轮胎的 我们就只需要改动轮子的设计,而不需要动底盘,车身,汽车的设计了。

IOC容器的最最最最核心思想........................
ioc的思想最核心的地方在于,资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处。第一,资源集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度。

二、Spring IOC 容器底层注解使用

2.1) xml配置文件的形式 VS 配置类的形式

2.1.1) 基于xml的形式定义Bean的信息
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
   
    //定义一个Bean的信息
    <bean id="car" class="com.tuling.compent.Car"></bean>
</beans>

去容器中读取bean

public static void main( String[] args ){
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    System.out.println(ctx.getBean("person"));
}
2.1.2) 基于读取配置类的形式定义Bean信息
@Configuration
public class MainConfig {
    @Bean
    public Person person(){
        return new Person();
    }
}

注意: 通过@Bean的形式是使用的话, bean的默认名称是方法名,若@Bean(value="bean的名称")那么bean的名称是指定的

去容器中读取Bean的信息(传入配置类)

public static void main( String[] args ){
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);
    System.out.println(ctx.getBean("person"));
}

2.2) 在配置类上写@CompentScan注解来进行包扫描

@Configuration
@ComponentScan(basePackages = {"com.tuling.testcompentscan"})
public class MainConfig {
}
2.2.1)排除用法 excludeFilters(排除@Controller注解的,和TulingService的)
@Configuration
@ComponentScan(basePackages = {"com.tuling.testcompentscan"},excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {TulingService.class})
})
public class MainConfig {
}
2.2.2)包含用法 includeFilters ,注意,若使用包含的用法, 需要把useDefaultFilters属性设置为false(true表示扫描全部的)
@Configuration
@ComponentScan(basePackages = {"com.tuling.testcompentscan"},includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class, Service.class})
},useDefaultFilters = false)
public class MainConfig {
}
2.2.3)@ComponentScan.Filter type的类型

a)注解形式的FilterType.ANNOTATION @Controller @Service @Repository @Compent
b)指定类型的 FilterType.ASSIGNABLE_TYPE @ComponentScan.Filter(type =
FilterType.ASSIGNABLE_TYPE,value = {TulingService.class})
c)aspectj类型的 FilterType.ASPECTJ(不常用)
d)正则表达式的 FilterType.REGEX(不常用)
e)自定义的 FilterType.CUSTOM

public enum FilterType {
    //注解形式  比如@Controller @Service @Repository  @Compent
    ANNOTATION,
    //指定的类型
    ASSIGNABLE_TYPE,
    //aspectJ形式的
    ASPECTJ,
    //正则表达式的
    REGEX,
    //自定义的
    CUSTOM
}

FilterType.CUSTOM 自定义类型如何使用

public class TulingFilterType implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //获取当前类的注解源信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取当前类的class的源信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //获取当前类的资源信息
        Resource resource =  metadataReader.getResource();
        if(classMetadata.getClassName().contains("dao")) {
            return true;
        }
        return false;
    }
}
@ComponentScan(basePackages = {"com.tuling.testcompentscan"},includeFilters = {
        @ComponentScan.Filter(type = FilterType.CUSTOM,value = TulingFilterType.class)
},useDefaultFilters = false)
public class MainConfig {
}

2.3配置Bean的作用域对象

2.3.1)在不指定@Scope的情况下,所有的bean都是单实例的bean,而且是饿汉加载(容器启动实例就创建好了)
@Bean
public Person person() {
    return new Person();
}
2.3.2)指定@Scope为 prototype 表示为多实例的,而且还是懒汉模式加载(IOC容器启动的时候,并不会创建对象,而是在第一次使用的时候才会创建)
@Bean
@Scope(value = "prototype")
public Person person() {
    return new Person();
}
2.3.3)@Scope指定的作用域方法取值

a) singleton 单实例的(默认)
b) prototype 多实例的
c) request 同一次请求
d) session 同一个会话级别

2.4)Bean的懒加载@Lazy(主要针对单实例的bean 容器启动的时候,不创建对象,在第一次使用的时候才会创建该对象)

@Bean
@Lazy
public Person person() {
    return new Person();
}

2.5)@Conditional进行条件判断等

场景,有二个组件TulingAspect 和TulingLog ,我的TulingLog组件是依赖于TulingAspect的组件应用:自己创建一个TulingCondition的类 实现Condition接口

public class TulingCondition implements Condition {
    /**
     *
     * @param context
     * @param metadata
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //判断容器中是否有tulingAspect的组件
        if(context.getBeanFactory().containsBean("tulingAspect")) {
            return true;
        }
        return false;
    }
}

public class MainConfig {
    @Bean
    public TulingAspect tulingAspect() {
        return new TulingAspect();
    }
   
    //当切 容器中有tulingAspect的组件,那么tulingLog才会被实例化.
    @Bean
    @Conditional(value = TulingCondition.class)
    public TulingLog tulingLog() {
        return new TulingLog();
    }
}

2.6)往IOC 容器中添加组件的方式

①:通过@CompentScan +@Controller @Service @Respository @compent
适用场景: 针对我们自己写的组件可以通过该方式来进行加载到容器中。
②:通过@Bean的方式来导入组件(适用于导入第三方组件的类)
③:通过@Import来导入组件 (导入组件的id为全类名路径)

@Configuration
@Import(value = {Person.class, Car.class})
public class MainConfig {
}

通过@Import 的ImportSeletor类实现组件的导入 (导入组件的id为全类名路径)

public class TulingImportSelector implements ImportSelector {
    /可以获取导入类的注解信息
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.tuling.testimport.compent.Dog"};
    }
}
@Configuration
@Import(value = {Person.class, Car.class, TulingImportSelector.class})
public class MainConfig {
}

通过@Import的 ImportBeanDefinitionRegister导入组件 (可以指定bean的名称)

public class TulingBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //创建一个bean定义对象
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Cat.class);
        //把bean定义对象导入到容器中
        registry.registerBeanDefinition("cat",rootBeanDefinition);
    }
}
@Configuration
//@Import(value = {Person.class, Car.class})
//@Import(value = {Person.class, Car.class, TulingImportSelector.class})
@Import(value = {Person.class, Car.class, TulingImportSelector.class, TulingBeanDefinitionRegister.class})
public class MainConfig {
}

④:通过实现FacotryBean接口来实现注册 组件
使用场景:类的初始化过程比较复杂的时候,使用该方式(原理:工厂方法模式)

public class CarFactoryBean implements FactoryBean<Car> {
   
    /返回bean的对象
    @Override
    public Car getObject() throws Exception {
        return new Car();
    }
   
    /返回bean的类型
    @Override
    public Class<?> getObjectType() {
        return Car.class;
    }
   
    /是否为单利
    @Override
    public boolean isSingleton() {
        return true;
    }
}

2.7)Bean的初始化方法和销毁方法.

①:什么是bean的生命周期?
bean的创建----->初始化----->销毁方法
由容器管理Bean的生命周期,我们可以通过自己指定bean的初始化方法和bean的销毁方法

@Configuration
public class MainConfig {
   
    //指定了bean的生命周期的初始化方法和销毁方法.
    @Bean(initMethod = "init",destroyMethod = "destroy")
    public Car car() {
        return new Car();
    }
}

针对单实例bean的话,容器启动的时候,bean的对象就创建了,而且容器销毁的时候,也会调用Bean的销毁方法
针对多实例bean的话,容器启动的时候,bean是不会被创建的而是在获取bean的时候被创建,而且bean的销毁不受IOC容器的管理.

②:通过 InitializingBean和DisposableBean 的二个接口实现bean的初始化以及销毁方法

@Component
public class Person implements InitializingBean,DisposableBean {
    public Person() {
        System.out.println("Person的构造方法");
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean的destroy()方法 ");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean的 afterPropertiesSet方法");
    }
}

③:通过JSR250规范 提供的注解@PostConstruct 和@ProDestory标注的方法

@Component
public class Book {
    public Book() {
        System.out.println("book 的构造方法");
    }
    @PostConstruct
    public void init() {
        System.out.println("book 的PostConstruct标志的方法");
    }
    @PreDestroy
    public void destory() {
        System.out.println("book 的PreDestory标注的方法");
    }
}

④:通过Spring的BeanPostProcessor的 bean的后置处理器会拦截所有bean创建过程
postProcessBeforeInitialization 在init方法之前调用
postProcessAfterInitialization 在init方法之后调用

@Component
public class TulingBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("TulingBeanPostProcessor...postProcessBeforeInitialization:"+beanName);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("TulingBeanPostProcessor...postProcessAfterInitialization:"+beanName);
        return bean;
    }
}

BeanPostProcessor的执行时机

populateBean(beanName, mbd, instanceWrapper)
initializeBean{
    applyBeanPostProcessorsBeforeInitialization()
    invokeInitMethods{
        isInitializingBean.afterPropertiesSet
        自定义的init方法
    }
    applyBeanPostProcessorsAfterInitialization()方法
}

2.8)通过@Value + @PropertySource来给组件赋值

public class Person {
  
    //通过普通的方式
    @Value("司马")
    private String firstName;
   
    //spel方式来赋值
    @Value("#{28-8}")
    private Integer age;
    通过读取外部配置文件的值
    @Value("${person.lastName}")
    private String lastName;
}   

@Configuration
@PropertySource(value = {"classpath:person.properties"}) //指定外部文件的位置
public class MainConfig {
    @Bean
    public Person person() {
        return new Person();
    }
}

2.9)自动装配

@AutoWired的使用
自动注入:

//一个Dao
@Repository
public class TulingDao {
}
@Service
public class TulingService {
    @Autowired
    private TulingDao tulingDao;
}

结论:

a:自动装配首先是按照类型进行装配,若在IOC容器中发现了多个相同类型的组件,那么就按照 属性名称来进行装配

@Autowired
private TulingDao tulingDao;

比如,我容器中有二个TulingDao类型的组件 一个叫tulingDao 一个叫tulingDao2,那么我们通过@AutoWired 来修饰的属性名称时tulingDao,那么拿就加载容器的tulingDao组件,若属性名称为tulignDao2 那么他就加载的时tulingDao2组件

b:假设我们需要指定特定的组件来进行装配,我们可以通过使用@Qualifier("tulingDao")来指定装配的组件或者在配置类上的@Bean加上@Primary注解

@Autowired
    @Qualifier("tulingDao")
    private TulingDao tulingDao2;

c:假设我们容器中即没有tulingDao 和tulingDao2,那么在装配的时候就会抛出异常
No qualifying bean of type 'com.tuling.testautowired.TulingDao' available
若我们想不抛异常 ,我们需要指定 required为false的时候可以了

 @Autowired(required = false)
    @Qualifier("tulingDao")
    private TulingDao tulingDao2;

d:@Resource(JSR250规范)
功能和@AutoWired的功能差不多一样,但是不支持@Primary 和@Qualifier的支持

e:@InJect(JSR330规范)
需要导入jar包依赖
功能和支持@Primary功能 ,但是没有Require=false的功能

<dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
</dependency>

f:使用autowired 可以标注在方法上
标注在set方法上

//@Autowired
    public void setTulingLog(TulingLog tulingLog) {
        this.tulingLog = tulingLog;
    }

标注在构造方法上:

@Autowired
    public TulingAspect(TulingLog tulingLog) {
        this.tulingLog = tulingLog;
    }

标注在配置类的入参中(可以不写)

@Bean
    public TulingAspect tulingAspect(@Autowired TulingLog tulingLog) {
        TulingAspect tulingAspect = new TulingAspect(tulingLog);
        return tulingAspect;
    }

3.0) 我们自己的组件 需要使用spring ioc的底层组件的时候,比如 ApplicationContext等

我们可以通过实现XXXAware接口来实现

@Component
public class TulingCompent implements ApplicationContextAware,BeanNameAware {
    private ApplicationContext applicationContext;
    @Override
    public void setBeanName(String name) {
        System.out.println("current bean name is :【"+name+"】");
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

3.1) 通过@Profile注解 来根据环境来激活标识不同的Bean

@Profile标识在类上,那么只有当前环境匹配,整个配置类才会生效
@Profile标识在Bean上 ,那么只有当前环境的Bean才会被激活
没有标志为@Profile的bean 不管在什么环境都可以被激活

@Configuration
@PropertySource(value = {"classpath:ds.properties"})
public class MainConfig implements EmbeddedValueResolverAware {
    @Value("${ds.username}")
    private String userName;
    @Value("${ds.password}")
    private String password;
    private String jdbcUrl;
    private String classDriver;
    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.jdbcUrl = resolver.resolveStringValue("${ds.jdbcUrl}");
        this.classDriver = resolver.resolveStringValue("${ds.classDriver}");
    }
   
    //标识为测试环境才会被装配
    @Bean
    @Profile(value = "test")
    public DataSource testDs() {
        return buliderDataSource(new DruidDataSource());
    }
   
    //标识开发环境才会被激活
    @Bean
    @Profile(value = "dev")
    public DataSource devDs() {
        return buliderDataSource(new DruidDataSource());
    }
   
    //标识生产环境才会被激活
    @Bean
    @Profile(value = "prod")
    public DataSource prodDs() {
        return buliderDataSource(new DruidDataSource());
    }
    private DataSource buliderDataSource(DruidDataSource dataSource) {
        dataSource.setUsername(userName);
        dataSource.setPassword(password);
        dataSource.setDriverClassName(classDriver);
        dataSource.setUrl(jdbcUrl);
        return dataSource;
    }
}

激活切换环境的方法
方法一:通过运行时jvm参数来切换 -Dspring.profiles.active=test|dev|prod
方法二:通过代码的方式来激活

public static void main(String[] args) {
     
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.getEnvironment().setActiveProfiles("test","dev");
    ctx.register(MainConfig.class);
    ctx.refresh();
    printBeanName(ctx);
}
``
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335