Spring从入门到精通—IOC详解

1、什么是Spring IOC/DI

  • 控制反转(Inversion of Control,IoC)
  • 所谓控制反转就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器负责的。这样控制权就由应用转移到了外部容器,控制权的转移就是所谓反转。
  • 依赖注入(Dependency Injection,DI)
  • 在运行期,由外部容器动态地将依赖对象注入到组件中。换句话说,就是在运行时能 Bean对象设置属性值

2、bean标签

一个bean标签代表一个spring容器中的java对象,可以在bean中经常使用的属性如下:
1. id 属性 :起名称 不能包含特殊符号 根据id 获得配置对象
2. class属性:创建对象所在全路径
3. name属性:功能和id一样 ,id不能包含特殊符号,name可以(基本不用,为了满足struts1遗留问题)
4. scope属性:Bean的作用范围,scope常用的值有:-singleton-prototype,分别表示单例多例,如果没写默认就是单例

3、Bean的3种实例化方式

  • 1.直接使用bean标签来实例化pojo,这中方法Spring默认调用的是这个pojo的无参构造器来实例化bean对象的

首先创建一个EmailDaoImpl.java

package com.xzy.dao.Impl;

import com.xzy.dao.EmailDao;
public class EmailDaoImpl implements EmailDao {

    @Override
    public void sent() {
        System.out.println("发送email>>>>>>>>>>>>");
    }
}

在ApplicationContext.xml文件中使用<bean id=“" class="" />标签配置bean:

 <!--1.直接使用bean class来实例化-->
<bean id="email" class="com.xzy.dao.Impl.EmailDaoImpl"/>

经过这两步就配置好了一个bean,测试代码简单,就是调用了一下sent方法,下面是执行的结果:

  • 2.使用静态工厂实例化pojo
    首先新建一个静态工厂DaoFactory.java
package com.xzy.dao;

import com.xzy.dao.Impl.EmailDaoImpl;
public class DaoStaticFactory {

    /**
     * 静态工厂实例化bean
     * @return
     */
    public static EmailDao createInstance(){
        return new EmailDaoImpl();
    }
}

接着在xml中配置如下:

<bean id="email1" class="com.xzy.dao.DaoStaticFactory" factory-method="createInstance"/>
  • 3.使用实例化工厂实例化pojo
    首先新建一个实例化工厂:
package com.xzy.dao;

import com.xzy.dao.Impl.EmailDaoImpl;
/**
 * 实例工厂实例化bean
 */
public class DaoInstanceFactory {

    public EmailDao createInstance(){
        return new EmailDaoImpl();
    }
}

在xml文件中配置如下:

<!--3.通过实例工厂实例化bean-->
<bean id="factory" class="com.xzy.dao.DaoInstanceFactory"/>
<bean id="email2" factory-bean="factory" factory-method="createInstance"/>

4、Spring依赖注入的2种常用方式

  1. 构造方法注入
  2. setter方法注入

首先,新建学生实体类Student:

package com.xzy.bean;

import org.springframework.stereotype.Component;
import java.util.Properties;

public class Student {
    private int age;
    private String name;
    private Teacher tea;
    private Properties info=null;


    public Student(){
        System.out.println("默认调用无参构造方法实例化bean");
    }

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public Student(String name, Teacher tea) {
        this.name = name;
        this.tea = tea;
    }

   //省略getter,setter......

1. 使用构造方法注入在xml中配置如下,正常情况下只用指定参数的名字和参数的值:
<constructor-arg name="" value="" />,name就是构造方法中的参数名,value即为这个参数的值下面是一个简单的配置示例:

    <!--1.构造方法注入-->
 <bean id="student" class="com.xzy.bean.Student">
      <constructor-arg name="age" value="20" />
      <constructor-arg name="name" value="小明" />
 </bean>

程序运行的结果:

当构造方法出现命名冲突的时候,可以使用type属性指定参数的数据类型:

   <!--当构造方法中出现命名冲突的时候,还可以使用type属性指定参数的类型-->
<bean id="student" class="com.xzy.bean.Student">
    <constructor-arg  value="20" type="int"></constructor-arg>
    <constructor-arg  value="小红" type="java.lang.String"></constructor-arg>
</bean>

程序运行的结果:

2. setter方法注入,这种方法和实例化bean相同,都是用了property属性

<bean id="student" class="com.xzy.bean.Student">
        <property name="age" value="#{25*7-20*7}"></property>
        <property name="name" value="#{'aaaaaaa'.toUpperCase()}"></property>
        <property name="tea" value="#{teacher}"></property>
        <property name="info">
            <value>
                jdbc.driver=com.mysql.jdbc.Driver
                jdbc.url=jdbc:mysql://localhost:3306/mydb
            </value>
        </property>
    </bean>

程寻运行的结果:


5、SpEL(Spring表达式)

        SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言。为什么要总结SpEL,因为它可以在运行时查询和操作数据,尤其是数组列表型数据,因此可以缩减代码量,优化代码结构。个人认为很有用。
        SpEL有三种用法,一种是在注解@Value中;一种是用于XML配置;最后一种是在代码块中使用Expression。下面说一下它最基础最重要的一种用法:xml配置法。(使用注解的方式的语法和xml方式的语法一样的,只是使用注解会更方便)

5.1 SpEL的基本语法
    语法格式``
  • #{123}、#{'字符串'} :数字、字符串
  • #{beanId}:对另一个bean的引用,类似ref属性
  • #{beanId.propName}::操作数据
  • #{beanId.toString()}:执行方法
  • #{T(类).字段|方法}:静态方法或字段
    • Spring表达式支持大多数的数学操作符、逻辑操作符、关系操作符。
      1.关系操作符
      包括:等于 (==, eq),不等于 (!=, ne),小于 (<, lt),,小于等于(<= ,
      le),大于(>, gt),大于等于 (>=, ge)
      2.逻辑操作符
      包括:and,or,and not(!)
      3.数学操作符
      包括:加 (+),减 (-),乘 (*),除 (/),取模 (%),幂指数 (^)。

      新建一个实体Customer

      package com.xzy.bean;
      
      import java.util.Arrays;
      import java.util.List;
      import java.util.Map;
      import java.util.Set;
      
      
      public class Customer {
      
          private String name;
          private String sex="男";
          private double pi;
      
      @Override
          public String toString() {
              return "Customer{" +
                      "name='" + name + '\'' +
                      ", sex='" + sex + '\'' +
                      ", pi=" + pi 
                      '}';
          }
      

      在配置文件中如下配置:

         <bean id="customer" class="com.xzy.bean.Customer">
              <!--操作字段:#{ref.Field}-->
              <property name="name" value="#{student.name}"></property>
              <!--静态字段:#{T.(TYPE).staticField}-->
              <property name="pi" value="#{T(Math).PI}"></property>
              <property name="sex" value="#{'女'}"></property>
          </bean>
      

      程序的执行结果如下:

      剩下的各种运算就不在这里试了,有兴趣的话可以自己尝试。

      6、Spring集合类型注入

          官方的一句话:In the <list/>, <set/>, <map/>, and <props/> elements, you set the properties and arguments of the Java Collection types List, Set, Map, and Properties, respectively.就是说,你可以用<list>setmapprops来配置对应的Java集合类型:List、Set、Map、以及Array(数组),以及Properties也可以配置。
      举个栗子:
      在上例Customer实体类的基础上修改,分别增加List属性、Set属性、Map属性和数组属性一个,具体代码如下:

      package com.xzy.bean;
      
      import java.util.Arrays;
      import java.util.List;
      import java.util.Map;
      import java.util.Set;
      public class Customer {
      
          private String name;
          private String sex="男";
          private List<String> shopCar;    //购物车
          private Set<String> price;       //价格
          private Map<String,Double> goods;   //物品
          private String[] address;        //地址
      
           //省略getter、setter....
      
          @Override
          public String toString() {
              return "Customer{" +
                      "name='" + name + '\'' +
                      ", sex='" + sex + '\'' +
                      ", shopCar=" + shopCar +
                      ", price=" + price +
                      ", goods=" + goods +
                      ", address=" + Arrays.toString(address) +
                      '}';
          }
      }
      
      • 使用<list>标签给List类型注入初始值:
      <!--bean的集合注入-->
          <bean id="customer" class="com.xzy.bean.Customer">
              <property name="name" value="#{student.name}"></property>
              <property name="sex" value="#{'女'}"></property>
              <!--List注入使用<list>元素 -->
              <property name="shopCar">
                  <list>
                      <value>书</value>
                      <value>手机</value>
                      <value>衣服</value>
                      <value>电脑</value>
                  </list>
              </property>
         </bean>
      
      • 使用<set>标签给Set类型注入初始值:
      <!--bean的集合注入-->
          <bean id="customer" class="com.xzy.bean.Customer">
              <property name="name" value="#{student.name}"></property>
              <property name="sex" value="#{'女'}"></property>
      
              <!--Set注入使用<set>元素 -->
              <property name="price">
                  <set>
                      <value>"#{3.5*6}"</value>
                      <value>"#{2000*2}"</value>
                      <value>"#{100*6}"</value>
                      <value>"#{5000*1}"</value>
                  </set>
              </property>
         </bean>
      
      • 使用<map>标签给Map类型注入初始值:
      <!--bean的集合注入-->
          <bean id="customer" class="com.xzy.bean.Customer">
              <property name="name" value="#{student.name}"></property>
              <property name="sex" value="#{'女'}"></property>
              <!--Map输注入
                使用<map>元素,特别注意,使用<entry>来指定一条数据的key和value
              -->
              <property name="goods">
                  <map>
                      <entry key="p1" value="23"></entry>
                      <entry key="p2" value="24"></entry>
                      <entry key="p3" value="25"></entry>
                      <entry key="p4" value="26"></entry>
                  </map>
              </property>
         </bean>
      
      • 使用<list>标签给List类型注入初始值:
      <!--bean的集合注入-->
          <bean id="customer" class="com.xzy.bean.Customer">
              <property name="name" value="#{student.name}"></property>
              <property name="sex" value="#{'女'}"></property>
              <!--List注入使用<list>元素 -->
              <property name="shopCar">
                  <list>
                      <value>书</value>
                      <value>手机</value>
                      <value>衣服</value>
                      <value>电脑</value>
                  </list>
              </property>
         </bean>
      
      • 使用<list>标签给List类型注入初始值:
      <!--bean的集合注入-->
          <bean id="customer" class="com.xzy.bean.Customer">
              <property name="name" value="#{student.name}"></property>
              <property name="sex" value="#{'女'}"></property>
             <!--数组注入使用<array>元素 -->
              <property name="address">
                  <array>
                      <value>西安</value>
                      <value>北京</value>
                      <value>南京</value>
                      <value>广州</value>
                  </array>
              </property>
         </bean>
      

      最后,对于customer的DI配置如下:

       <!--bean的集合注入-->
          <bean id="customer" class="com.xzy.bean.Customer">
              <property name="name" value="#{student.name}"></property>
              <property name="sex" value="#{'女'}"></property>
              <!--List注入
                  使用<list>元素
              -->
              <property name="shopCar">
                  <list>
                      <value>书</value>
                      <value>手机</value>
                      <value>衣服</value>
                      <value>电脑</value>
                  </list>
              </property>
              <!--Set注入
                 使用<set>元素
              -->
              <property name="price">
                  <set>
                      <value>"#{3.5*6}"</value>
                      <value>"#{2000*2}"</value>
                      <value>"#{100*6}"</value>
                      <value>"#{5000*1}"</value>
                  </set>
              </property>
              <!--Map输注入
                使用<map>元素,特别注意,使用<entry>来指定一条数据的key和value
              -->
              <property name="goods">
                  <map>
                      <entry key="p1" value="23"></entry>
                      <entry key="p2" value="24"></entry>
                      <entry key="p3" value="25"></entry>
                      <entry key="p4" value="26"></entry>
                  </map>
              </property>
              <!--数组注入
                  使用<array>元素
              -->
              <property name="address">
                  <array>
                      <value>西安</value>
                      <value>北京</value>
                      <value>南京</value>
                      <value>广州</value>
                  </array>
              </property>
          </bean>
      

      程序执行结果:

      7、使用Annotation自动装配扫描

      要使用Anntation配置spring容器,首先需要在ApplicationContext.xml文件中配置如下信息:

      <!--开启注解-->
      <context:annotation-config/>
      <!--告诉Spring去扫描哪里-->
      <context:component-scan base-package="com.xzy"></context:component-scan>
      

      设置组件与bean命名

    • 1.@Repository, @Service, and @Controller,@Component 这 四 个 Annotation 功 能 相 同都是声明一个bean组件,不同的是 @Repository 声明 Dao层 @Service 声明 Service层 @controller 声明 控制器层, @Component 就是一个普通的组件,例如对pojo实体可以使用他 都是用在类上的 Annotation,说明让Spring 实例化此类的对像,并放入 spring 容器中
    • 2.@componet(“id”)其中 id 声明 bean 对像的名字
    • 举个栗子:
      新建Student.java

      package com.xzy.bean;
      
      import org.springframework.stereotype.Component;
      
      @Component    //这一句就是告诉Spring这是个普通的组件
      public class Student {
      
          private int age;
          private String name;
      
      
          public Student() {
              System.out.println("1.实例化了bean。。。。。");
          }
      
          //省略getter、setter
          
          @Override
          public String toString() {
              return "Student{" +
                      "age=" + age +
                      ", name='" + name + '\'' +
                      '}';
          }
      }
      

      测试代码:

      package com.xzy;
      
      import com.xzy.bean.Student;
      import org.apache.log4j.Logger;
      import org.junit.Test;
      import org.junit.runner.RunWith;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.test.context.ContextConfiguration;
      import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
      
      
      /**
       * Unit test for simple App.
       */
      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration(value={"/ApplicationContext.xml"})
      public class AppTest {
      
          private static Logger log=Logger.getLogger(AppTest.class);
      
          @Autowired  //自动注入,Spring会为我们自动从容器中找到student的对象然后注入这里的变量
          private Student student;
      
          @Test
          public void test01(){
              System.out.println(student);
          }
      }
      

      程序运行结果:

      @Repository, @Service, and @Controller
      新建StudentDao以及StudentDaoImpl

      StudentDao
      package com.xzy.Dao;
      
      import com.xzy.bean.Student;
      public interface StudentDao {
      
          /**
           * 增加一个学生
           * @param stu
           * @return
           */
          public void addStudent(Student stu);
      
      }
      

      写一个StudentDaoImpi,模拟DAO层,并使用@Respositiry告诉Spring这是DAO层的组件

      StudentDaoImpl
      package com.xzy.Dao.DaoImpi;
      
      import com.xzy.Dao.StudentDao;
      import com.xzy.bean.Student;
      import org.springframework.stereotype.Repository;
      
      @Repository
      public class StudentDaoImpi implements StudentDao {
      
          public StudentDaoImpl() {
              System.out.println("Repository层实例化");
      
          }
      
          @Override
          public void addStudent(Student stu) {
              System.out.print("3.Dao层处理数据:");
      
              System.out.println("向数据库发一条insert语句添加一个学生:"+stu.getName());
      
          }
      }
      

      新建StudentService.java,模拟Service层,使用@Service注解告诉Spring这是一个Service的组件

      StudentService.java
      package com.xzy.Service;
      
      import com.xzy.Dao.StudentDao;
      import com.xzy.bean.Student;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      
      @Service
      public class StudentService {
      
          public StudentService() {
              System.out.println("Service层实例化");
          }
      
          @Autowired
          private StudentDao stuImp;
      
          public void add(Student stu){
              System.out.println("2.service层收到控制层的数据后发给Dao层");
               stuImp.addStudent(stu);
          }
      }
      

      新建StudentServlet.java,模拟控制器层,并使用@Controller注解告诉Spring这是控制器。

      StudentServlet.java
      package com.xzy.Servlet;
      
      import com.xzy.Service.StudentService;
      import com.xzy.bean.Student;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Controller;
      
      @Controller
      public class StudentServlet {
      
          public StudentServlet() {
              System.out.println("Controller层实例化");
          }
      
          @Autowired
          private StudentService stuService;
          @Autowired
          private Student stu;
      
          public void addAction() {
              System.out.println("1.控制层发数据给sevice层");
              stu.setAge(23);
              stu.setName("狗子");
              stuService.add(stu);
          }
      }
      

      最终的执行结果:

      可以看到这三个注解是由其存在的意义的,@Repository是告诉spring这是一个DAO层的类应该最先实例化(确实也应该如此), @Service层接着DAO层实例化完成后实例化, and @Controller层最后实例化,只用这样才可以确保不会空指针。

      设置组件扫描的base-packages

      @Configuration
      @ComponentScan(“基包名”)
      Public class AppConfig{}

      @Configuration
      @ComponentScan(basepackages=“基包名”)
      Public class AppConfig{}

      @Configuration
      @ComponentScan(basepackages={“基包名”,”…”})
      Public class AppConfig{}

      @Configuration
      @ComponentScan(basePackageClasses={App1Config.class,App2Config.class})
      Public class AppConfig{}
      以上 App1Config 与 App2Config 所在的包作为组件扫描的基础包

      8.3 Annotation 自动装配

      1.@Autowired 自动装配和 JSR 330’s @Inject 对应,可用在构造方法、属性 setter 方法,有属性@Autowired(required=false)
      @Primary 用于声明 bean 的首先,用在多个 bean,无法选择装配谁的情况可以指明使用哪个

      2.@Required 声明依赖必须提供 用在 setter 方法
      @Required
      public void setMovieFinder(MovieFinder movieFinder) {
      this.movieFinder = movieFinder;
      }

      3.@Qualifiers 注明要装配 bean 的标识,用于多个 bean 无法确定装配哪个的情况

      8.4 处理自动装配的歧义
      Spring提供的自动装配是非常好用,可是用这么个问题:比如,一个接口有三个实现类,当要将接口类型自动装配置时,就出现不唯一的问题,Spring 会抛出 NoUniqueBeanDefinitionException。正如下面这种情况:
      写一个接口:

      package com.xzy.utils;
      
      import org.springframework.stereotype.Component;
      @Component
      public interface ReadData {
      
          public void read();
      
      }
      
      

      接口的三个实现类:

      package com.xzy.utils;
      
      import org.springframework.stereotype.Component;
      
      @Component
      public class USBRead implements ReadData {
          @Override
          public void read() {
             System.out.println("USB读取数据.....");
          }
      }
      
      
      package com.xzy.utils;
      
      import org.springframework.stereotype.Component;
      
      @Component
      public class SSDRead implements ReadData {
          @Override
          public void read() {
              System.out.println("SSD读取数据......");
          }
      }
      
      
      package com.xzy.utils;
      
      import org.springframework.stereotype.Component;
      
      @Component
      public class BlueRead implements ReadData {
          @Override
          public void read() {
              System.out.println("蓝牙读取数据.......");
          }
      }
      

      这时如果让Spring给我们自动装配,他都懵逼了,因为这个接口有3个实现类,都可以装配,他不知道装配那个,如下图所示:

      此时如果直接运行就会发生如下异常:

      2019-08-01 21:49:51 [ERROR]-[org.springframework.test.context.TestContextManager] Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@685cb137] to prepare test instance [com.xzy.AppTest@50a638b5]
        org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.xzy.AppTest': Unsatisfied dependency expressed through field 'read'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.xzy.utils.ReadData' available: expected single matching bean but found 3: blueRead,SSDRead,USBRead
      

      解决办法
      解决方法1:在实现类的头上使用@Primary注解告诉Spring首选哪个装配,比如在USBRead类的头上加上@Primary:

      解决方法2:使用@Qualifier 注解限定自动装配的 Bean

留言区

还能输入500个字符