工厂模式
在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属性中的方法名的。