2、spring注入及自动装配

     

1. 课程回顾 1-1

1.1. Spring 概述 1-1

1.2. Spring IOC 概述 1-2

1.3. Spring IOC 编程 1-2

2. Spring Bean依赖 2-2

2.1.1. 依赖注入基础 2-2

2.1.2. 依赖注入进阶 2-2

2.1.3. 依赖值的自动装配 2-5

3. Spring 注解应用 3-6

3.1. Spring 注解概述 3-6

3.2. Spring 注解基本应用 3-6

3.2.1. 常用注解说明 3-6

3.2.2. 注解应用案例 3-7

3.2.3. 配置注解扫描 3-7

3.2.4. 编写测试类获取bean 3-8

3.3. Spring 注解应用增强 3-8

3.3.1. 作用域及生命周期 3-8

3.3.2. 延迟加载配置 3-8

3.3.3. 自动装配配置 3-9

4. 总结 4-9

4.1. 重点和难点分析 4-9

4.2. 常见FAQ 4-9

 

 

  1. 课程回顾

 

    1.1. Spring 概述

      1. Spring 是什么?(框架,半成品)
      2. Spring 能解决什么问题(面向对象,面向切面,面向服务)
      3. Spring 框架核心?(IOC,AOP,MVC,…)

           个人认为:Spring 最强大是它的资源整合能力。

 

    1.2. Spring IOC 概述

        1. IOC是什么?(控制反转:由spring构建对象,管理对象依赖)
        2. IOC 应用优势?(解耦,更好的管理对象,使用系统资源)
        3. IOC 的核心?(工厂,配置,依赖注入)
        4. IOC 编程步骤?(类,配置,获取)

 

    1.3. Spring IOC 编程

      1. Spring Bean对象初始化,作用域,声明周期,延迟加载

            1) Bean类型的编写(修饰符,构造方法)

            2) Bean 的配置(applicationContext.xml)

            3) Bean 的作用域(singleton,prototype)

            4) Bean 的生命周期(生命周期方法的使用)

            5) Bean 的延迟加载(局部lazy-init,全局 default-lazy-init)

 

      1. Spring Bean依赖(依赖注入,自动装配)

            1) 依赖注入(DI)的定义(通过spring为类中的属性注入值)

            2) 依赖注入的实现(set注入,构造注入)

            3) 依赖注入中的集合值的注入(数组,list,set,map)

            4) 依赖注入中的自动装配(未讲)

 

    2. Spring Bean依赖

        2.0.1. 依赖注入基础

          重点了解set,构造注入的方式以及单个值如何实现注入。

        2.0.2. 依赖注入进阶

          重点了解集合类型值的注入,例如listhashmap,properties

            案例1

            定义一个相对复杂的对象

              public class ComplexObject {

                private String[] names;

                private List<String> address;

                private Map<String,Integer> map;

                private Properties properties;

                //set,get

              }

            在配置文件中配置此对象,并为属性注入值

 

<?xml version="1.0" encoding="UTF-8"?>

<beans 

    default-lazy-init="true"

    xmlns="http://www.springframework.org/schema/beans" 

    xmlns:p="http://www.springframework.org/schema/p"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:tx="http://www.springframework.org/schema/tx"

    xmlns:aop="http://www.springframework.org/schema/aop"

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xmlns:util="http://www.springframework.org/schema/util"

    xmlns:jpa="http://www.springframework.org/schema/data/jpa"

    xsi:schemaLocation="  

       http://www.springframework.org/schema/beans   

       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd  

       http://www.springframework.org/schema/mvc   

       http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd   

       http://www.springframework.org/schema/tx   

       http://www.springframework.org/schema/tx/spring-tx-4.3.xsd   

       http://www.springframework.org/schema/aop

       http://www.springframework.org/schema/aop/spring-aop-4.3.xsd

       http://www.springframework.org/schema/util

       http://www.springframework.org/schema/util/spring-util-4.3.xsd

       http://www.springframework.org/schema/data/jpa

       http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context-4.3.xsd"> 

          

      <bean id="cObj" class="beans.ComplexObject">

         <!-- 为String[]数组属性注入值 -->

         <property name="names">

             <list>

                <!-- #{}为spring中的一个表达式 -->

                <value>name-1</value>

                <value>name-2</value>

             </list>

         </property>

         <!-- 为list<String>集合注入值 -->

         <property name="address">

             <list>

               <value>北京</value>

               <value>深圳</value>

               <value>上海</value>

             </list>

         </property>

         <!-- 为map属性注入值 -->

         <property name="map">

            <map>

               <entry key="k1" value="200"/>

               <entry key="k2" value="300"/>

            </map>

         </property>

         <!-- 为properties属性注入值 -->

         <property name="properties">

            <props>

              <prop key="pk1">pv1</prop>

              <prop key="pk2">pv2</prop>

            </props>

         </property>      

      </bean>

</beans>

 

 

 

      案例2spring配置文件中引入properties文件中的数据

 

        step1:类路径的根目录定义properties文件cfg.properties,其内容如下:

            driver=com.mysql.jdbc.Driver

            url=jdbc:mysql:///test

            username=root

            password=root

 

      step2:spring配置文件中引入此配置文件(借助util标签) 

        <util:properties   id="cfg"   location="classpath:cfg.properties"/>

 

      step3:在Bean对象中引入配置文件中的值 

               <bean id="dataSource" class="beans.DataSource">

                      <property name="driver" value="#{cfg.driver}"/>

                      <property name="url"  value="#{cfg.url}"/>

                      <property name="username" value="#{cfg.username}"/>

                      <property name="password" value="#{cfg.password}"/>

               </bean>

 

          其中#{}spring中一个表达式,通过这个表达式可以获取properties文件中的值

          例如#{cfg.driver}为获取idcfg对应的对象中keydriver的值。

 

    2.0.3. 依赖值的自动装配 

      Spring中为bean对象中的属性提供了自动装配功能,此功能的开启需要借助bean标签中的autowire属性进行指定,此属性的值默认有如下几个:

 

 

NO 

自动配置(默认)

 

ByName

按名字自动装配(重点掌握)

 

ByType

按类型自动状态(重点掌握),但有多个类型时会出错

 

Constructor

byType类似,不同之处在于它应用于构造器参数。

 

       例如:

        定义并配置DataSource对象

          public class DataSource {

          }

         <bean id="dataSource" class="beans.DataSource"/>

          定义并配置JdbcTemplate对象

          public class JdbcTemplate {

            private DataSource dataSource;

            public JdbcTemplate() {}

            public JdbcTemplate(DataSource dataSource) {

               this.dataSource=dataSource;

              }

          public void setDataSource(DataSource dataSource) {

            this.dataSource = dataSource;

            }

 

   <bean id="jdbcTemplate" class="beans.JdbcTemplate" autowire="constructor"> </bean>

      自动装配应用总结

 

        v byName 按属性对应的set方法名从容器中查找名字相同的bean,然后进行注入。假如出现了名字相同,但类型不同的Bean对象时,会注入失败

        v byType 先从容器中查找属性类型相匹配的值然后找按类中对应的set方法(看参数类型)最后通过set方法为对象的属性注入值。假如容器中出现多个类型相同的对象,就会注入失败。

        v constructor 先从容器中查找属性类型相匹配的值然后找类中对应的构造方法(看参数类型)最后通过构造方法为对象的属性注入值。假如容器中出现多个类型相同的对象,此时再比对参数名字,假如有同名的则直接注入,没有同名的就会注入失败。

    3. Spring 注解应用

      3.1. Spring 注解概述

          Spring中提供了两种方式对Bean进行描述,一种是基于xml方式,一种是基于注解方式。基于注解方式主要是依托于注解对bean以及bean中的属性进行描述

          然后spring底层通过反射获取bean上定义的这些注解,通过注解描述初始化对象,管理对象的作用域以及对象与对象之间的依赖。

 

      3.2. Spring 注解基本应用 

        3.2.1. 常用注解说明

          组件应用注解

 

注解名

说明

 

@Component

通用注解

 

@Repository

持久层组件应用注解

 

@Service

业务层组件应用注解

 

@Controller

控制层组件应用注解

 

    3.2.2. 注解应用案例

       数据层对象

        @Repository 

      业务层对象

        @Service 

      控制层对象

        @Controller

 

   3.2.3. 配置注解扫描

       Spring中通过指定一个包路径,由系统自动扫描该包及其子包所有组件类,当发现组件类定义前有特定的注解标记时,就将该组件纳入到Spring容器。

 

      例如:用组件扫描,首先需要在XML配置中指定扫描父级package路径,例如

         <context:component-scan base-package=”com.company.spring/>”

       在这个配置中,容器会自动扫描org.example包及其子包下所有组件,并实例化bean对象。

 

      3.2.4. 编写测试类获取bean

 

    3.3. Spring 注解应用增强

 

      3.3.1. 作用域及生命周期

@Scope("singleton")
@Repository
public class SysUserDao {
      /**@PostConstruct注解修饰方法在对象初始化时执行*/
      @PostConstruct
      public void init(){
        System.out.println("init");  
      }
      public void insertObject(){
          System.out.println("insertObject");
      }
      /**@PreDestroy对象销毁时执行*/
      @PreDestroy
      public void destory(){
          System.out.println("destory");
      }
}

 

 

3.3.2. 延迟加载配置

@Lazy(false)
@Service
public class SysUserService {
}

 

    3.3.3. 自动装配配置

      注解方式的自动装配一般会借助@Autowired@Resource实现,具体过程参考   

      注解自动装配使用说明:

        应用位置

        1@Autowired/@Qualifier 一般用于修饰属性和构造方法

        2@Resource 一般用于修饰属性和set方法

 

      注入规则:

        1@Autowired 修饰属性,构造方法,set方法时默认按照属性类型或参数类型进行值的注入。假如容器中有多个类型相同的Bean,此时还会按名字进行匹配,没有相匹配的则注入失败。假如希望按名字进行匹配需要再此基础加上@Qualifier注解。

 

        2@Resource 修饰属性,set方法时默认按属性名或set方法名进行匹配,假如Resource指定了名称则按指定名称进行匹配,假如没有找到相匹配的名称,则按类型进行值的注入。

 

  1. Spring 注解工厂应用剖析

     1.1. Bean工厂是用于做什么的?

       所有的工厂都是用于创建对象的,对象创建一般会基于某中策略对对象进行存储。

 

      例如Spring 中的Bean工厂:ApplicationContext

        1) ClassPathXmlApplicationContext

        2) AnnotationApplicationContext

        3) …….

 

  1.2. Bean工厂的实现的基本原理?

 

      Spring 中所有的Bean假如需要由Spring管理,此时需要以xml描述的方式或注解的描述方式告诉spring容器。Spring底层内置了对xml的解析,通过反射获取注解描述,然后基于这些描述通过反射构建对象。

 

  1.3. Bean工厂理解的拔高?

      手写基于注解方式的Spring Bean工厂

        1) 包的扫描(将包转换为类所在的路径,然后进行文件的查找)

        2) 构建类全名(包名+类名,例如java.util.Date

        3) 基于反射构建类的对象(获取Class对象,构建构造方法对象,构建类的实例对象)

        4) 存储类的对象(key是谁)

        5) 需要时从工厂中获取bean对象即可

 

  1.4. Bean 工厂手写应用实践

       1.4.1. 注解定义实现

        定义一个用于修饰bean,描述bean的注解。

          @Retention(RetentionPolicy.RUNTIME)

          @Target(ElementType.TYPE)

          public @interface Component {     

            String value() default "";

          }

 

      定义一个用于描述配置的注解

         @Retention(RetentionPolicy.RUNTIME)

        @Target(ElementType.TYPE)

        public @interface ComponentScan {

            String value() default "";

        }

 

 

    1.4.2. 定义Bean组件

      定义一个IdGenerator类,然后使用@Component注解修饰

       @Component

      public class IdGenerator {}

 

    1.4.3. 定义Bean工厂

      这个Bean工厂要实现的功能是基于配置类上的@ComponentScan注解,对注解中定义包进行类的扫描,然后构建类的对象,再将对象存储到map集合,需要时从map获取。其代码如下:

public class AnnotationAppContext{
    private Map<String,Object> beanMap=
    new HashMap<String,Object>();
    public AnnotationAppContext(Class<?> c)throws Exception{
        //1.获取class(例如AppConfig)上的@ComponentScan注解
        ComponentScan cs=c.getAnnotation(ComponentScan.class);
        //2.获取注解中定义的包名
        String pkg=cs.value();
        //替换包名中的“.”,改成目录结构
        String dir=pkg.replaceAll("\\.", "/");
        //3.基于包获取包对应的类路径
        URL url=
        getClass().getClassLoader()
        .getResource(dir);
        System.out.println("url="+url);
        //4.获取类路径下所有的class文件(例如IdGenerator)
        File pkgDir=new File(url.getPath());
        //4.1获取目录下所有class文件
        File[] fs=pkgDir.listFiles();//class
        //4.2遍历所有文件,构建类全名
        for(File f:fs){
            String fname=
            f.getName().substring(0,f.getName().lastIndexOf("."));
            String clsName=pkg+"."+fname;
            Class<?> cls=Class.forName(clsName);
            //5.基于class文件上注解(@Component)描述构建类对象
            if(!cls.isAnnotationPresent(Component.class))
            continue;
            Object obj=newInstance(cls);
            //6.将类对象存储到map集合
            //6.1获取key的值
            Component cp=cls.getAnnotation(Component.class);
            String key=cp.value();
            if("".equals(key)){
                key=String.valueOf(
                fname.charAt(0)).toLowerCase()+
                fname.substring(1);
            }
            //6.2存储到map
            beanMap.put(key, obj);
        }
    }
    private Object newInstance(Class<?> cls)
    throws Exception{
        Constructor<?> c=
        cls.getDeclaredConstructor();
        c.setAccessible(true);
        return c.newInstance();
    }
    public Object getBean(String beanName){
        return beanMap.get(beanName);
    }
    @SuppressWarnings("unchecked")
    public <T>T getBean(String beanName,
            Class<T> cls){
        return (T) beanMap.get(beanName);
    }
    public static void main(String[] args)
    throws Exception{
        AnnotationAppContext ctx=
        new AnnotationAppContext(AppConfig.class);
        
        IdGenerator obj1=(IdGenerator)
        ctx.getBean("idGenerator");
        IdGenerator obj2=
        ctx.getBean("idGenerator",IdGenerator.class);
        
        System.out.println(obj1);
        System.out.println(obj2==obj1);
    }
}

 

2. 总结

2.1. 重点和难点分析

  1. Spring 注解的强化理解
  2. Spring 注解方式实现自动装配
  3. Spring Bean工厂手写方式的实现。

 

2.2. 常见FAQ

  1. URL是什么
  2. URL的值什么情况下为空
  3. 注解是什么,如何自己定义
  4. 工厂应用中泛型方法如何实现

 

2.3. 作业

  1. 总结spring注解应用
  2. 尝试手写spring bean 工厂

 

posted @ 2018-03-26 21:06  项羽齐  阅读(249)  评论(0编辑  收藏  举报