美文网首页
Spring 初步的理解

Spring 初步的理解

作者: 心_的方向 | 来源:发表于2018-03-27 15:15 被阅读37次

    理解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<String, Object> 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<? extends Object> 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<String> propertiesKeys = properties.stringPropertyNames();
        Field[] fields = instanceObj.getClass().getDeclaredFields();
        for (String key : propertiesKeys) {
          // 如果存在,说明可能存在对其他的bean对象的依赖
          if(key.contains(modulename)&&!key.equals(modulename)){
            try {
              Class<? extends Object> 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. 常见的配置方式如下:
    <?xml version="1.0" encoding="UTF-8"?>
    <!-- xmlns表示默认的命名空间,不使用前缀的标签,都代表是这个命名空间下的标签 -->
    <!-- xmlns:xsi表示使用xsi作为前缀的命名空间 -->
    <!-- xsi:schemaLocation定义命名空间和对应的XSD文档(可以对文档格式合法性进行验证)的位置关系 -->
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 可以导入另外一个xml配置文件,进行组合 -->
        <import resource="another.xml"/>
    
        <!-- 在使用注解和xml配置文件混合配置的时候使用 -->
        <context:annotation-config />  
    
        <!-- 最常见声明bean的方式,id为bean的名称,class为bean的classpath -->
        <bean id="a" class="com.wulei.A">
    
        <!-- 对名为a的bean声明一个别名 -->
        <alias name="a" alias="aAlias" />
    
        <!-- 这里在声明一个bean的同时,还指定了这个bean的bPropertie属性值依赖于名为a的bean -->
        <!-- spring默认是预加载的,可以设置lazy-init属性为懒加载 -->
        <!-- init-method和destroy-mehod用于指定自定义的初始化和销毁方法 -->
        <bean id="b" class="com.wulei.B" lazy-init="true" init-method="" destroy-method="">
            <property name="bPropertie" ref="a" />
        </bean>
        <bean id="b1" class="com.wulei.B">
            <property name="bPropertie" ref="a" />
        </bean>
        <bean id="b2" class="com.wulei.B">
            <property name="bPropertie" ref="a" />
        </bean>
    
        <!-- 如果设置bean的作用域为prototype,那么就不再默认是单例模式-->
        <bean id="b3" class="com.wulei.B" scope="prototype">
        </bean>
    
        <!--
        public class C {
            public Foo(A a, List<B> bList, int i,String name s) {
                // ...
            }
        }
        对构造函数参数的依赖注入声明方式
        -->
        <bean id="c" class="com.wulei.C">
            <constructor-arg ref="a" />
            <constructor-arg>
                <list>
                    <ref bean="b1" />
                    <ref bean="b2" />
                </list>
            </constructor-arg>
            <constructor-arg type="int" value="1" />
            <constructor-arg name="s" value="oneString" />
        </bean>
    
    </beans>
    
    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文件中的<context:component-scan base-package="org.example"/>
    @Configuration
    @ComponentScan(basePackages = "org.example")
    public class AppConfig  {
       ...
    }
    
    1. 这种情况如何实例化一个容器?
    // AppContext类必须是要被@Configuration声明过的
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppContext.class)
    

    相关文章

      网友评论

          本文标题:Spring 初步的理解

          本文链接:https://www.haomeiwen.com/subject/xcdrcftx.html