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 ```java 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 '}'; } ``` 在配置文件中如下配置: ```xml ``` 程序的执行结果如下:
        剩下的各种运算就不在这里试了,有兴趣的话可以自己尝试。 #### 6、Spring集合类型注入     官方的一句话:`In the , , , and elements, you set the properties and arguments of the Java Collection types List, Set, Map, and Properties, respectively.`就是说,你可以用``、`set`、`map`、`props`来配置对应的Java集合类型:List、Set、Map、以及Array(数组),以及Properties也可以配置。 举个栗子: 在上例Customer实体类的基础上修改,分别增加List属性、Set属性、Map属性和数组属性一个,具体代码如下: ```java 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 shopCar; //购物车 private Set price; //价格 private Map 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类型注入初始值: ```xml 手机 衣服 电脑 ``` - 使用``标签给Set类型注入初始值: ```xml "#{3.5*6}" "#{2000*2}" "#{100*6}" "#{5000*1}" ``` - 使用``标签给Map类型注入初始值: ```xml ``` - 使用``标签给List类型注入初始值: ```xml 手机 衣服 电脑 ``` - 使用``标签给List类型注入初始值: ```xml 西安 北京 南京 广州 ``` 最后,对于customer的DI配置如下: ```xml 手机 衣服 电脑 "#{3.5*6}" "#{2000*2}" "#{100*6}" "#{5000*1}" 西安 北京 南京 广州 ``` 程序执行结果:
        #### 7、使用Annotation自动装配扫描 要使用Anntation配置spring容器,首先需要在ApplicationContext.xml文件中配置如下信息: ```xml ``` 设置组件与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 ```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 + '\'' + '}'; } } ``` 测试代码: ```java 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 ```java 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层的组件 ```java 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的组件 ```java 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这是控制器。 ```java 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。正如下面这种情况: 写一个接口: ```java package com.xzy.utils; import org.springframework.stereotype.Component; @Component public interface ReadData { public void read(); } ``` 接口的三个实现类: ```java package com.xzy.utils; import org.springframework.stereotype.Component; @Component public class USBRead implements ReadData { @Override public void read() { System.out.println("USB读取数据....."); } } ``` ```java package com.xzy.utils; import org.springframework.stereotype.Component; @Component public class SSDRead implements ReadData { @Override public void read() { System.out.println("SSD读取数据......"); } } ``` ```java package com.xzy.utils; import org.springframework.stereotype.Component; @Component public class BlueRead implements ReadData { @Override public void read() { System.out.println("蓝牙读取数据......."); } } ``` 这时如果让Spring给我们自动装配,他都懵逼了,因为这个接口有3个实现类,都可以装配,他不知道装配那个,如下图所示:
        此时如果直接运行就会发生如下异常: ```accesslog 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个字符