200 Success
200 Success 博客
200 Success
常用标签
组员

一、写在前面

最开始的Java Web项目一般采用的是三层架构的模式,即表现层,业务逻辑层,持久层。每一层都依赖着上一层的对象来实现本层的功能。例如,在一个简单模拟的学生登录系统中:

  • 持久层
public class StudentDaoImpl implements IStudentDao {
    public boolean findStudent(){
        return true;
    }
}
  • 业务逻辑层
public class StudentServiceImpl implements IStudentService {
    private IStudentDao studentDao = new StudentDaoImpl();
    public boolean login(){
        return studentDao.findStudent();
    }
}
  • 表现层(在这里用一个普通的Java类模拟)
public class MainTest {
    public static void main(String[] args) {
        IStudentService studentService = new StudentServiceImpl();
        studentService.login();
    }
}

什么是耦合?通俗的来讲,耦合指的是模块间的依赖。这里的模块可以指对象,也可以指方法等等。

什么是解耦?很明显地,解耦就是降低模块间的耦合度,注意:不是消灭!!!

很明显地,三层各对象间的耦合度过高。而降低耦合度有利于实现软件的高可维护性。为了解决对象之间的耦合度过高的问题,提出了IoC的概念,实现对象之间的解耦,从而降低耦合度(不能消灭)。

二、IoC的概念

IoC是Inversion of Control的缩写,多数书籍翻译成“控制反转”。通过容器去控制对象之间的依赖关系。控制权由应用代码中转到了外部容器,控制权的转移就是反转。

实现Ioc控制反转后,控制权会从主动转为被动。就好比业务逻辑层需要依赖持久层,在最开始的时候是通过New的方式实例化一个对象,这是主动;但是,通过控制反转后,对象将由IoC容器提供,不需要业务逻辑层主动去New,这是被动。

IoC的作用:降序模块间的耦合(依赖关系)

接下来看一个IoC的小例子。

/**
 * 用反射的方式创建Bean对象
 *
 * Bean:在计算机英语中,有可重复组件的意思。
 * Java Bean:用Java语言编写的可重用组件。
 *            Java Bean > 实体类 (业务层,持久层也可以看成Java Bean,因为他们也可以重用)
 */
public class BeanFactory {
    private static Properties props;
    //定义一个Map,称之为容器,用来存放创建的对象,防止类的对象被实例化多次
    private static Map<String,Object> beans;

    //静态代码块,在类加载的时候执行一次
    static {
        props = new Properties();     //不能完全消灭耦合的一个例子,只能降低
        InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            props.load(in);     //加载进来
        } catch (IOException e) {
            throw new ExceptionInInitializerError("读取properties配置失败");
        }
        //实例化容器
        beans = new HashMap<String, Object>();
        Enumeration keys = props.keys();        //把properties配置文件的所有keys提取出来
        while(keys.hasMoreElements()){
            String key = keys.nextElement().toString();
            String beanPath = props.getProperty(key);
            try{
                Object value = Class.forName(beanPath).newInstance();
                beans.put(key,value);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    /**
     * 根据Bean的名称获取
     * @param beanName
     * @return Bean对象
     */
    public static Object getBean(String beanName){
        return beans.get(beanName);
    }
}

在这里Ioc容器指的就是Map集合,在类加载的时候,通过读取配置文件,反射创建出对象,并把他们存入到Ioc容器,再通过getBean()方法从容器中获取对象。

最开始的代码就可以改成:

private IStudentDao studentDao = (StudentDaoImpl) BeanFactory.getBean("studentDao");
private IStudentService studentService = (StudentServiceImpl) BeanFactory.getBean("studentService");

这样一来,就可以很好地实现IoC控制反转(控制权转移,从主动变被动),降低了对象之间的耦合度,提高了软件的可维护性。IoC控制反转是一种设计模式,不仅仅只是Spring框架有,当然也不仅仅Java语言有。

为什么IoC可以实现解耦?我们来看一下下面几张图。

  • 当系统没有解耦时,系统的各对象都是紧密的连接起来的,形成依赖关系。

<center></center>

  • 但是,当我们引入IoC容器后,A、B、C、D这4个对象之间没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IoC容器。

<center></center>

  • 假设我们把IoC容器取开,这时候,A、B、C、D这4个对象之间已经没有了耦合关系,彼此毫无联系。从而实现了解耦的功能。

<center></center>

三、Spring中的IoC

要通过Spring实现Ioc控制反转,首先我们需要先获取Spring的Ioc核心容器。

BeanFactory和ApplicationContext是Spring的两大核心接口,其中ApplicationContext是BeanFactory的子接口。它们都可以当做Spring的容器。

那么问题来了,这两个接口有什么不同呢?

  • ApplicationContext:

适用对象范围:单例对象适用
加载对象策略:立即加载。在构建核心容器时,在读取配置时候,立即通过反射创建对象。

  • BeanFactory:

适用对象范围:多例对象适用
加载对象策略:延迟加载。什么时候根据Id获取对象,什么时候创建对象。

我们比较常用的是AppicationContext接口,那么这个接口常用的实现类有哪些呢?

ClassPathXmlApplicationContext:加载类路径下的配置文件,不在的话加载不了(常用)
FileSystemXmlApplicationContext:可以加载磁盘任意路径下的配置文件(必须有访问权限)
AnnotationConfigApplicationContext:用于读取注解创建容器

//ApplicationContext接口(单例,立即加载)
//1.获取核心容器   常用的是第一种,加载类路径下的
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//ApplicationContext ac = new FileSystemXmlApplicationContext("F:\\bean.xml");
//2.从容器中获取对象  有两种方式,二选一即可
IStudentService studentService = (StudentServiceImpl) ac.getBean("studentService");
IStudentDao studentDao = ac.getBean("studentDao",IStudentDao.class);
//BeanFactory接口(多例,延迟加载)
//1.获取核心容器
Resource resource = new ClassPathResource("bean.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
//2.从容器中获取对象
IStudentService studentService = (StudentServiceImpl) beanFactory.getBean("studentService");
<?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">
        <!--把对象的创建交给spring来管理-->
        <bean id="studentService" class="org.leslie.service.impl.StudentServiceImpl"></bean>
        <bean id="studentDao" class="org.leslie.dao.impl.StudentDaoImpl"></bean>
</beans>

四、Spring对Bean的管理细节

4.1 创建Bean对象的三种方式

  • 使用默认构造函数构建

有带参构造函数时,默认构造函数不能省;如果没有,则可以省。
在Spring配置文件中使用bean标签,只有id和class属性,如果没有默认构造函数,则对象无法创建。

<bean id="studentService" class="org.leslie.service.impl.StudentServiceImpl"></bean>
  • 使用工厂中的非静态方法创建对象-JAR包(实例工厂)
/**
 * 模拟一个非静态方法工厂类(要使用的类存在于jar包中,无法通过修改源码提供默认构造函数)
 */
public class InstanceFactory {
    public IStudentService getStudentService(){
        return new StudentServiceImpl();
    }
}
<bean id="instanceFactory" class="org.leslie.factory.InstanceFactory"></bean>
<bean id="studentService" factory-bean="instanceFactory" factory-method="getStudentService"></bean>
  • 使用工厂中的静态方法创建对象-JAR包(静态工厂)
/**
 * 模拟一个静态方法的工厂类(要使用的类存在于jar包中,无法通过修改源码提供默认构造函数)
 */
public class StaticFactory {
    public static IStudentService getStudentService(){
        return new StudentServiceImpl();
    }
}
<bean id="studentService" class="org.leslie.factory.StaticFactory" factory-method="getStudentService"></bean>

4.2 Bean对象的作用范围

通过Bean标签的scope属性实现,取值有以下几种,其中最常用的是单例的和多例的。

singleton:单例的 (默认值)
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用域集群环境的会话范围(全局会话范围),当不是集群环境,就是session

4.3 Bean对象的生命周期

拿人的生命周期来类比的话,通俗的讲,生命周期指的是出生->活着->死亡大致这三个阶段。在Bean对象中即对象的创建->对象的使用->对象的清除。

Bean对象分为单例对象和多例对象,这两种对象的生命周期是不一样的。

  • 单例对象

出生:当容器创建时,对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象死亡
总结:单例对象的生命周期和容器相同

  • 多例对象

出生:当我们使用对象时,spring框架帮我们创建
活着:对象只要是在使用过程中就一直活着
死亡:当对象长时间不用,且没有别的对象引用时,由Jvm的垃圾回收期回收

在<bean>标签中,init-method和destroy-method属性的值分别对应的是初始化方法和毁灭方法。

<bean id="studentService" class="org.leslie.service.impl.StudentServiceImpl"
      scope="prototype" init-method="init" destroy-method="destory"></bean>

在注解模式中,分别对应的是@PostConstruct@PreDestory

五、DI的概念

DI是Dependency Injection的缩写,即依赖注入。<font color="red">既然IoC是控制反转,那么是什么控制被反转了呢?答案是获得依赖对象的过程被反转了。</font>在最开始,获得依赖对象的方式是主动去New,而经过控制反转后,方式变成了由Ioc容器主动注入。因此,所谓依赖注入,就是IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

IoC和DI有什么关系呢?其实是他们是从<font color="red">不同的角度的描述的同一件事情</font>,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

简单的说:

  • IoC是指把Bean对象交给IoC容器管理。
  • DI是给创建的Bean对象中的成员变量注入值的。
  • DI不能单独存在,需要在IoC的基础上完成操作。(具体为什么,看下面DI的XML配置就知道了)

六、Spring中的DI

6.1 DI能注入的数据类型

8种基本类型和String(整型 byte short int long,浮点型 float double,逻辑型 boolean,字符型 char)

其他Bean类型(由配置文件中或者注释配置过的Bean)

复杂类型/集合类型

6.2 注入的方式

6.2.1 带参构造函数

类中要加上带参构造方法,加了<constructor-arg>标签,就是使用带参构造函数,不同上面创建对象的默认构造函数。

使用的标签:constructor-arg
出现的位置:bean标签的内部
标签的属性:
type:用于指定要输入数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:索引方式,从0开始
name:用于指定构造函数中参数的名字 (常用)
==========以上三个用于指定给构造函数中哪个参数赋值(三选一)===============
value:用于提供八种基本类型和String类型的数据
ref:用于指定其他的bean类型数据,指的是spring中的ioc核心容器中出现过的bean对象
优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据也得提供。

<bean id="studentService" class="org.leslie.service.impl.StudentServiceImpl">
   <constructor-arg name="name" value="test"></constructor-arg>
   <constructor-arg name="age" value="18"></constructor-arg>
   <constructor-arg name="birthday" ref="now"></constructor-arg>  
    <!--value="1970-01-01" 不行,因为这不是八大基本类型和String类-->
</bean>
<bean id="now" class="java.util.Date"></bean>

6.2.2 set方法

类中要加上属性的set方法。

使用的标签:property
出现的位置:bean标签的内部
标签的属性:
name:用于指定注入时所调用的set方法名称 setName() 此时是name
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据,指的是spring中的ioc核心容器中出现过的bean对象
优势:创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:如果某个属性必须有值,是因为获取对象时,有可能set方法没有执行
因为实例化对象是调用的是默认构造函数,set方法才是赋值的。

<bean id="studentService2" class="org.leslie.service.impl.StudentServiceImpl2">
    <property name="name" value="张三"></property>
    <property name="age" value="19"></property>
    <property name="birthday" ref="now"></property>
</bean>

那么问题来了,DI能注入的数据类型有三种,在上面,无论是通过带参构造函数还是set方法,输入的数据类型都只是基本类型和String和其他bean类型。复杂类型/集合该怎么注入呢?

/**
 * 业务层实现类
 */
public class StudentServiceImpl3 implements IStudentService {

    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String,String> myMap;
    private Properties myProps;
    
    //省略setter方法和其他方法
}

在这里我们采用set方法注入,当是复杂类型/集合类型注入时,省略value和ref属性,在标签内加新的标签。

<!--复杂类型的注入/集合类型的注入
 给List结构集合注入的标签有:List,Array,Set
 给Map结构集合注入的标签有:map ,props
 !!!总结:结构相同,标签可以互换!!!
-->
<bean id="studentService3" class="org.leslie.service.impl.StudentServiceImpl3">
 <property name="myStrs">
     <array>
         <value>AAA</value>
         <value>BBB</value>
         <value>CCC</value>
     </array>
 </property>

 <property name="myList">
     <list>
         <value>AAA</value>
         <value>BBB</value>
         <value>CCC</value>
     </list>
 </property>

 <property name="mySet">
     <set>
         <value>AAA</value>
         <value>BBB</value>
         <value>CCC</value>
     </set>
 </property>
 <!--=======================kv对的类型=============================-->
 <property name="myMap">
     <map>
         <entry key="testA" value="aaa"></entry>
         <entry key="testB">
             <value>bbb</value>
         </entry>
     </map>
 </property>

 <property name="myProps">
     <props>
         <prop key="testC">ccc</prop>
         <prop key="testD">ddd</prop>
     </props>
 </property>
</bean>

6.2.3 注解

下面是一个完整版的曾经的XML配置(没包含复杂类型/集合类型)

 <bean id="studentService" class="org.leslie.service.impl.StudentServiceImpl"
      scope="" init-method="" destroy-method="">
          <property name="" value="" /ref=""></property>
 </bean>

从这个配置我们看到了四部分:

  • 用于创建对象的

他们的作用就和在XML配置文件中编写一个<bean></bean>标签实现的功能是一样的

@Component

作用:如果反射创建对象,把对象存入Spring容器

属性:value:指定bean的id,默认值:当前类名首字母小写

下面三个和上面的一样,是别名

@Controller 表现层

@Service 业务层

@Repository 持久层

//给dao类注解方便给service调用
@Repository("studentDao")
public class StudentDaoImpl implements IStudentDao {
    public boolean findStudent(){
        return true;
    }
}
  • 用于注入数据的

和<property></property>标签实现的作用是一样的

@Autowired:

作用:自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可注入成功

如果IOC容器中没有任何bean的类型和要注入的变量类型匹配,则报错

如果Ioc容器中有多个类型匹配,就根据变量名匹配,如果都不匹配就报错(先找value在找key)

出现位置:变量上或者是方法上

细节:在使用注解注入时,set方法就不是必须的了

@Qualifier:

作用:在按照类中注入的基础之上再按照名称注入,他在给类成员注入时不能单独使用。但是在给方法参数注入时可以

属性:value:用于指定注入bean的id

@Resource:

作用:直接按照bean的id注入,可以独立使用

属性:name:用于指定bean的id。

以上三个注入都只能注入其他bean类型的数据,而基本类型和String无法使用。另外,集合类型的注入只能通过XML来实现。

@Value:

作用:注入基本类型和String类型

属性:value:用于指定数据的值

public class StudentServiceImpl implements IStudentService {
    //@Autowired
    //@Qualifier("studentDao")  这个不能单独使用,要和autowired配套
    @Resource(name = "studentDao")
    private IStudentDao studentDao = null;
    //访问修饰符  数据类型   变量名    数据类型
    .........
}
  • 用于改变作用范围的

和<bean></bean>标签中的scope属性一样的

@Scope

作用:用于指定bean的作用范围

属性:value:指定范围的取值。singleton 单例 prototype 多例

@Scope("prototype")              //在类名上注释,默认是单例
public class StudentServiceImpl implements IStudentService {...........}
  • 和生命周期相关的

和<bean></bean>标签中的init-method和destroy-method的属性一样的

@PreDestroy

作用:指定销毁方法

@PostConstruct

作用:指定初始化方法

@PostConstruct
public void init(){
   System.out.println("初始化了");
}

@PreDestroy
public void destroy(){
   System.out.println("灭掉了");
}
Java
访问: 161 次
    Hokori
    February 7th, 2020 at 04:58 am

    icon_mrgreen.png 沙发

      Leslie
      February 7th, 2020 at 03:38 pm

      早噢

添加新评论

icon_biggrin.pngicon_neutral.pngicon_twisted.pngicon_arrow.pngicon_eek.pngicon_smile.pngicon_sad.pngicon_cool.pngicon_evil.pngicon_mrgreen.pngicon_exclaim.pngicon_surprised.pngicon_razz.pngicon_rolleyes.pngicon_wink.pngicon_cry.pngicon_confused.pngicon_lol.pngicon_mad.pngicon_question.pngicon_idea.pngicon_redface.png