(一)spirng IOC、DI的详细应用

一、spring核心概念 IOC和DI

  IOC即控制反转,这是一种设计思想,有反转既有正转,那么什么是反转什么是正转呢?

  • 控制反转控制反转,首先是控制,再者是反转。
  • 所谓控制,在传统JAVA SE设计标准中,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
  • 所谓反转,有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
  • 所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

      那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

  • IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

      理解了IoC和DI的概念后,一切都将变得简单明了,剩下的工作只是在spring的框架中堆积木而已。

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

  配置spring 容器有多种形式,起初是以xml配置文件为主流,这一方式复杂且繁琐,后期代码量大的时候难以维护。后来注解形式的配置变成了为主流,springboot便是其集大成者。

  1.   xml配置文件的形式

<?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. 基于读取配置类的形式定义Bean信息

  •   定义配置类,配置类可以直接定义一些列的bean,也可以通过扫描的形式加入bean
@Configuration 
public class MainConfig {
  @Bean
  public Person person(){
   return new Person(); 
  } 
}
  • 在配置类上写@CompentScan注解来进行包扫描,这种形式可以排除或者指定某些bean。
@Configuration
 @ComponentScan(basePackages = {"com.tuling.testcompentscan"})
 public class MainConfig {
 }
    •   排除用法 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 {
 }
    •   包含用法 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 { 
}
    • @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
   其中,ANNOTATION 和 ASSIGNABLE_TYPE 区别在于,后者一次性只能指定一个类,而前者包含所有带有该注解的类。
      •     FilterType.CUSTOM 自定义类型如何使用
public class TulingFilterType implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader,
        MetadataReaderFactory metadataReaderFactory) throws IOException {
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        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 {
}
  • 去容器中读取Bean的信息(传入配置类)
public static void main( String[] args ) { 
  AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class); 
  System.out.println(ctx.getBean("person"));
 }

   

  3.配置Bean的作用域对象

  • @Scope指定的作用域方法取值,其中单例和多例多常用,最后两个几乎用不到
a) singleton 单实例的(默认) 
b) prototype 多实例的 
c) request 同一次请求 
d) session 同一个会话级别
  • 在不指定@Scope的情况下,所有的bean都是单实例的bean,而且是饿汉加载(容器启动实例就创建好了)
@Bean 
public Person person() { return new Person(); }
  • 指定@Scope为 prototype表示为多实例的,而且还是懒汉模式加载(IOC容器启动的时候,并不会创建对象,而是在第一次使用的时候才会创建)
@Bean 
@Scope(value = "prototype")
 public Person person() { 
return new Person();
}
  • Bean的懒加载@Lazy(主要针对单实例的bean 容器启动的时候,不创建对象,在第一次使用的时候才会创建该对象)
@Bean 
@Lazy
 public Person person() { return new Person(); }
  • @Conditional进行条件判断等.
  场景,有二个组件TulingAspect 和TulingLog ,我的TulingLog组件是依赖于TulingAspect的组件
       应用:自己创建一个TulingCondition的类 实现Condition接口 
public class TulingCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context,
        AnnotatedTypeMetadata metadata) {
        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();
    }
}

  4.往IOC 容器中添加组件的方式

  • ①:通过@CompentScan +@Controller @Service @Respository @componet

    适用场景: 针对我们自己写的组件可以通过该方式来进行加载到容器中。

  • ②:通过@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, TulingImportSelector.class, TulingBeanDefinitionRegister.class}
)
public class MainConfig {
}
  • ④:通过实现FacotryBean接口来实现注册组件 
public class CarFactoryBean implements FactoryBean<Car> {
    @Override
    public Car getObject() throws Exception {
        return new Car();
    }

/返回bean的类型
    @Override
    public Class<?> getObjectType() {
        return Car.class;
    }
/是否为单例
    @Override
    public boolean isSingleton() {
        return true;
    }
}

  5.BeanFactory和ApplicationContext(bean工厂和应用上下文)

   

  • Bean 工厂(com.springframework.beans.factory.BeanFactory)是Spring 框架最核心的接口,它提供了高级IoC 的配置机制。
  • 应用上下文(com.springframework.context.ApplicationContext)建立在BeanFactory 基础之上。

     由上图可以知道,BeanFactory是最顶层接口,而ApplicationContext则集成BeanFactory,增加了许多高级特性。

  • ApplicationContext 的初始化和BeanFactory 有一个重大的区别:BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean 时才实例目标Bean;而ApplicationContext 则在初始化应用上下文时就实例化所有单实例的Bean 

  6.bean的生命周期

  • 指定bean的初始化和销毁方法    
  bean的创建----->初始化----->销毁方法
    •   针对单实例bean的话,容器启动的时候,bean的对象就创建了,而且容器销毁的时候,也会调用Bean的销毁方法
    •   针对多实例bean的话,容器启动的时候,bean是不会被创建的而是在获取bean的时候被创建,而且bean的销毁不受IOC容器的管理. 
  由容器管理Bean的生命周期,我们可以通过自己指定bean的初始化方法和bean的销毁方法
@Configuration
public class MainConfig { //指定了bean的生命周期的初始化方法和销毁方法.
    @Bean(initMethod = "init", destroyMethod = "destroy")
    public Car car() {
        return new Car();
    }
}
  • 通过 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()方法
 }
  • 自动装配
    •     @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;
    •     @Resource(JSR250规范) 
    功能和@AutoWired的功能差不多一样,但是不支持@Primary 和@Qualifier的支持 
    •     @InJect(JSR330规范)
    需要导入jar包依赖,功能和支持@Primary功能 ,但是没有Require=false的功能
    •   使用autowired 可以标注在方法上 
        标注在set方法上 
@Autowired
 public void setTulingLog(TulingLog tulingLog) { 
  this.tulingLog = tulingLog; 
}
        标注在构造方法上
@Autowired
 public TulingAspect(TulingLog tulingLog) {
   this.tulingLog = tulingLog;
 }
    •   我们自己的组件 需要使用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;
    }
}
原文地址:https://www.cnblogs.com/shyroke/p/14532664.html