zoukankan      html  css  js  c++  java
  • Spring之IOC(容器,控制反转)

    1、IOC(容器)

    什么是容器?容器是一种为某种特定组件的运行提供必要支持的一个软件环境。例如,Tomcat就是一个Servlet容器,它可以为Servlet的运行提供运行环境。通常来说,使用容器运行组件,除了提供一个组件运行环境之外,容器还提供了许多底层服务。例如,Servlet容器底层实现了TCP连接,解析HTTP协议等非常复杂的服务,如果没有容器来提供这些服务,我们就无法编写像Servlet这样代码简单,功能强大的组件。

    Spring的核心就是提供了一个IoC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。

    1.1、IOC的基本介绍

    Spring提供的容器又称为IoC容器,IoC全称Inversion of Control,直译为控制反转。IOC 是面向对象编程中的一种设计原则,可以用来减低代码之间的耦合度。在 spring 中,控制反转把对象创建和对象之间的调用过程,交给 spring 进行管理,减低了代码的耦合度。

    谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

    为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

    在IoC模式下,控制权发生了反转,即从应用程序转移到了IoC容器,所有组件不再由应用程序自己创建和配置,而是由IoC容器负责,这样,应用程序只需要直接使用已经创建好并且配置好的组件。

    用图例说明一下,传统程序设计如图2-1,都是主动去创建相关对象然后再组合起来:

    当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图2-2所示:

    IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

    参考:https://www.cnblogs.com/NancyStartOnce/p/6813162.html

    1.2、IOC的基本使用

    下载完 sprig 解压文件后可以看到很多jar 包,将 spring 所必需的四个包和 commons-logging 包导入项目当中:

    spring 框架依赖的jar包有 commons-logging,如果不添加的话会报错。

    先编写一个简单的 User 类,然后在 src 目录下新建一个spring的配置文件即 xml 文件,该配置文件可自定义,比如 bean01.xml。

    User 类:

    package test;
    
    public class User {
        public void add() {
            System.out.println("add。。。");
        }
    }

    spring 配置文件 bean01.xml 如下,class 里面写的是完整类名。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!-- 配置创建的对象-->
        <bean id="user" class="test.User"></bean>
    </beans>

    然后就可以随便建一个测试类来进行测试:

    package test.testDemo;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import test.User;
    
    public class Test01 {
    
        public static void main(String[] args) {
            //加载spring配置文件,并创建了配置文件中配置的类的实例对象)
            ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");
    
            //获取bean,即配置创建的对象
            User user = (User) context.getBean("user");   //getBean()方法里面的参数是 xml 配置文件中的bean节点的id
    //调用 user.add(); } }

    执行上面代码可以看到输出 User 类中的 add() 方法。

    默认情况下,通过 bean 获取到的实例对象是单例的,即多次通过 getBean() 方法获取同一个 bean 时(在不同配置文件中,即使 bean 标签的 class 指向的是同一个类,也不是同一个bean,此时创建的不是同一个实例对象),获取到的实例对象实际上都是同一个对象。

    1.3、IOC底层原理

    原理:通过解析 xml 配置文件获取类名,然后通过反射来创建该类的一个实例对象并返回。

    如果后面类发生了改变,比如类名改了,只需修改配置文件即可,所以大大降低了耦合度。(因为如果是传统程序,类名发生修改,在所有引用到该类的地方都需要进行修改)

    2、bean介绍

    在 Spring 的 IoC 容器中,我们把所有组件统称为JavaBean,即配置一个组件就是配置一个Bean。也就是一个 bean 标签指的就是一个 bean。

    2.1、spring对于bean的操作

    bean 管理指的是两个操作:

    1. spring 创建对象
    2. spring 注入属性

    IoC又称为依赖注入(DI:Dependency Injection),依赖注入就是注入属性,注入属性是在创建对象的基础之上完成的,先创建对象,实际上是然后再注入属性。

    bean 实现上述两个操作有两种方式:

    1. 基于xml配置文件方式实现
    2. 基于注解方式实现

    Spring 的 IoC 容器同时支持属性注入和构造方法注入,并允许混合使用。

    在设计上,Spring的IoC容器是一个高度可扩展的无侵入容器。所谓无侵入,是指应用程序的组件无需实现Spring的特定接口,或者说,组件根本不知道自己在Spring的容器中运行。这种无侵入的设计有以下好处:

    1. 应用程序组件既可以在Spring的IoC容器中运行,也可以自己编写代码自行组装配置;
    2. 测试的时候并不依赖Spring容器,可单独进行测试,大大提高了开发效率。

    2.2、FactoryBean(工厂bean)

    在普通的 bean 当中,class属性指的是什么类,则取到的就是该类的实例对象。但通过 FactoryBean 我们可以自定义 bean 的创建过程,包括自定义返回的类、是否单例、bean的类型。

    定义一个工厂 bean,MyBean.java如下,可以看到,实际返回的是 User  bean:

    package test;
    
    import org.springframework.beans.factory.FactoryBean;
    
    public class Mybean implements FactoryBean {
    
        @Override
        public User getObject() throws Exception {  //实际返回的对象实例
            User user = new User();
            return user;
        }
    
        @Override
        public Class<?> getObjectType() {  //Bean的类型
            return null;
        }
    
        @Override
        public boolean isSingleton() {  //是否为单例,true为单例,false为非单例
            return false;
        }
    }

    配置文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="mybean" class="test.Mybean"></bean>
    </beans>

    测试代码:

    定义FactoryBean后,Spring创建的Bean实际上是这个FactoryBeangetObject()方法返回的Bean。

    package test.testDemo;
    
    import dao.UserDao;
    import dao.impl.UserDaoImpl;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import service.UserService;
    import service.impl.UserServiceImpl;
    import test.Mybean;
    import test.User;
    import test.User02;
    
    public class Test01 {
    
        public static void main(String[] args) {
            //加载spring配置文件
            ApplicationContext context = new ClassPathXmlApplicationContext("bean04.xml");
    
            //获取配置创建的对象,实际将返回User类实例。这里的 getBean() 方法要同时写上 id 和类.class,否则可能报错
            User user = context.getBean("mybean", User.class);
            user.setName("wen");
            System.out.println(user);
        }
    }

    2.3、bean的作用域(作用范围)

    bean 的作用域可以理解为 bean 的作用范围。Spring3 中为 Bean 定义了5种作用域,分别为 singleton(单例,默认值)、prototype、request、session 和 global session。

    2.3.1、singleton作用域(单例,默认值)

    singleton:单例模式,Singleton 作用域是Spring 中的默认作用域。在单例模式下,多次通过 getBean() 方法获取同一个 bean 时,获取到的实例对象实际上都是同一个对象。在不同配置文件中,即使 bean 标签的 class 指向的是同一个类,也不是同一个bean,此时创建的不是同一个实例对象。在同一个配置文件中,指向的是不同的class,也不会是同一个实例对象。

    Singleton 是单例类型,在加载配置文件时即创建 bean 对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。

    该模式在多线程下是不安全的。Singleton 作用域是Spring 中的缺省作用域,也可以显示的将 Bean 定义为 singleton 模式,配置为:

    <bean id="..." class="..." scope="singleton"></bean>

    2.3.1、prototype作用域

    prototype:原型模式,多实例。在每次通过 Spring 容器获取 prototype 定义的 bean 时,容器都会创建一个新的 Bean 实例,每个 Bean 实例都有自己的属性和状态。跟单例 singleton 不同, singleton 全局只有一个对象。

    <bean id="SingletonBean" class="com.spring.demo.SingletonBean" scope="prototype"></bean>

    2.4、bean的生命周期

    bean 的生命周期,即 bean 从创建到销毁的过程。

    bean 的生命周期如下:

    1. 实例化。通过构造器创建 bean 实例
    2. 属性赋值。设置 bean 的属性(依赖注入)
    3. 调用后置处理器的 postProcessBeforeInitialization() 方法(需在 xml 中配置后置处理器,跟配置一个bean一样,class指向后置处理器即可。后置处理器实际上就是一个实现了BeanPostProcessor接口的类)
    4. 初始化。调用 bean 标签 init-method 指定的类的方法,init-method 指定的方法由类定义,可进行一些初始化操作(需手动配置)。初始化完成后 bean 就可以使用了
    5. 调用后置处理器的 postProcessAfterInitialization() 方法
    6. 销毁。当容器关闭时,调用 bean 标签 destroy-method 指定的类的方法,destroy-method 指定的方法由类定义。(可调用ApplicationContextObj.close()方法来手动销毁bean)

    3、IOC创建对象

    3.1、基于XML配置文件创建对象

    在spring配置文件中,使用 bean 标签就可以实现对象的创建。如下:

    <bean id="user" class="test.User"></bean>

    基于 xml 配置文件创建对象时,默认情况下是执行该类的无参数构造函数来创建对象的,所以如果该类没有无参构造函数程序将会报错。

    bean 标签常见属性:

    • id:给该类指定一个唯一标识。每个<bean ...>都有一个id标识,相当于Bean的唯一ID。
    • class:类的完整类名

    3.2、注解方式创建对象(@Conmponent、@Service、@Controller、@Repository

    使用Spring的IoC容器,实际上就是通过类似XML这样的配置文件,把我们自己的Bean的依赖关系描述出来,然后让容器来创建并装配Bean。一旦容器初始化完毕,我们就直接从容器中获取Bean使用它们。使用XML配置的优点是所有的Bean都能一目了然地列出来,并通过配置注入能直观地看到每个Bean的依赖。它的缺点是写起来非常繁琐,每增加一个组件,就必须把新的Bean配置到XML中。我们可以使用注解的方式进行配置,让Spring自动扫描Bean并组装它们。

    spring 针对 bean 管理中创建对象提供了四种注解:

    1. @Conmponent
    2. @Service 业务层
    3. @Controller web层
    4. @Repository 持久层

    创建对象有四种注解,但目前来说,这四个注解的功能都是一样的。只是建议 @Service 用在业务层,@Controller 用在 web 层,@Repository 用在持久层,但并不是强制的,可以混用。有四个注解只是为了后续的版本当中进行功能扩展 。

    使用注解创建对象:

    先导入依赖包,除了 spring 的几个基本包外,还需要导入 aop 包:

    然后需要开启组件扫描。开启组件扫描需要先引入命名空间,然后就可以使用 <context:component-scan base-package="要扫描的包路径"></context:component-scan> 标签开启组件扫描:

    <?xml version="1.0" encoding="UTF-8"?>
    <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">
    
        <!--开启组件扫描。base-package写包名,若要扫描多个包,可以用逗号隔开,或者直接写多个包共用的上级目录-->
        <context:component-scan base-package="test, service.impl"></context:component-scan>
    </beans>

    开启组件扫描后就可以给类添加注解了,下面的@Component可以换成 @Service等其它的几个注解,效果都一样。

    package test;
    
    import org.springframework.stereotype.Component;
    
    
    //注解里面value属性名及值都可以省略不写,即写成@Compnent,此时bean的id默认是类名首字母小写后的名称
    @Component(value = "user")   //相当于<bean id="user" class="完整类名"></bean>
    public class User {
        public void add() {
            System.out.println("add...");
        }
    }

    验证,使用bean:

    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import test.User;
    
    public class Test {
        public static void main(String[] args) {
            //加载spring配置文件
            ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");
    
            //获取配置创建的对象
            User user = (User) context.getBean("user");
            System.out.println(user);
            user.add();
        }
    }

    3.2.1、组件扫描配置

    开启组件扫描后,默认情况是配置的包下的所有类、所有注解都会扫描。我们可以配置只扫描哪些注解,或者不扫描哪些注解。

    配置只扫描 @Controller 注解,注意,要加上user-default-filters标签:

    <?xml version="1.0" encoding="UTF-8"?>
    <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">
    
        <context:component-scan base-package="test, service" use-default-filters="false">   <!--use-default-filters表示不使用默认filter-->
            <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        </context:component-scan>
    
    </beans>

    配置了只扫描 @Controller 注解后,其他注解将不会被扫描到,如果使用其他注解的 bean 程序将会报错:No bean named 'xxx' available...

    配置不扫描@Controller 注解,注意,不能加上user-default-filters标签:

    <?xml version="1.0" encoding="UTF-8"?>
    <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">
    
        <context:component-scan base-package="test, service">
            <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        </context:component-scan>
    
    </beans>

    3.2.2、注解配置bean作用域(@Scope())

    对于Spring容器来说,当我们把一个Bean标记为添加注解比如@Component后,它就会自动为我们创建一个单例(Singleton),即容器初始化时创建Bean,容器关闭前销毁Bean。在容器运行期间,我们调用getBean(Class)获取到的Bean总是同一个实例。

    我们可以通过 @Scope() 注解来配置 bean 的作用域:

    比如声明为 prototype 作用域:

    @Component
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope("prototype")
    public class MailSession {
        ...
    }

    4、IOC依赖注入(注入属性)

    4.1、基于XML配置文件的依赖注入

    我们可以在 xml 文件中直接配置在创建该类的对象时,同时给该类配置属性。

    基于 xml 配置文件来注入类属性有两种方式:

    1. 通过类的 set() 方法注入属性。类似 setName() 等等
    2. 通过类的有参构造函数注入属性

    4.1.1、通过set()方法注入属性(property标签)

    我们在类中定义了 setter,就可以在 spring 的配置文件中通过配置直接给该类实例指定属性值。代码示例如下:

    User类:

    public class User {
        private String name;
    
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }

    配置文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!-- 配置创建的对象-->
        <bean id="user" class="springtest.User">
            <property name="name" value="wen"></property>
            <property name="age" value="12"></property>
        </bean>
    </beans>

    使用上述配置后,在创建 User 类时会自动给该类注入属性。

    测试代码:

    package test;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import springtest.User;
    
    public class test01 {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");
    
            //获取配置创建的对象
            User user = (User) context.getBean("user");
    
            System.out.println(user.getName() + user.geAge());  //输出 wen12
        }
    }

    4.1.2、通过有参构造函数注入属性(constructor-arg标签)

    如果一个类中有有参构造函数,我们就可以通过有参构造来给该类的实例注入属性。当类中有有参构造函数,我们也只能使用有参构造来注入属性,否则将会报错。

    代码示例如下:

    User 类:

    package springtest;
    
    public class User {
        private String name;
    
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        //有参构造。定义了有参构造则编译器不会自动创建无参构造,所以配置文件中如果不使用有参构造来注入属性的话程序会报错
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }

    配置文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="user" class="springtest.User">
            <!-- 也可以使用索引的形式 <constructor-arg index="0" value="wen"></constructor-arg> -->
            <constructor-arg name="name" value="wen"></constructor-arg>   
            <constructor-arg name="age" value="11"></constructor-arg>
        </bean>
    </beans>

    使用上述配置后,在创建 User 类时会自动调用该类的有参构造来给该类注入属性。

    测试代码:

    package test;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import springtest.User;
    
    public class test01 {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");
    
            //获取配置创建的对象
            User user = (User) context.getBean("user");
            System.out.println(user.getName() + user.geAge());  //输出 wen11
        }
    }

    4.1.3、注入外部bean(注入类)

    如果注入的属性值是booleanintString这样的数据类型,可以通过value注入。如果注入的属性值是类,则可以通过 ref 注入。

    示例:

    UserServiceImpl 类中有一个 setter 需要设置 userDao 属性为 UserDao 类:

    public class UserServiceImpl implements UserService {
        private UserDao userDao;
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
    
        public void add() {
            this.userDao.add();
        }
    }

    在传统程序中,需要调用 setUserDao() 方法来主动将一个 UserDao 类注入。写法如下:

    public class Test01 {
    
        public static void main(String[] args) {
            UserServiceImpl userServiceImpl = new UserServiceImpl();
            UserDao userDao = new UserDaoImpl();
            userServiceImpl.setUserDao(userDao);   //调用 setUserDao 注入UserDao类
            userServiceImpl.add();
        }
    }

    使用spring配置文件可以直接将 UserDao bean作为属性注入 UserService当中,实际上相当于调用了 setUserDao() 方法。配置文件如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean name="userdao" class="dao.impl.UserDaoImpl"></bean>
    
        <bean name="userservice" class="service.impl.UserServiceImpl">
            <property name="userDao" ref="userdao"></property>
        </bean>
    </beans>

    Bean的顺序不重要,Spring根据依赖关系会自动正确初始化。 

    测试代码:

    package test.testDemo;
    
    import dao.UserDao;
    import dao.impl.UserDaoImpl;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import service.UserService;
    import service.impl.UserServiceImpl;
    import test.User;
    
    public class Test01 {
    
        public static void main(String[] args) {
            //加载spring配置文件
            ApplicationContext context = new ClassPathXmlApplicationContext("bean02.xml");
    
            UserServiceImpl userServiceImpl = context.getBean(UserServiceImpl.class);
            userServiceImpl.add();
        }
    }

    4.1.4、内部bean注入类

    上面注入外部bean,实际上就是在外部建一个bean,然后将该bean赋值给 UserService bean的属性。除了上面的写法,我们还可以采用内部bean的写法来将一个类注入给另一个类的属性。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean name="userservice" class="service.impl.UserServiceImpl">
            <property name="userDao">
                <bean id="userdao" class="dao.impl.UserDaoImpl"></bean>   <!--  如果该内部bean还需要注入属性,可以再给该内部bean配置property -->
            </property>
        </bean>
    </beans>

    4.1.5、注入集合

     给类注入集合,示例如下:

    package test;
    
    import java.util.*;
    
    public class User02 {
        private String[] myArr;
        private List<String> myList;
        private Set<String> mySet;
        private Map<String, String> myMap;public void setMyArr(String[] myArr) {
            this.myArr = myArr;
        }
    
        public void setMyList(List<String> myList) {
            this.myList = myList;
        }
    
        public void setMySet(Set<String> mySet) {
            this.mySet = mySet;
        }
    
        public void setMyMap(Map<String, String> myMap) {
            this.myMap = myMap;
        }
    }

    配置文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="user02" class="test.User02">
            <!-- 给数组注入数据 -->
            <property name="myArr">
                <array>
                    <value>AAA</value>
                    <value>BBB</value>
                </array>
            </property>
    <!-- 注入 list 集合数据 --> <property name="myList"> <list> <value>AAA</value> <value>BBB</value> </list> </property>
    <!-- 注入 set 集合数据 --> <property name="mySet"> <set> <value>AAA</value> <value>BBB</value> </set> </property>
    <!-- 注入 Map 数据 --> <property name="myMap"> <map> <entry key="testA" value="aaa"></entry> <entry key="testB"> <value>bbb</value> </entry> </map> </property> </bean> </beans>

    上面的集合当中的元素的值都是字符串,如果集合当中的元素是类的话,我们可以使用 ref 标签来注入:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="user" class="test.User02">
            <!-- myList2集合的元素是User类实例对象 -->
            <property name="myList2">
                <list>
                    <ref bean="user01"></ref>
                    <ref bean="user02"></ref>
                </list>
            </property>
        </bean>
    
        <bean id="user01" class="test.User"></bean>
        <bean id="user02" class="test.User"></bean>
    </beans>

    上面将集合注入属性只是注入了某个类当中,我们可以把集合提取出来,多个类就可以重用这些集合。

    首先我们需要先导入 util 命名空间,util命名空间提供了集合相关的配置,在使用命名空间前要导入util命名空间,如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:util="http://www.springframework.org/schema/util"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                                http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd">
    
    </beans>

    下面提取出一个 List 并在 bean 中注入,代码示例:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:util="http://www.springframework.org/schema/util"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                                http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd">
    
        <util:list id="userList">
            <value>张三</value>
            <value>李四</value>
            <value>王五</value>
        </util:list>
    
        <bean id="user02" class="test.User02">
            <!-- 注入 list 集合数据 -->
            <property name="myList" ref="userList"></property>
        </bean>
    
    </beans>

    4.1.6、注入其它类型值(空值、特殊符号)

    注入空值:

    <bean id="user" class="test.User">
        <property name="name">
            <null></null>
        </property>
        <property name="age" value="12"></property>
    </bean>

    注入特殊符号:

    XML中共有5个特殊的字符,分别是:&<> “’。如果配置文件中的注入值包括这些特殊字符,就需要进行特别处理。

    有两种解决方法:

    1. 采用本例中的<![CDATA[ ]]>特殊标签,将包含特殊字符的字符串封装起来。<![CDATA[ ]]>的作用是让XML解析器将标签中的字符串当作普通的文本对待,以防止某些字符串对XML格式造成破坏。
    2. 使用XML转义序列表示这些特殊的字符,这5个特殊字符所对应XML转义序列如下:

     示例:给 name 属性赋值为 <<wen>>

    <bean id="user" class="test.User">
        <property name="name" value="&lt;&lt;wen&gt;&gt;"></property>
        <property name="age" value="12"></property>
    </bean>
    <bean id="user" class="test.User">
        <property name="name">
            <value><![CDATA[<<wen>>]]></value>   <!-- 给name赋值为<<wen>> -->
        </property>
        <property name="age" value="12"></property>
    </bean>

    4.1.7、自动装配(autowire)

    传统的XML方式配置 Bean 组件都是通过 <property> 标签为Bean的属性注入所需的值,当需要维护的Bean组件及需要注入的属性更多时,势必会增加配置的工作量,这时我们可以使用自动装配。

    使用自动装配只需给 bean 标签添加 autowire 属性即可。配置示例:

    <bean id="user" class="test.User" autowire="byName"/>

    通过设置<bean>元素的autowire属性指定自动装配,代替了通过<property>标签显示指定Bean的依赖关系。由BeanFactory检查XML配置文件的内容,为Bean自动注入依赖关系。

    Spring提供了多种自动装配方式,autowire属性常用的取值如下所示

    • no:不使用自动装配。Bean依赖关系必须通过property元素定义。
    • byName:根据属性名自动装配。BeanFactory查找容器中的全部Bean,找出 id 与属性的 setter 方法入参匹配的Bean。找到即自动注入,否则什么都不做。
    • byType:根据属性类型自动装配。BeanFactory查找容器中的全部Bean,如果正好有一个与依赖属性类型相同的Bean,就自动装配这个属性;但是如果有多个这样的Bean,Spring无法决定注入哪个Bean,就抛出一个致命异常;如果没有匹配的Bean,就什么都不会发生,属性不会被设置。
    • constructor:与byType的方式类似,不同之处在于它应用于构造器参数。如果在容器中没有找到与构造器参数类型一致的Bean,那么将会抛出异常

    自动装配的局限性:

    • 不是所有类型都可以使用自动装配,不能自动装配的数据类型有:Object、基本数据类型(Date、CharSequence、Number、URI、URL、Class、String)等等。因为自动装配是注入了一个bean。
    • 自动装配不如显式装配精确,如果可能的话尽量使用显式装配。

    实例:

    假设 User 类中有一个属性类型为 Animal 类:

    public class User {
        private Animal animal;
    
        public Animal getAnimal() {
            return animal;
        }
    
        public void setAnimal(Animal animal) {
            this.animal = animal;
        }
    }

    此时我们可以使用自动装配:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="user" class="test.User" autowire="byName"></bean>
    
        <bean id="animal" class="test.Animal"></bean>
    </beans>

    上面使用 byName 类型的自动装配,id 为animal 的 bean 将匹配到 User 类中的 setter,所以会将 animal 类注入 User 中的 animal 属性当中。(上面配置直接改成byType也可以)

    验证代码:

    package UnitDemo;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import test.User;
    import test.User02;
    
    public class Test {
        public static void main(String[] args) {
            ApplicationContext context2 = new ClassPathXmlApplicationContext("bean01.xml");
            User user = (User) context2.getBean("user");
            user.getAnimal().setName("cat");
            System.out.println(user.getAnimal().getName());
        }
    }

    4.1.8、引入properties配置文件

    如果配置过多,在一个 xml 文件里面维护可能相对比较困难,这时我们可以在一个 propreties 配置文件中配置一些属性,然后再在 xml 配置文件中引入 propreties 文件的配置。比如可用于连接数据库的配置。

    示例:

    在项目的 src 目录下建一个 properties 类型文件 jdbc.properties,properties 类型文件的内容是键值对形式。往该文件写入以下内容:

    prop.driverClass=com.mysql.jdbc.Driver
    prop.url=jdbc:mysql://localhost:3306/userDB
    prop.username=root
    prop.password=123456

    然后就可以在 xml 配置文件中引入该文件,在引入之前我们需要先写 context 命名空间:

    <?xml version="1.0" encoding="UTF-8"?>
    <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">
    
    </beans>

    最后就可以通过 <context> 标签将 properties 文件引入,并且可以用 ${} 来使用 properties 文件的值:

    <?xml version="1.0" encoding="UTF-8"?>
    <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">
    
        <!--引入外部属性文件-->
        <context:property-placeholder location="classpath:jdbc.properties"/>
        
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${prop.driverClass}"></property>  <!--通过${}使用外部配置文件的值-->
            <property name="url" value="${prop.url}"></property>
            <property name="username" value="${prop.username}"></property>
            <property name="password" value="${prop.password}"></property>
        </bean>
    </beans>

    4.2、注解方式依赖注入(@Autowired、@Qualifier、@Resource、@Value)

    spring 注解注入属性提供了几种注解:

    1. @Autowired:根据属性类型进行自动装配,即 byType
    2. @Qualifier:根据属性名称进行自动装配,即 byName。需要配合@Autowired进行使用,用于在@Autowired匹配到多个类时,指定究竟使用哪个类来进行注入。
    3. @Resource:既可以根据类型注入,也可以根据名称注入。不指定 name 和 type 则自动按照 byName 方式进行装配,如果没有匹配成功,则回退为一个原始类型进行匹配,如果匹配成功则自动装配。
    4. @Value:注入基本数据类型数据

    使用注解,比如@Autowired,就相当于把指定类型的Bean注入到指定的字段中。和XML配置相比,注解的方式大幅简化了注入,因为它不但可以写在set()方法上,还可以直接写在字段上,甚至可以写在构造方法中。

    @Component
    public class UserService {
        MailService mailService;
    
        public UserService(@Autowired MailService mailService) {
            this.mailService = mailService;
        }
        ...
    }

    4.2.1、@Autowired(根据类型装配)

    @Autowired 注解会根据属性类型进行自动装配,即 byType。默认情况下它要求依赖对象必须存在,如果不存在,程序将会报错。但我们也可以设置它的required属性为false,来让它的依赖对象如果允许为 null 值,@Autowired(required = false)

    实例:比如在 UserServiceImpl 类中的某个属性注入 UserDaoImpl 类型值。

    UserDaoImpl 代码:只需给实现类添加注解即可,接口不能添加注解

    package dao.impl;
    
    import dao.UserDao;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public class UserDaoImpl implements UserDao {
    
        @Override
        public void add() {
            System.out.println("userdaoimpl add...");
        }
    }

    UserServiceImpl 代码:

    package service.impl;
    
    import dao.UserDao;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import service.UserService;
    
    @Service
    public class UserServiceImpl implements UserService {
    
        //不需要添加set方法
        @Autowired
        private UserDao userDao;
    
        @Override
        public void add() {
            System.out.println("userserviceimpl add...");
            userDao.add();
        }
    }

    验证代码:

    package UnitDemo;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import service.UserService;
    import test.User;
    
    public class Test {
        public static void main(String[] args) {
            //加载spring配置文件
            ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");
    
            //获取配置创建的对象
            UserService userService = (UserService) context.getBean("userServiceImpl");
            userService.add();   //将输出 userserviceimpl add...    userdaoimpl add...
        }
    }

    4.2.2、@Qualifier(通过名称标识唯一类注入@Autowired中)

    使用 @Autowired 时,如果某个类有多个实现类,则 spring 无法识别究竟将哪个实现类来进行注入,此时程序将会直接报错。此时我们可以将 @Autowired 和 @Qualifier 搭配使用,@Qualifier 可通过名称来标识究竟使用哪个类来进行注入。

    @Qualifier 用法示例:

    假设 UserDao 有多个实现类:UserDaoImpl、UserDaoImpl02,UserDaoImpl02代码如下:

    package dao.impl;
    
    import dao.UserDao;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public class UserDaoImpl02 implements UserDao {
        @Override
        public void add() {
            System.out.println("userdaoimpl02 add...");
        }
    }

    此时如果我们直接使用 @Autowired 程序将会直接报错,因为 spring 无法识别究竟使用哪个类来进行注入。所以我们可以使用 @Qualifier("bean标识") 来指定究竟使用哪个类来进行注入:

    package service.impl;
    
    import dao.UserDao;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.stereotype.Service;
    import service.UserService;
    
    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        @Qualifier("userDaoImpl02")
        private UserDao userDao;
    
        @Override
        public void add() {
            System.out.println("userserviceimpl add...");
            userDao.add();
        }
    }

    验证代码:

    package UnitDemo;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import service.UserService;
    import test.User;
    
    public class Test {
        public static void main(String[] args) {
            //加载spring配置文件
            ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");
    
            //获取配置创建的对象
            UserService userService = (UserService) context.getBean("userServiceImpl");
            userService.add();   //将输出 userserviceimpl add...    userdaoimpl02 add...
        }
    }

     4.2.3、@Resource(byType、byName)

    匹配规则:

    1. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
    2. 如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
    3. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
    4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。

    UserDaoImpl02 实现类:

    package dao.impl;
    
    import dao.UserDao;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public class UserDaoImpl02 implements UserDao {
        @Override
        public void add() {
            System.out.println("userdaoimpl02 add...");
        }
    }

    UserServiceImpl 实现类:

    package service.impl;
    
    import dao.UserDao;
    import org.springframework.stereotype.Service;
    import service.UserService;
    
    import javax.annotation.Resource;
    
    @Service
    public class UserServiceImpl implements UserService {
        @Resource(name = "userDaoImpl02")
        private UserDao userDao;
    
        @Override
        public void add() {
            System.out.println("userserviceimpl add...");
            userDao.add();
        }
    }

    验证代码:

    package UnitDemo;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import service.UserService;
    import test.User;
    
    public class Test {
        public static void main(String[] args) {
            //加载spring配置文件
            ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");
    
            //获取配置创建的对象
            UserService userService = (UserService) context.getBean("userServiceImpl");
            userService.add();   //将输出 userserviceimpl add...   userdaoimpl02 add...
        }
    }

    4.2.4、@Value(注入基本数据类型)

    @Value 注解即注入一个基本类型数据。

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    @Component(value = "user")
    public class User {
    
        @Value(value = "wen")
        private String name;
    
        public void showName() {
            System.out.println(this.name);
        }
    }

    验证代码:

    package UnitDemo;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import service.UserService;
    import test.User;
    
    public class Test {
        public static void main(String[] args) {
            //加载spring配置文件
            ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");
    
            //获取配置创建的对象
            User user = (User) context.getBean("user");
            user.showName();   //输出 wen
        }
    }

    4.2.5、注入list

    有些时候,我们会有一系列接口相同,不同实现类的Bean。例如,注册用户时,我们要对email、password和name这3个变量进行验证。为了便于扩展,我们先定义验证接口:

    public interface Validator {
        void validate(String email, String password, String name);
    }

    然后,分别使用3个Validator对用户参数进行验证:

    @Component
    public class EmailValidator implements Validator {
        public void validate(String email, String password, String name) {
            if (!email.matches("^[a-z0-9]+\@[a-z0-9]+\.[a-z]{2,10}$")) {
                throw new IllegalArgumentException("invalid email: " + email);
            }
        }
    }
    
    @Component
    public class PasswordValidator implements Validator {
        public void validate(String email, String password, String name) {
            if (!password.matches("^.{6,20}$")) {
                throw new IllegalArgumentException("invalid password");
            }
        }
    }
    
    @Component
    public class NameValidator implements Validator {
        public void validate(String email, String password, String name) {
            if (name == null || name.isBlank() || name.length() > 20) {
                throw new IllegalArgumentException("invalid name: " + name);
            }
        }
    }

    最后,我们通过一个Validators作为入口进行验证:

    @Component
    public class Validators {
        @Autowired
        List<Validator> validators;
    
        public void validate(String email, String password, String name) {
            for (var validator : this.validators) {
                validator.validate(email, password, name);
            }
        }
    }

    注意到Validators被注入了一个List<Validator>,Spring会自动把所有类型为Validator的Bean装配为一个List注入进来,这样一来,我们每新增一个Validator类型,就自动被Spring装配到Validators中了,非常方便。

    因为Spring是通过扫描classpath获取到所有的Bean,而List是有序的,要指定List中Bean的顺序,可以加上@Order注解:

    @Component
    @Order(1)
    public class EmailValidator implements Validator {
        ...
    }
    
    @Component
    @Order(2)
    public class PasswordValidator implements Validator {
        ...
    }
    
    @Component
    @Order(3)
    public class NameValidator implements Validator {
        ...
    }

    4.2.5、初始化和销毁

    有些时候,一个Bean在注入必要的依赖后,需要进行初始化(监听消息等)。在容器关闭时,有时候还需要清理资源(关闭连接池等)。我们通常会定义一个init()方法进行初始化,定义一个shutdown()方法进行清理,然后,引入JSR-250定义的Annotation:

    在Bean的初始化和清理方法上标记@PostConstruct@PreDestroy

    @Component
    public class MailService {
        @Autowired(required = false)
        ZoneId zoneId = ZoneId.systemDefault();
    
        @PostConstruct
        public void init() {
            System.out.println("Init mail service with zoneId = " + this.zoneId);
        }
    
        @PreDestroy
        public void shutdown() {
            System.out.println("Shutdown mail service");
        }
    }

    Spring容器会对上述Bean做如下初始化流程:

    • 调用构造方法创建MailService实例;
    • 根据@Autowired进行注入;
    • 调用标记有@PostConstructinit()方法进行初始化。

    而销毁时,容器会首先调用标记有@PreDestroyshutdown()方法。Spring只会根据注解查找无参数方法,对方法名不作要求。

    4.2.6、完全注解开发(@Configuration)

    我们可以通过 spring 配置类来实现完全注解开发,即不需要 xml 配置文件。

    用 @Configuration 注解来新建配置类,比如命名为 SpringConfig,以此替代 xml 配置文件:

    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan(basePackages = {"test", "dao", "service"})  //指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的<context:component-scan base-package=“org.woster”/>是一样的。
    public class SpringConfig {
    }

    使用@ComponentScan来告诉容器需要扫描的包,如果不指定参数,即只有@ComponentScan,则会自动搜索当前配置类所在的包以及子包。

    使用配置类获取 ApplicationContext 对象的语法跟使用配置文件的不太一样,除此之外,其他情况都一样:

    package UnitDemo;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import test.SpringConfig;
    import test.User;
    
    public class Test {
        public static void main(String[] args) {
            ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
            User user = (User) context.getBean("user");
            user.showName();
        }
    }
  • 相关阅读:
    遍历数组
    push/pop和unshift/shift
    完全卸载oracle11g
    截图神器-snipaste
    截图神器-snipaste
    VS2015 +.NETMVC5 +EF实践
    VS2015 +.NETMVC5 +EF实践
    github 客户端总是登录失败,提示密码错误
    github 客户端总是登录失败,提示密码错误
    【.NET MVC分页】.NET MVC 使用pagelist 分页
  • 原文地址:https://www.cnblogs.com/wenxuehai/p/14602093.html
Copyright © 2011-2022 走看看