02-IOC

nobility 发布于 2022-03-18 503 次阅读


IOC

传统开发方式如下:创建一个接口UserService,使用其实现类UserServiceImpl中的方法,这就有一个问题,当需要修改方法时(修改为其他的实现类)必须到源代码中进行修改,不符合开闭原则,耦合度高

UserService user = new UserServiceImpl();
user.hello();

工厂模式开发方式:创建一个获取该接口下的实现类的工厂UserServiceFactory,就解决了修改方法时对源代码的修改,只需要将工厂中返回的实现类对象修改即可,但是手动管理工厂又有些麻烦,也不太符合开闭原则

class{
  public static UserService getUserService(){
    return new UserServiceImpl();	//当需要修改方法时,将此处返回的实现类修改即可
  }
}

UserService user = new UserServiceFactory.getUserService();
user.hello();

基本使用

xml方式

现在已有userService接口,并且也有该接口的实现类UserServiceImpl,创建Spring-Framework全局配置文件,一般命名为applicationContext.xml,填写以下配置信息

<?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="userService" class="com.UserServiceImpl">  <!--bean标签用于声明Spring容器中管理的对象--> 
    <!--com.UserServiceImpl类的对象交由Spring创建,使用id可从IOC容器中获取的该对象-->
    <!--id属性用于从IOC容器中获取使用该类创建的对象,若省略id属性默认是该类的首字母小写类名-->
    <!--class属性指定类的全类名-->
    <!--可使用name属性替代id属性,唯一区别就是:name属性可使用特殊字符命名,而id属性却不能-->
    <property name="name" value="world"/>  <!--property标签用于为对象的属性赋值--> 
    <!--name属性指定该属性的属性名,value属性指定该属性的属性值--> 
  </bean>
</beans>

编写程序进行使用

public static void main(String[] args) {
  ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
  //从类路径中获取配置文件,并创建applicationContext对象,SpringBean工厂对象
  UserService userService = (UserService) applicationContext.getBean("userService");
  //使用getBean()方法根据容器中的ID获取对象,需要强转
  userService.hello();	//hello world
  //执行接口中定义的方法
}

注解方式

现在已有userService接口,并且也有该接口的实现类UserServiceImpl,创建Spring-Framework全局配置文件,一般命名为applicationContext.xml,填写以下配置信息

<?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
                           https://www.springframework.org/schema/context/spring-context.xsd">
  <!--
    首先添加 xmlns:context="http://www.springframework.org/schema/context" 约束文件
    再在 xsi:schemaLocation属性后增加 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
    之后使用下面标签才能有代码提示
  -->
  
  <context:component-scan base-package="com"/>
  <!--开启注解扫描,base-package属性指定要扫描的包,也就是说只有开启注解扫描的包,包下的源代码中才能有效的使用注解进行管理-->
</beans>

在类中使用注解,具体如下

package com;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("userService")  //对应xml中的id属性,用于从IOC容器中获取使用该类创建的对象,省略默认是该类首字母小写类名
//@Repository()
//@Service() 
//@Controller()
//上面这三个注解与Component注解作用相同,用于MVC分层,便于越多,后续Spring新版本会增强这些注解
public class UserServiceImpl implements UserService {
  @Value("world")  //直接属性注入方式无需setter方法,若在setter方法上使用该注解也是可以的
  private String name;

  @Override
  public void hello() {
    System.out.println("hello" + this.name);
  }
}

编写程序进行使用

public static void main(String[] args) {
  ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
  UserService userService = (UserService) applicationContext.getBean("userService");
  userService.hello();
}

Spring的Bean管理

IOC(Inversion of Control)控制反转的思想,就是将所有对象的创建和依赖关系维护容器工厂(Spring)管理,原理就是使用反射创建对象

Bean实例化的三种方式

<!--1.默认使用无参构造方式-->
<bean id="userServiceConstruction" class="com.UserServiceImpl"/>

<!--2.使用静态工厂方法实例化(简单工厂模式)-->
<bean id="userServiceSimpleFactory" class="com.UserServiceImpl" factory-method="SimpleFactory"/>
<!--factory-method属性指定工厂方法,用于返回该实例-->

<!--3.使用实例工厂方法实例化(工厂方法模式)-->
<bean id="userService" class="com.UserServiceImpl"/>
<bean id="userServiceFactoryMethod" factory-bean="userService" factory-method="userServiceFactoryMethod"/>
<!--需要先创建实例,再使用factory-bean属性指定工厂实例,再factory-method属性指定工厂方法,用于返回该实例-->

用于测试的关键代码如下

package com;

public class UserServiceImpl implements UserService {  //UserService接口置空了
  public UserServiceImpl() {
    System.out.println("无参构造方法被执行");
  }

  public static UserServiceImpl SimpleFactory() {
    System.out.println("静态工厂方法被执行");
    return null;  //用于测试就返回null了,以免构造方法多次输出
  }

  public UserServiceImpl userServiceFactoryMethod() {
    System.out.println("实例工厂方法被执行");
    return null;  //用于测试就返回null了,以免构造方法多次输出
  }
  
  public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    //无参构造执行两次的原因是在使用实例工厂方法时,为了不冲突,又创建了一个实例
    //无参构造方法被执行
    //静态工厂方法被执行
    //无参构造方法被执行
    //实例工厂方法被执行
  }
}

Bean的作用范围

xml方式
<!--1.默认是单例模式,实例创建采用预加载策略-->
<bean id="userServiceSingle" class="com.UserServiceImpl"/>
<!--2.使用多例模式,实例创建采用懒加载策略-->
<bean id="userServiceMultiton" class="com.UserServiceImpl" scope="prototype"/>

下面是测试程序

public static void main(String[] args) {
  ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

  UserService userServiceSingle1 = (UserService) applicationContext.getBean("userServiceSingle");
  UserService userServiceSingle2 = (UserService) applicationContext.getBean("userServiceSingle");
  System.out.println(userServiceSingle1 == userServiceSingle2);  //true

  UserService userServiceMultiton1 = (UserService) applicationContext.getBean("userServiceMultiton");
  UserService userServiceMultiton2 = (UserService) applicationContext.getBean("userServiceMultiton");
  System.out.println(userServiceMultiton1 == userServiceMultiton2);  //false
  //预加载会在读取配置文件创建applicationContext对象时就会将对象创建出来,懒加载只有在获取该对象时才会创建出来
  //无参构造方法被执行
  //true
  //无参构造方法被执行
  //无参构造方法被执行
  //false
}
注解方式
package com;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component("userService")
@Scope("prototype") //指定多例模式
public class UserServiceImpl implements UserService {
}

下面是测试程序

public static void main(String[] args) {
  ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
  UserService userService1 = (UserService) applicationContext.getBean("userService");
  UserService userService2 = (UserService) applicationContext.getBean("userService");
  System.out.println(userService1 == userService2);  //false
}

Bean的生命周期

  1. 实例化对象,执行构造方法
  2. 注入属性,执行setter方法
  3. 若该类实现了BeanNameAware接口,会执行setBeanName()方法
  4. 若该类实现了BeanFactoryAwareApplicationContextAware接口,会执行setBeanFactory()方法和setApplicationContext()方法
  5. 有一个类实现了BeanPostProcessor接口,会执行postProcessBeforeInitialization()方法
  6. 若该类实现了InitializingBean接口,会执行afterPropertiesSet()方法
  7. 若该类配置了init-method方法,会执行该初始化方法
  8. 有一个类实现了BeanPostProcessor接口,会执行postProcessAfterInitialization()方法
  9. 执行该类自身的业务方法
  10. 若该类实现了DisposableBean接口,会执行destroy()方法
  11. 若该类配置destroy-method方法,会执行该销毁方法
xml方式
<!--只有单例模式下的对象才能被销毁-->
<bean id="userService" class="com.UserServiceImpl" init-method="initMethod" destroy-method="destroyMethod">
  <property name="name" value="value"/>
</bean>

下面是整个生命周期测试代码

package com;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserServiceImpl implements UserService, BeanNameAware, BeanFactoryAware,
    ApplicationContextAware, BeanPostProcessor, InitializingBean, DisposableBean {
  public static void main(String[] args) {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //ClassPathXmlApplicationContext才有关闭容器方法
    UserService userService = (UserService) applicationContext.getBean("userService");
    userService.service();
    applicationContext.close(); //关闭容器才会执行销毁方法
    //1.执行构造方法
    //2.执行setter()方法
    //3.setBeanName()方法,对象在容器中的名字为:userService
    //4.1.执行setBeanFactory()方法,当前Bean工厂为:org.springframework.beans.factory.support.DefaultListableBeanFactory@57a3af25: defining beans [userService]; root of factory hierarchy
    //4.2.执行setBeanFactory()方法,当前Bean工厂为:org.springframework.context.support.ClassPathXmlApplicationContext@7e0e6aa2, started on Fri Dec 11 19:15:41 CST 2020
    //6.执行afterPropertiesSet()方法
    //7.执行initMethod()方法
    //9.执行业务方法
    //10.执行destroy()方法
    //11.执行destroyMethod()方法
  }

  public UserServiceImpl() {
    System.out.println("1.执行构造方法");
  }

  public void setName(String name) {
    System.out.println("2.执行setter()方法");
  }

  @Override
  public void setBeanName(String name) {
    System.out.println("3.setBeanName()方法,对象在容器中的名字为:" + name);
  }

  @Override
  public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    System.out.println("4.1.执行setBeanFactory()方法,当前Bean工厂为:" + beanFactory);
    //BeanFactory是顶层工厂接口
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    System.out.println("4.2.执行setBeanFactory()方法,当前Bean工厂为:" + applicationContext);
    //ApplicationContext是BeanFactory工厂的子接口
  }

  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("5.执行postProcessBeforeInitialization()方法");
    return null;
  }

  @Override
  public void afterPropertiesSet() {
    System.out.println("6.执行afterPropertiesSet()方法");
  }

  public void initMethod() {
    System.out.println("7.执行initMethod()方法");
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("8.执行postProcessAfterInitialization()方法");
    return null;
  }

  @Override
  public void service() {
    System.out.println("9.执行业务方法");
  }

  @Override
  public void destroy() {
    System.out.println("10.执行destroy()方法");
  }

  public void destroyMethod() {
    System.out.println("11.执行destroyMethod()方法");
  }
}
注解方式

初始化和销毁注解是在javax.annotation包中,jdk1.8以后的版本需要引入javax.annotation-api包才能使用,注意在Web项目中依赖范围应该是provided

package com;

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component("userService")
public class UserServiceImpl implements UserService {
  @PostConstruct  //初始化方法注解
  public void initMethod() {
    System.out.println("执行initMethod()方法");
  }

  @PreDestroy  //销毁方法注解
  public void destroyMethod() {
    System.out.println("执行destroyMethod()方法");
  }
}

编写程序进行测试

public static void main(String[] args) {
  ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //ClassPathXmlApplicationContext才有关闭容器方法
  UserService userService = (UserService) applicationContext.getBean("userService");
  applicationContext.close(); //关闭容器才会执行销毁方法
}

Spring的依赖注入

DI(Dependency Injection)依赖注入就是在Spring创建对象时,将对象所依赖的属性注入进去

构造注入

<bean id="userService" class="com.UserServiceImpl">
  <constructor-arg name="name" value="张三"/> <!--constructor-arg标签用于向构造方法中传递参数-->
  <!--name属性指定形参,value指定参数值-->
  <!--如果是复杂数据类型,则需要使用ref属性指定其他bean标签的id进行传值-->
  <!--不常用的属性:index属性可指定形参的位置来传递参数值,type属性可指定参数的数据类型-->
  <!--若index和name属性都省略则会按照属性顺序进行注入-->
</bean>

setter注入

<bean id="userService" class="com.UserServiceImpl">
  <property name="name" value="张三"/>  <!--property标签用于向setter方法中传递参数-->
  <!--name属性指定形参,value指定参数值-->
  <!--如果是复杂数据类型,则需要使用ref属性指定其他bean标签的id进行传值-->
</bean>

自动注入

仅针对引用类型有效

xml方式
<bean id="userDao" class="com.UserDaoImpl" />
<bean id="userService" class="com.UserServiceImpl" autowire="byType" />
<!--
使用autowire指定自动注入的规则
  byName:要求变量名与要注入的Bean的id名完全一致
  byType:要求当前容器中符合注入要求的数据类型:一致、或是子类、或是实现类,并且不能有多个符合条件的
-->
注解方式

@Resource注解是在javax.annotation包中,jdk1.8以后的版本需要引入javax.annotation-api包才能使用,注意在Web项目中依赖范围应该是provided

package com;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component("userService")
public class UserServiceImpl implements UserService {
  @Autowired  
  //byType自动注入,若有多个同类型的bean则根据变量的变量名进行自动注入(类的首字母小写),若有多个同类型的bean但其中有一个bean类上使用了@Primary注解会优先注入
  //@Qualifier()  //同时使用Qualifier注解,即可指定bean的id注入,
  //@Resource()   //Resource注解相当于同时使用Autowired和@Qualifier注解,即byName自动注入,若byName失败会自动byType自动注入
  //也可使用name属性指定bean的id注入
  private UserDao userDao;
}

P命名空间注入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       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">
  <!--将第一行的约束文件进行复制,并且增加一个p的命名空间,地址尾部改为p 就变成了 xmlns:p="http://www.springframework.org/schema/p"-->
  <!--
    此时就可以使用以下语法进行属性注入了
      p:属性名="属性值"
      p:属性名-ref="其他bean标签的id"
  -->
  <bean id="userService" class="com.UserServiceImpl" p:name="张三"/>
</beans>

SpEl注入

SpEL(Spring Expression Language)spring表达式语言,使用#{}包裹表达式的方式注入属性

<bean id="userServiceOther" class="com.UserServiceImpl"/>
<bean id="userService" class="com.UserServiceImpl">
  <property name="name" value="#{'张三'}"/>  <!--注入字符串使用单引号-->
  <property name="age" value="#{20 - 2}"/>  <!--注入数字,可是进行简单的计算-->
  <property name="say" value="#{userServiceOther.hello()}"/>  <!--可调用其他bean的方法,若只指定其他bean的id即可注入另一个bean-->
  <property name="sex" value="#{T(Math).PI}"/>  <!--使用静态字段或方法-->
</bean>

集合类型属性注入

<bean id="userService" class="com.UserServiceImpl">
  <property name="array">
    <list>
      <value>1</value>
      <value>2</value>
    </list>
  </property>
  <property name="list">
    <list>
      <value>1</value>
      <value>2</value>
    </list>
  </property>
  <property name="set">
    <set>
      <value>1</value>
      <value>2</value>
    </set>
  </property>
  <property name="map">
    <map>
      <entry key="key1" value="1"/>  <!--key-ref和value-ref属性可指定其他Bean-->
      <entry key="key2" value="2"/>
    </map>
  </property>
  <property name="properties">
    <map>
      <entry key="key1" value="1"/>
      <entry key="key2" value="2"/>
    </map>
  </property>
</bean>

该Java类中定义如下

package com;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class UserServiceImpl implements UserService {
  private String[] array;
  private List<String> list;
  private Set<String> set;
  private Map<String, Integer> map;
  private Properties properties;
  //setter省略
}

xml和注解方式整合

通常使用xml方式进行Bean管理,使用注解方式进行依赖注入,这样即便于开发,也便于维护,这时无需开启整个包的注解扫描,因为只用到了依赖注入的注解,开启注解配置即可,即在applicationContext.xml中增加下面配置

<context:annotation-config />

若需要管理的Bean过于多时,可在在applicationContext.xml中使用import标签引入其他Spring配置文件,具体使用如下

<import resource="classpath:spring-config/student.xml"/>  <!-- classpath:表示当前类路径,路径分隔符使用斜杠-->
<import resource="classpath:spring-config/*"/>  <!--也可以使用星号通配符-->
此作者没有提供个人介绍
最后更新于 2022-03-18