前言:去年到现在一直没有很好的时间完成这个spring基础+源码的博客目标,去年一年比较懒吧,所以今年我希望我的知识可以分享给正在奋斗中的互联网开发人员,以及未来想往架构师上走的道友们我们一起进步,从一个互联网职场小白到一个沪漂湿人,一路让我知道分享是一件多么重要的事情,总之不对的地方,多多指出,我们一起徜徉代码的海洋!
我这里做每个章节去说的前提,不是一定很标准的套用一些官方名词,目的是为了让大家可以理解更加清楚,如果形容的不恰当,可以留言出来,万分感激
1、反转(转移)控制(Inverse of Control)
所谓反转控制,就是我们常说的IOC,大学老师经常告诉我们,ioc就是反转控制,就是控制反转,一头雾水你也听不明白,听我说来
我们在没有spring的时候,对于对象的创建,我们是在代码里做这个事情的,new 对象,既然我们需要让spring来托管对象,我们引出下面的概念
- 控制:对于成员变量赋值的控制权
- 反转控制:把对于成员变量赋值的控制权,从代码中反转(转移)到Spring⼯⼚和配置⽂件中完成
- 好处:解耦合
- 底层实现:工厂设计模式
我画了一张图,就可以描述很清楚
对于成员变量赋值的控制权==Spring配置文件+Spring工厂(ApplicationContext)
2、依赖注入
依赖注入是Spring的编程方式,简称DI(Dependency Injection)
- 注入:通过Spring的⼯⼚及配置⽂件,为对象(bean,组件)的成员变量赋值
- 依赖:当⼀个类需要另⼀个类时,就意味着依赖,⼀旦出现依赖,就可以把另⼀个类作为本
类的成员变量,最终通过Spring配置⽂件进⾏注⼊(赋值)。我的UserService是需要访问数据库对吧,所以我必须要有数据库的操作UserDao,没他不行,非他不可!这就叫做依赖!
我需要你的时候,等于我就要依赖你,所以我们就可以按照依赖注入的想法,当然你要提供set方法,我就可以把你注入进来,把你做为我的成员变量,进而我们可以通过Spring配置文件,注入进来!
所以面试不会问这个概念,而属于一种Spring的思想。
好处:解耦合!
3、Spring工厂创建复杂对象
3.1、什么是简单对象
指的是可以直接通过new构造方法,来创建对象,反射底层,也是调用构造方法。
3.2、复杂对象
不能直接通过new创建,比如Connection,SqlSessionFactory对象,这种对象不能直接new,创建过程比较复杂。
所以我们希望Spring既能帮我们创建简单对象,也可以帮我们创建复杂对象,这样Spring是不是就可以帮我们创建一切对象了?
3.3、Spring工厂创建复杂对象的三种方式
既然知道Spring要创建复杂对象,那么他有几种方式呢,下面我们来认识下:
- FactoryBean接口
1、开发步骤
实现FactoryBean接口
getObject:用于书写创建复杂对象的代码,并把复杂对象作为方法的返回值返回出去
类似哪些需要在这里创建呢,比如Connection,SqlSessionFactory
getObjectType:同理上面创建什么样的复杂对象,就返回什么样的复杂对象对应的Class对象
isSingleton:如果这个复杂对象只需要创建一次,返回true;如果这个复杂对象每一次调用都需要创建一个新的对象,返回false;
public class ConnectionFactoryBean implements FactoryBean { /** * 用于书写创建复杂对象的代码,并把复杂对象作为方法的返回值返回出去 */ @Override public Object getObject() throws Exception { return null; } /** * 返回所创建复杂对象的Class对象 * @return */ @Override public Class<?> getObjectType() { return null; } /** * 如果这个复杂对象只需要创建一次的话,返回true * 如果这个复杂对象每一次调用都需要创建一个新的对象,返回false * @return */ @Override public boolean isSingleton() { return false; } }
所以Spring把这个复杂对象的创建过程交给了用户去自定义复杂对象!
public class ConnectionFactoryBean implements FactoryBean<Connection> { /** * 用于书写创建复杂对象的代码,并把复杂对象作为方法的返回值返回出去 */ @Override public Connection getObject() throws Exception { Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/users?useSSL=false","root","root"); return connection; } /** * 返回所创建复杂对象的Class对象 * @return */ @Override public Class<?> getObjectType() { return Connection.class; } /** * 如果这个复杂对象只需要创建一次的话,返回true * 如果这个复杂对象每一次调用都需要创建一个新的对象,返回false * @return */ @Override public boolean isSingleton() { return false; } }
applicationContext.xml配置文件如下
<!--如果Class中指定的类型 是FactoryBean接⼝的实现类,那么通过id值获得的是 这个类所创建的复杂对象Connection--> <bean id="conn" class="com.chenxin.spring5.factorybean.ConnectionFactoryBean"> </bean>
看看结果对不对
conn = com.mysql.jdbc.JDBC4Connection@481a15ff
思考下,咦为毛是Connection对象,我不是应该是获取ConnectionFactoryBean对象吗?
想想我们要得到的结果是不是Connection对象?所以Spring在这个实现FactoryBean接口上,就发生了变化,此时你再通过工厂创建对象,Spring就不会按照你以为的那样,给你一个ConnectionFactoryBean对象,而是真正给你一个你要创建的复杂对象!
细节:
1、如果就想获得FactoryBean类型的对象 ctx.getBean("&conn") 获得就是ConnectionFactoryBean对象
2、isSingleton⽅法 返回 true-只会创建⼀个复杂对象,返回false--每⼀次都会创建新的对象
问题:用户需要根据这个对象的特点 ,决定是返回true还是false ,(SqlSessionFactory返回true比较合理,因为是重量级对象,只用一个即可,不存在Connection的问题), (Connection也是一样,是不是每次都需要创建连接对象,连接对象是不能被公用的,因为有可能会有事务的操作,比如,有个conn1,第一个客户t1,t1开启了事务,开始运行,此时第二个用户t2也拿到连接conn1,也开启了事务,如果t1执行的快,直接提交了,那么t2就会被影响,所以对吧!)
3、mysql⾼版本连接创建时,需要制定SSL证书,解决问题的⽅式
4、依赖注入的体会:对于URL,username,password,是不是在ConnectionFactoryBean中是非常重要的,是需要依赖这些字符串的对吧,既然是依赖,我就可以作为我的成员变量,通过Spring的配置文件来完成注入,赋值,所以我们可以修改下代码
public class ConnectionFactoryBean implements FactoryBean<Connection> { private String driverClassName; private String url; private String username; private String password; public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } public void setUrl(String url) { this.url = url; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } /** * 用于书写创建复杂对象的代码,并把复杂对象作为方法的返回值返回出去 */ @Override public Connection getObject() throws Exception { Class.forName(driverClassName); Connection connection = DriverManager.getConnection(url, username, password); return connection; } /** * 返回所创建复杂对象的Class对象 * * @return */ @Override public Class<?> getObjectType() { return Connection.class; } /** * 如果这个复杂对象只需要创建一次的话,返回true * 如果这个复杂对象每一次调用都需要创建一个新的对象,返回false * * @return */ @Override public boolean isSingleton() { return false; } }
配置文件改为
<bean id="conn" class="com.chenxin.spring5.factorybean.ConnectionFactoryBean"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/users?useSSL=false"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean>
这样结果是不是一样的?所以这里还是体会了依赖注入的思想!解耦合。
相信到这很多人会有疑问,我们来分析下
1、为什么Spring规定FactoryBean接口,实现,并且要把复杂对象的创建代码写在getObject里?
实际上有个东西叫做接口回调!任何开源框架内,反射+接口回调,什么都能做!
说明下,我们在学java基础的时候学过instanceof判断两个对象是否属于同一个类型的,Spring在拿到bean标签id=conn的时候,会判断当前这个ConnectionFactoryBean这个类是不是FactoryBean的子类,如果不是的话,就说明是简单对象,Spring直接帮我们new就可以了;如果是的话,那么你肯定就要实现三个方法,getObject,getObjectType和isSingleton,既然你实现了,Spring就能找到getObject方法,Spring就能调用这个方法,就可以获取到这个连接对象Connection;于是就可以返回出去。有人肯定会问,你怎么知道是Spring调用的,很明显你要是自己调用,你肯定要写new ConnectionFactoryBean(),然后自己去getObject方法,但是你知道你并没有这么做,所以是Spring按照约定来调用的!
总结:Spring中⽤于创建复杂对象的⼀种⽅式,也是Spring原⽣提供的,后续讲解Spring整合其他框架,也是⼤量应⽤FactoryBean;
- 实例工厂
为什么有了FactoryBean,Spring还要整个实例工厂呢,原因以下几点
1、避免框架的侵入
如果选择Spring框架进行复杂对象开发的话,我们要实现FactoryBean接口,如果哪天我离开了Spring框架呢,那么之前实现FactoryBean接口的代码就没法用了
实际上FactoryBean接口输入Spring框架的侵入
2、整合遗留系统:比如我现有公司已经有了一个ConnectionFactory,里面有个getConnection方法已经可以帮我们获取Connection对象了,给我们的是class文件,我们只能拿到class文件
那么我们没法通过改源码的方式来进行创建复杂对象,所以我们有了实例工厂
遗留代码比如:
public class ConnectionFactory { public Connection getConnection(){ Connection conn = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/users?useSSL=false", "root", "root"); }catch (Exception e){ e.printStackTrace(); } return conn; } }
类和工厂实际上以前公司代码已经有了, 我们只需要去解决配置文件的问题
先把这个ConnectionFactory对象由Spring创建出来,进而要创建connection连接对象的时候,再通过Spring来关联这个ConnectionFactory工厂,调用getConnection方法,来得到connection对象
<!-- 先交给Spring工厂创建这个工厂的对象 --> <bean id="connectionFactory" class="com.chenxin.spring5.factorybean.ConnectionFactory"> </bean> <!-- 然后把之前的ConnectionFactory和Spring整合进来,让Spring帮忙创建这个conn对象 --> <bean id="conn" factory-bean="connectionFactory" factory-method="getConnection"></bean>
- 静态工厂
作为静态工厂,和实例工厂的区别,体现在这个getConnection方法是不是静态的
public class StaticConnectionFactory { public static Connection getConnection(){ Connection conn = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/users?useSSL=false", "root", "root"); }catch (Exception e){ e.printStackTrace(); } return conn; } }
之前我们通过new ConnectionFactory().getConnection()得到对象,这里就是直接StaticConnectionFactory.getConnection()即可
所以配置文件这么写就可以了
<bean id="connectionFactory" class="com.chenxin.spring5.factorybean.StaticConnectionFactory" factory-method="getConnection"/>
三种方式我这边就讲解完了!
4、Spring创建工厂总结
Spring即可以创建简单对象,也可以创建复杂对象,目的都是为了解耦!如果看了我前面两集讲解的Spring,这张图你一定会很清楚!
下一节我们来分析Spring创建工厂的次数是如何控制的,敬请期待!