Spring 初步的理解

理解IoC container

  1. 什么是IoC Container?
    IoC(Inversion of control)是一种设计思想,可以将设计好的对象交给容器去控制(包括对象的整个生命周期、以及对象之间的组装)。

  2. 它解决了什么问题?
    传统应用程序中,为了实现业务逻辑,肯定需要多个对象来协作完成任务,例如:

    • 有一个用户信息类
    • 有一个用户类,这个用户类会依赖于用户信息类
    • 然后用一个客户端类去创建用户类和用户信息类,并且将用户信息类主动注入到用户类中

    有了IoC Container之后,在客户端类中就不需要再去主动去创建对象和注入依赖了,而是通过配置文件或者注解的形式表现出来,然后IoC Container通过解析配置文件和注解去完成模块的创建和模块间的依赖注入。这样可以使代码之间的耦合度更低

  3. IoC原型(参考JinhaoPlus的博客)

  • 如果不采用IoC,传统程序依赖注入的方式
class MainModule{
  private DependModuleA moduleA;  // 存在依赖关系
  public DependModuleA getModuleA() {
    return moduleA;
  }
  public void setModuleA(DependModuleA moduleA) {
    this.moduleA = moduleA;
  }
}

class DependModuleAImpl {
  public void funcFromModuleA() {
    System.out.println("This is func from Module A");
  }
}

public class SimpleIOCDemo {
  public static void main(String[] args) throws ClassNotFoundException {
    MainModule mainModule = new MainModule();
    // 依赖注入
    mainModule.setModuleA(new DependModuleAImpl()); 
    mainModule.getModuleA().funcFromModuleA();
  }
}
  • 自己定义一个简易的IoC
定义一个properties配置文件(xxx.properties)描述依赖关系
moduleA=DependModuleAImpl
mainModule=MainModule
mainModule.moduleA=moduleA
class SimpleIoCContainer{
  private Properties properties = new Properties();
  // 存储已经加载过的bean,String为bean的名称
  private Map moduleMap = new HashMap<>();
  public SimpleIoCContainer(String configurFile) {
    try { // 加载配置文件
      properties.load(new FileInputStream(configurFile));
    } catch (Exception e) {
      e.printStackTrace();
    }    
  }
  public Object getBean(String moduleName) throws ClassNotFoundException {
    Object instanceObj;
    // 如果不为空,说明这个bean已经创建过,返回已经创建过的bean
    if(moduleMap.get(moduleName)!=null){
      System.out.println("return old bean");
      return moduleMap.get(moduleName);
    }
    System.out.println("create new bean");
    String fullClassName = properties.getProperty(moduleName);
    if(fullClassName == null)
      throw new ClassNotFoundException();
    else{
      // 通过properties的值获取类对象
      Class clazz = Class.forName(fullClassName);
      try {
        // 通过类对象创建一个实例对象
        instanceObj = clazz.newInstance();
        // 检查每个属性是否有对其他bean的依赖
        instanceObj = buildAttachedModules(moduleName,instanceObj);
        // bean创建完成,put到map中
        moduleMap.put(moduleName, instanceObj);
        return instanceObj;
      } catch (InstantiationException e) {
        e.printStackTrace();
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      }
    }
    return null;
  }
  /*
  * 实现依赖注入
  */
  private Object buildAttachedModules(String modulename , Object instanceObj) {
    Set propertiesKeys = properties.stringPropertyNames();
    Field[] fields = instanceObj.getClass().getDeclaredFields();
    for (String key : propertiesKeys) {
      // 如果存在,说明可能存在对其他的bean对象的依赖
      if(key.contains(modulename)&&!key.equals(modulename)){
        try {
          Class clazz = Class.forName(properties.getPropert(key));
          // 存在依赖其他bean的字段属性
          for (Field field : fields) {
            if(field.getType().isAssignableFrom(clazz))
              // 依赖注入
              field.set(instanceObj, moduleMap.get(properties.getPropert(key)));
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
        
      }
    }
    return instanceObj;
  }
}

public class SimpleIOCDemo {
  public static void main(String[] args) throws ClassNotFoundException {
    SimpleIOCContainer container = new SimpleIOCContainer();  
    // spring默认是预加载的(容器创建时,就开始执行bean的创建和依赖注)
    // 这里实现是在getBean方法执行时才会加载(懒加载方式)
    DependModuleAImpl moduleA = container.getBean("moduleA");
    MainModule mainModule = container.getBean("mainModule");
  }
}

Spring IoC容器的工作过程

  1. 容器的启动阶段

    • 和自己写的ioc原型类似,首先要找到并加载配置文件或者注解等元信息
    • 容器对加载的配置信息进行解析和分析
    • 将解析处理后的信息装配成BeanDefinition(bean在Spring ioc内部表示的一种数据结构,用来描述一个bean实例所具有的属性值、构造函数参数值、具体实现的信息),相到于一个生成bean的“内部图纸”
    • 将BeanDefinition注册到容器中
    • 对BeanDefinition进行一些后处理
  2. Bean实例化阶段

    • 容器通过对BeanDefinition的解析进行实例化对象
    • 如果有依赖,就装配依赖(注:最后创建的实例是经过spring包装的BeanWrapper实例,而不是简单的bean实例,为了提供统一访问bean属性的接口,其可以方便的进行属性设置,而不用通过反射的方式)
    • 可以通过定义BeanPostProcessor处理器对已经装配好的实例进行一些处理(可选)
    • 可以设置init-method和destroy-method来对bean的初始化和销毁过程设置毁掉函数(可选)
  3. Bean完整的生命周期(下面以ApplicationContext作为容器为例)
    来自于知乎
    1. Spring对Bean进行实例化,相当于new
    2. 为bean设置对象属性(依赖注入)
    3. 注入Aware接口
    • 如果bean实现了BeanNameAware接口,将Spring的id传递给setBeanName方法并执行,目的是提供接口通过Bean来访问Bean的id
    • 如果bean实现了BeanFactory接口,Spring把BeanFactory容器实例传递给setBeanFaoctory方法并执行,目的是通过bean获取Spring容器实例,使用场景-》bean通过spring容器发布事件
    • 如果Bean实现了ApplicationContextAware接口,Spring容器将调用setApplictionContext(Application ctx)方法,把应用上下文作为参数传入。
      它与BeanFactory的区别在于,前者在spring容器调用时会把自己作为参数传入,而不需要程序员去指定参数传入,而且前者提供的功能更多。
    1. 经过上面的步骤后,bean对象就被正确的构造,但是还可以通过一下方法进行自定义处理 ,实现BeanPostProcessor接口
    • postProcessBeforeInitialzation( Object bean, String beanName ) 当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。 这个函数会先于InitialzationBean执行,因此称为前置处理。
    • postProcessAfterInitialzation( Object bean, String beanName ) ,Spring会在InitialzationBean完成后执行
    1. 如果bean实现了InitializingBean接口,spring将调用afterPropertiesSet()方法。如果设置了属性init-method,在这之后,还会执行init-method属性指定的自定义方法。
    2. bean将一直驻留在应用上下文中(如果bean的作用域为prototype类型,就会把bean实例交给客户端,不驻留在容器中),直到应用上下文被销毁
    3. 如果bean实现了DispostbleBean接口,销毁前将执行destroy方法
    4. 如果这个Bean配置了destroy-method属性,会自动调用这个自定义的销毁方法
  4. BeanFactoty作为容器的几点不同

  • 不会调用ApplicationContextAware接口的setApplicationContext()方法
  • postProcessBeforeInitialzation()方法和postProcessAfterInitialization()方法不会自动调用,必须自己通过代码手动注册
  • BeanFactory容器启动的时候,不会去实例化所有Bean,包括所有scope为singleton且非懒加载的Bean也是一样,而是在调用的时候去实例化(默认为懒加载)

基于xml配置文件的方式声明bean、依赖注入(主要使用场景:主要用于第三方类库)

  1. 常见的配置方式如下:






    
    

    
      

    
    

    
    

    
    
    
    
        
    
    
        
    
    
        
    

    
    
    

    
    
        
        
            
                
                
            
        
        
        
    


  1. 如何让这个配置文件被容器加载?
// 还可以使用FileSystemXmlApplicationContext
ApplicationContext context = new ClassPathXmlApplicationContext("xxx.xml");

基于注解的方式声明bean、依赖注入(主要使用场景:Bean的实现类是当前项目开发的)

  1. @Autowired,此注解可以声明依赖注入,使用方式如下
public class A {

    private final B b;

    // 对属性字段使用
    @Autowired
    private C c;

    private D d;

    private E e;

    // 对构造函数使用
    @Autowired
    public MovieRecommender(B b) {
        this.b = b;
    }

    // 对setter方法使用
    @Autowired
    public void setD(D d) {
        this.d = d;
    }

    // 对一个方法使用
    @Autowired
    public void wireE(E e) {
        this.E = e;
    }
}
  1. @Bean,它是一个方法级别的注解,声明当前方法的返回值为一个Bean。同时它必须使用在有@Configuration或@Component注解的类中,@Configuration注解让容器知道这个类相当于一个xml配置文件,@Bean有一些属性可以定义,例如:init-method、destroy-method、name等。使用方式如下,
/*
@Configuration包含了@Component,他们的区别在于如下场景。
如果使用@Component,那么在userInfo中,country()会生成一个新的实例,
而使用@Configuration则不会出现这种情况,会使用同一个Country实例。
@Component
public class MyBeanConfig {

    @Bean
    public Country country(){
        return new Country();
    }

    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country());
    }

}
*/
@Configuration
public class AppContext {
    // 该方法必须返回一个对象
    @Bean
    public Course course() {
        return new Course();
    }
}
  1. @Component、@Repository(用于标注数据访问层组件)、@Service(用于标注业务层组件)、@Controller(用于标注控制层组件),这四个注解表明一个类声明为一个bean。

  2. @Qualifier,在依赖注入时,如果有多个候选的Bean,可以指定具体某一个Bean来作为注入的类,使用方式如下

@Autowired  
public void setOffice(@Qualifier("office")Office office)  
{  
    this.office =office;  
}  
//或则如下
@Autowired   
@Qualifier("office")   
private Office office; 
  1. @ComponentScan,自动扫描包明下所有使用@Component @Service @Repository @Controller 的类,并注册为Bean。需要将@ComponentScan添加在包含有@Configuration的类中,其中basePackages属性
    可以指定扫描的路径。
// 相当于xml文件中的
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
   ...
}
  1. 这种情况如何实例化一个容器?
// AppContext类必须是要被@Configuration声明过的
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppContext.class)

你可能感兴趣的:(Spring 初步的理解)