IOC其实就是工厂模式+Java的反射机制

反射机制

我们可能之前就有见过反射机制的出现,就是在JDBC中加载数据库驱动时的。Class.forName(“com.mysql.jdbc.Driver”)

JDBC中加载数据库驱动

编写jdbc的工程代码用于分析程序的耦合

程序的编译和运行需要com.mysql.jdbc.Driver驱动 jar包,如果没有驱动包mysql-connector-java,则会报错:程序包com.mysql.jdbc不存在(compilation completed with I error),即编译时遇到一个错误,并不是运行时异常,我们在实际开发过程中,可能会遇到编译期就报错了,这种编译期的报错证明我们这个类的运行在没有依赖类的情况下,是不能正常编译的

修改:Class.forName("com.mysql.jdbc.Driver"),

即使用导包的方式写的。我们把Driver类加载进内存,Driver类一旦加载进内存,放在静态代码块的注册驱动的代码会随着类的加载而自动执行,即driver类一旦加载进内场就会注册驱动。

程序的耦合和解耦的思路分析:

这个类没有mysql的驱动将无法编译,这个特性称为程序的耦合。

耦合:程序的依赖关系。包括类之间的依赖方法之间的依赖

解耦降低程序间的依赖关系

有些是我们可以避免的,有些是我们无法避免的,在编译期就具体依赖某个类或某个jar包是我们应该避免的。导致整个类的独立性就非常的差,故在com.mysql.jdbc.Driver类的依赖关系没有解除之前,整类的独立性是很差的。实际开发中应该做到:编译期不依赖其他类(保证编译时整个类的独立性),运行时才依赖其他类。只有做到这一点,才能解决独立性很差的问题。

我们的写法是:

Class.forName(”com.mysql.jdbc.Driver”,将类名变成字符串,也就是说不再依赖于某个具体的驱动类,这样带来的好处是整个类就可以独立,即编译可以通过,但是因为没有驱动类是无法运行的,会报ClassNotFoundException异常,这个异常是运行时异常。也就是说程序在编译期是没有任何问题的,只是运行是没有具体的驱动类。

工厂模式+反射机制

三层架构解耦:

现在程序中的依赖关系是:service中依赖了一个具体的dao实现类,还有表现层依赖一个service,使用了new关键字的代码如下:

1、 编写业务层接口:

2、 编写业务层实现类,业务层调用持久层

3、 编写持久层接口:

4、 编写持久层实现类:

5、 模拟表现层调用业务层

以上代码虽然可以正常运行,但是业务层调用持久层时使用了new关键字,表现层调用业务层时也使用了new关键字。这种依赖关系有很强的耦合性,使代码的独立性很差。如果把持久层的实现类删除,则会出现编译期错误,那么如何解除依赖关系呢?使用“反射机制+工厂模式”解耦。

反射机制:

读取配置文件中配置的内容,读取全限定类名,反射创建对象。

1、 需要一个配置文件来配置我们要创建的的service和dao对象,配置文件的内容:唯一标识=全限定类名(key=value),配置文件可以是xml,也可以是properties,我们选择properties,因为它在读取的时候相对于xml是比较简单的。

2、 通过读取配置文件中配置的内容,读取全限定类名,反射创建对象

通过读取配置文件来获取要创建的对象的全限定类名(而不是在类中写死,如果以后更换成oracle数据库,以后仍然可能要改代码),然后使用反射来创建对象(依赖一个字符串),而避免使用new关键字创建对象(依赖具体的驱动类)。这样可以减少他们之间的耦合。

工厂模式:

创建一个BeanFactory类(一个创建Bean对象的工厂,就是创建Service和dao对象的工厂):

用工厂模式创建对象:

这不是工厂模式的最终版。代码分析如下:

1、使用静态代码块给properties赋值,

2、获取properties文件的流对象,不要通过new FileInputStream,如下所示:

因为FileInputStream的参数不知道怎么写,不能用src写相对路径,因为web工程一旦部署之后,src没了,相对路径src绝对用不了,也不能写磁盘绝对路径, 即使这个工程可以用,但是不能保证你的工程都有c盘、d盘、e盘、f盘,所有不要采用new FileInputStream的方式,而是采用另外一种方式,即用类加载器的方式获取。如下所示:

3、如果properties文件没有读成功的话,抛出ExceptionInInitializerError错误,没有这个文件,任何对象都创建不出来。

4、读取配置文件得到全限定类名来反射创建对象

对上面使用了new关键字的代码进行改造:

 

现在如果删除AccountDaoImpl,再次运行会报运行时异常ClassNotFoundException,没有这个类运行不了是正常的,但是不会出现编译期错误。

单例对象

以上虽然完成了工厂模式解耦,但是现在的代码仍然有问题。

运行以上代码,结果如下:

打印出了5个对象。此时对象是多例对象

单例对象:从始至终只有一个对象实例。也就是说打印5次只有一个实例,servlet就是一个单例对象。

单例对象与多例对象有什么区别呢?

service中定义一个类成员,并在方法中对类成员i的值进行修改。

 

运行结果如下:

由于每个对象都有一个独立的实例,从而保证了类对象在创建时重新初始化了类中的属性,不管有多少个service对象来访问的时候,由于对象都是新创建的,所以这个值不会有变化。

但是如果这个对象是单例的对象,这种情况下,对象只被创建了一次,从而类中的成员只会初始化一次,在这种前提下,除了第一次获取一个正常的1之外,后面的4次都是改变后的值。所以说在多个线程访问或多个对象访问且对象的实例只有一个,这时候你要操作类成员的话,类成员在方法中能够进行改变的时候,单例对象有线程安全问题

之前讲servlet的时候,讲过尽量不要定义servlet的类成员。

多例对象没有线程安全问题,但是多例对象由于对象被创建了多次,执行效率没有单例对象高单例对象虽然有线程问题,但是之前讲过的代码并没有在方法中修改类成员的值,在service和dao中是没有的,所有单例对象如果没有可以改变的类成员,则是没有线程问题的

将变量定义到方法中,由于每次方法都会重新初始化,也能保证每次得到的都是1.从这点分析,我们并不需要多例对象。

将多例对象改造为单例对象:

但是由于BeanFactory在创建对象的时候使用了newInstance()方法,

表明了每次都会调用默认构造函数创建对象,所以每次都是一个新的对象,通过刚才的分析,显然我们并不需要这么多对象,我们需要的是每次是同一个对象就够了。从这一点出发,我们要对这个方法进行调整,调整的前提是我们的对象只能newInstance()一次,我们可以创建完了之后存起来。因为由于java的垃圾回收机制,对象就会在长时间不用时被回收,当下次再用的时候肯定就没有了。如果下次再用的时候再用newInstance()创建,那么就用不着单例对象了。现在只能用一次newInstance(),对象创建完后应该马上存起来,定义一个Map,用于存放我们要创建的对象,我们把它称之为容器

枚举类型的遍历:(用hasMoreElements()nextElement()方法)

工厂模式解耦的升级版(最终版):

静态代码块只在类加载的时候执行一次,通过Bean的名称获取对象,这个对象已经是单例的了

多个对象但是只有一个实例

由于循环打印了5次,在只有一个实例的情况下,对这个值进行了反复的操作,

多个线程访问或多个对象访问且对象的实例只有一个时,类成员会由于第一个对象的修改,后面看到的都是改过的数据.出现了线程安全问题.所以尽量不要定义类成员,而是定义到方法中成为局部变量,一旦定义到方法中去之后,这个问题就随着消失了.

 

这时结果都为1了.

单例对象类中没有可以改变的类成员,则没有线程安全问题.所以我们的对象可以是单例对象,单例对象的效果更好。

 

posted on 2021-04-04 21:00  周文豪  阅读(563)  评论(0编辑  收藏  举报