黑马Spring的4天教程--第一天:工厂模式、单例对象的线程安全问题、单例工厂模式、Spring负责创建业务层和持久层对象、BeanFactory接口下的孙子接口特点、Bean对象作用范围和生命周期

工厂模式

在ui的包中,调用service包中的类,进而调用dao包中的类。

实体层domain包中,是User类的属性和set、get、和toString等方法,有些书上叫做POJO

持久层dao包中,是一个简单的save方法,这里并没有使用到实体类User(为了简单考虑,所以实体类User这里是摆设,DAO层并没有持有User对象,只是为了概念结构完整)

业务层service包中,持有一个dao层的对象作为成员变量,并在自身的业务方法中,直接调用dao层对象的方法。

表现层ui包中,是一个Test类,直接调用service包的对象。

——————————————————————————

接下来试图尽量减少耦合。目的是当new的类的全限定名改变之后,不在编译期报错(也就是不用重新编译,也就不用重新部署,不可能每次修改代码,再重新编译)。

思路是使用工厂模式。把要new的类的全限定名,放在配置文件bean.properties里,在java代码中只写配置文件的路径。

那么如果需要获取的类改变,只需要改配置文件,而不需要修改java代码重新编译。工厂类代码BeanFactory

这里不抛出,而是捕获处理异常,原因是service层是基于接口的,接口如果不定义详细异常,就无法在service层里去处理调用该类后抛出的异常。

那么对应dao层和service层的代码也要修改:

这里因为dao层代码中没有new,所以不用改。下面是service层的改动,ui层不改也可以运行。当然也可以改UI层的代码。

单例对象的线程安全问题

类的成员变量,如果在单例模式下,堆中仅有一个对象,那么当多线程(多ui层用户)去进行修改堆中成员变量值时,就需要考虑线程安全问题,增加难度。

所以,尽量不要把(允许修改的)带值的变量放在类的成员变量中,而应该把这种变量放在方法中,在方法中的变量,为每个线程所独立拥有,不需要考虑线程安全问题。

单例工厂模式

我们上面的例子中BeanFactory工厂类,每次调用其中的getBean(String  str)方法,都会new一次,产生一个新的对象。

如果要求无论调用getBean方法多少次,仅仅需要产生一个对象实例(就像Servlet那样),那可以对该类进行改造。

思路是:在该类的静态代码块中,创建一个map<String,Object>,其中每个条目,String代表properties配置文件中的key,Object代表该key对应的全限定类名的对象。

在该类加载时,就把配置文件中所有的类的全限定名都找到,然后创建一个对象,作为一个条目放入map容器中。

然后修改getBean(String  str)方法的代码,查找对应的String  key,找到容器中的对象来作为返回值。

单例工厂模式,只有一个对象,所以如果在工厂中定义了类的成员变量,并提供了对其值进行允许修改的方法时,就要考虑线程安全的问题了。

Spring负责创建业务层和持久层对象

下载spring-framework-5.0.2.RELEASE-dist.zip  压缩包。按照黑马2018年初的Spring5的4天学习视频版本来配置。

压缩包的目录如下:在docs中有文档,libs中有jar包,有21个jar包,每个jar包都有对应的docs的文件和source源码文件,所以libs一共有63个文件。

在dao、service、ui包的同级上新建一个bean.xml的配置文件,将来用XML的方式来装配Spring。 下面是本例的Spring IoC的约束:

<?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="UserDao" class="dao.impl.UserDaoImpl">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="UserService" class="service.impl.UserServiceImpl">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

做这个实验,只需要在工程的libs中放入、并加入build path下面几个jar包就可以,其中common-loggin-1.2.jar是在Apache网站上有下载。

domain实体层的POJO一样是摆设,这里是为了概念上完整。

我们直接在ui层的Test中,进行Spring对象的获取:

选中一个接口,Ctrl+T可以看到其实现类,这里利用ClassPathXmlApplicationContext 来进行XML文件的读取。

dao层代码和service层代码还原到最初的new的状态;

把factory包删除掉,交由Spring 容器创建对象。

这里注意ClassPathXmlApplicationContext 参数的路径,包(文件夹)之间的分隔符是 / ,而不是工厂模式下的点号。

ui层的代码如下:

package ui;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import dao.IUserDao;
import service.IUserService;
import service.impl.UserServiceImpl;

public class Test {

	public static void main(String[] args) {
		//1、获取Spring的核心容器
		ApplicationContext appContext = new ClassPathXmlApplicationContext("config/bean.xml");
		//2、根据严格大小写的XML文件中的bean的id值,来获取对应类的全限定名对象
		//第一种单参数的返回值是Object需要强转;第二种双参数的第二参数是转成目标接口类型的字节码(这里不知道为啥也需要强转,可能是编译器的原因)
		IUserDao userdao = (IUserDao) appContext.getBean("UserDao");
		IUserService userservice = (IUserService) appContext.getBean("UserService",IUserService.class);
		
	    //3、测试是否能透过Spring的核心容器,拿到对象
		userdao.save();
		userservice.saveService();
	}

}

注意,Spring IoC的容器是创建的单例对象,所以就像上面例子中的map容器一样。

BeanFactory接口下的孙子接口特点

我们给ApplicationContext接口导入源码(在Spring的libs里有对应的spring-context-5.0.2.RELEASE-sources.jar源码),就能发现,其最上层接口是BeanFactory

我们继续导入BeanFactory的源码,在spring-beans-5.0.2.RELEASE-sources.jar中,所以ApplicationContext接口,它是BeanFactory接口的孙子接口。

ApplicationContext 接口的特点:

在读取XML配置文件后,文件中所有的录入bean对象都已经在容器中创建出了实例(适合于单例),被称为“立即加载”。

而最上层接口BeanFactory接口,是延迟加载的,在读取完XML配置文件后,并不创建实例,而是在getBean时,才去创建实例(适合于多例)。

ApplicationContext 接口下的两个实现类:

ClassPathXmlApplicationContext  和 FileSystemXmlApplicationContext 的区别。

前者使用的是“类路径“,去加载XML文件。所谓的类路径,指的就是开发时的src路径下,和打包后的classes路径。注意不要于类库lib路径搞混了。

后者使用的是磁盘文件的路径。

Bean对象的三种创建方式(Bean标签的三个属性 class、factory-method、factory-bean):

1、bean.xml文件中的bean标签的class属性,Spring会调用默认的无参构造函数去创建bean对象。其中id是自定义的缩写,class是真实类名;

2、静态工厂的方式去创建bean对象。xml文件中的bean标签的factory-method属性,指定bean对象的方法(不一定要求静态方法),来获取对象,前提需要有一个工厂StaticBeanFactory,其中的方法getBean返回这种对象类型。

xml文件中:注意,这个xml文件就是用于创建对象的,其中  bean标签的  id是自定义缩写(用于ui层的getBean的参数),class代表真实类名,factory-method属性是class类中的方法名(不一定是静态)。

3、通过实例工厂,创建对象。还是需要一个工厂类,其中的非静态方法,返回一个bean对象。

xml文件中:其中  bean标签的  id是自定义缩写,class代表真实工厂类名;

需要另写一个bean标签,其中id是自定义缩写(用于ui层的getBean的参数),factory-method属性是class类中的方法名;factory-bean是实例工厂类对应的bean标签的id值。

三种方法,在ui层中进行创建的语法都差不多。id都是用于在ui层中的getBean中进行获取的参数。下面是ui层的代码,是第三种方式的代码。

bean对象的作用范围

xml文件的bean标签的scope属性值控制,有以下几种:

1、singleton       用于在getBean创建对象时,是单例的。默认值就是该值。

2、prototype      多例。

3、request                 getBean创建的对象,仅在本次请求及请求转发中,可以使用。(JavaWeb工程中有效),相当于是在request域中有效。

4、session                   仅在本次会话中,可以使用。(JavaWeb工程中有效)

5、global-session        分布式的全局session,如果没有负载均衡(portlet技术),等同于session。

Bean对象的生命周期

xml文件的bean标签的init-method属性 和  destory-method属性,与对象的生命周期相关。

单例对象的生命周期

对于单例对象来说,对象的出生是容器( ClassPathXmlApplicationContext 对象)的加载XML文件开始的;容器存在时对象就存在;容器销毁后对象死亡。

比如,在对应的service包的实现类中,配置成员方法init( ) 和 destory( ) 用于执行与生命周期相关的动作。

注意,init-method属性标注的方法名,是在构造方法之后执行的。

而destory-method属性标注的方法名,想触发执行,必须让容器显式的进行销毁,即调用 ClassPathXmlApplicationContext 对象的close方法。如下图:

多例对象的生命周期

service包中的代码不用改变。但是对象创建的时机已经变了,不是在容器加载时进行创建,而是在ui层进行getBean时才进行创建。

出生:每次getBean时,创建一个实例。

活着:对象没有被JVM垃圾机制回收,就一直存在。

死亡:Spring不负责进行销毁多例对象,而是由JVM垃圾回收机制进行销毁。

(JVM垃圾回收机制:当对象A长时间不使用,并且也没有其他对象引用该对象时,就会回收。也就是说,如果有一个对象B引用了A,而B在使用,此时也不会回收A)

所以,多例对象,在ui层进行调用ac.close() 是不会执行  destroy-method属性中的方法名的。

猜你喜欢

转载自blog.csdn.net/qq_26882339/article/details/113157066