我们平时创建对象的方式无非就是以下两种:
有参构造 、无参构造
我们来看看在Spring中怎么处理这两种情况
首先我们先创建一个实体类:
package com.MLXH.pojo; public class User { private String name; private String sex; private int age; public User() { System.out.println("User的无参构造"); } public User(String name) { System.out.println("User的有参构造"); this.name = name; } public User(String name, int age) { System.out.println("User的第二种有参构造"); this.name = name; this.age = age; } public User(String name, String sex, int age) { System.out.println("User的第三种有参构造"); this.name = name; this.sex = sex; this.age = age; } public void setName(String name) { System.out.println(name); this.name = name; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
Spring创建对象的方式:
通过有参构造
- 通过下标
- 通过参数名 【推荐】
- 通过参数类型
通过无参构造
- 默认会用无参构造
注意点:实体类中一定要有一个无参构造方法
接下来我们看一下Spring是如何装配这些对象的:
<?xml version="1.0" encoding="UTF-8"?> <!--suppress SpringFacetInspection --> <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="user" class="com.MLXH.pojo.User"> </bean> <!--无参构造,执行set方法--> <bean id="user1" class="com.MLXH.pojo.User"> <property name="name" value="寒雪1"/> </bean> <!--使用构造器的参数下标进行赋值--> <bean id="user2" class="com.MLXH.pojo.User"> <constructor-arg index="0" value="寒雪2"/> <constructor-arg index="1" value="18"/> </bean> <!--通过名字进行赋值--> <bean id="user3" class="com.MLXH.pojo.User"> <constructor-arg name="name" value="寒雪3"/> <constructor-arg name="age" value="3"/> </bean> <!--通过类型进行赋值--> <bean id="user4" class="com.MLXH.pojo.User"> <constructor-arg type="java.lang.String" value="寒雪4"/> <constructor-arg type="java.lang.Integer" value="18"/> <constructor-arg type="java.lang.String" value="男"/> </bean> </beans>
测试类
package com.MLXH.pojo; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class UserTest { @Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User)context.getBean("user1"); System.out.println(user.toString()); } @Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) context.getBean("user2"); System.out.println(user); } @Test public void test3(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) context.getBean("user3"); System.out.println(user); } }
结果:运行第一个测试类的结果:
分析:我们针对输出的结果进行分析,我们拿到的是user1对象,那么为什么会输出这么多的语句呢?
原因是我们执行了spring的配置文件bean.xml的全部内容,当执行
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
语句时,会加载beans.xml中的全部内容,因此其中所有的装配信息都会进行加载,会按照我们装配的顺序进行加载。
注意:
<bean id="user1" class="com.MLXH.pojo.User"> <property name="name" value="寒雪1"/> </bean>
看似像是执行了有参构造,但其实是执行了无参构造,然后通过set方法将,name赋值进去的…就像是执行了这样的代码:
User user1 = new User(); user1.setName="寒雪1";
这只是执行了Test1的,其他的类似…
SpringIOC——控制反转中创建对象的细节
在刚开始使用Java的时候,创建对象的方式多半是使用 new+对应的构造方法,为了进一步提高对象的安全性和降低耦合,开始使用工厂模式和单例模式,通过调用工厂里的方法获取对象,再后来通过反射加配置文件的方式,在创建对象的过程中进一步降低耦合。
那么SpringIOC里创建对象的过程是怎样的呢?具体来说:SpringIOC是什么时候创建对象的?通过哪种方法创建的对象?创建的对象是单例的还是多例的?创建的对象是由谁来保管和控制的?
这些问题不仅仅是Spring需要考虑的,而且是程序员应该考虑的,我们创建对象的效率和对内存的使用率很大程度上影响到开发代码的优劣性。
接下来,我们就通过Spring创建对象的过程,一步步来讨论一下Spring框架IOC组件部分,创建对象的细节。
一、Spring创建对象的过程(在此之前我们已经配置好了Spring的配置文件)
启动spring容器(根据Spring配置文件不同创建不同的Spring容器)
//启动spring容器(根据Spring配置文件不同创建不同的Spring容器) ApplicationContextcontext = newClassPathXmlApplicationContext( "resources/spring_ioc.xml");
这里实现的步骤如下:
1.读取resources文件下spring_ioc.xml
2.Xml解析spring_ioc.xml
3.把解析xml里的内容存储到内存的map集合中
4.从map集合中读取集合内容,就是<Bean></Bean>清单里的内容
5.遍历集合中的所有数据,并反射实例化对象
Class.forName(“com.spring.ioc.Hello”).newInstance()
从容器中取出对象,以及使用对象
//从spring容器中获取对象hello Hello hello = (Hello)context.getBean("Hello"); //使用hello对象调用sayHello方法 hello.sayHello();
以上关键的两个环节中,需要重点理解的是spring容器为什么能创建保管对象,因为Spring在初始化的时候,就将Bean 里的属性调入放入到一个map集合中区维护了,这个map集合的key=”id”,value=”object” (反射实例化的对象),spring容器在底层就相当于这个map<id,object>,而Bean就被存放在这个map中。
当我们把spring容器启动好以后,也就是context对象创建完成后,通过调用context对象里的getBean()方法来从map中用key寻找value,从而获取对象。
细化分析:启动Spring容器时,将Bean存储到Map集合中,需要调用反射来实例化Bean中封装的类路径,那么xml配置文件中每存储一个不同的Bean,就会实现一次反射,产生一个实例对象,也就是说,Spring容器中产生的对象仅和xml配置文件相关,与取出对象操作无关,Spring可以实例化一个类多次(<Bean/>中id不同,class相同),也可以只实例化一个类一次,但多次调用。单例的创建时间是在Spring容器启动时。
二、创建对象的四种方式
由上我们知道Spring底层是通过反射来创建对象的,那我们在创建类的时候,可以创建实体类,抽象类,接口等等,spring底层反射到底使用什么方法来创建对象呢?这就需要考虑我们是创建什么对象了。
1. 无参构造
反射是通过无参构造来实例化类的,所以,如果我们创建一个实体类,并想将类产生的实例放到spring容器中,必须保证这个类有无参构造。当这个类中未写构造器时,java编译器会在编译时自动添加一个无参构造,但是如果这个类中有含参构造时,只能通过人工添加无参构造来实现反射,这一点在以后的开发设计中要牢记。
2. 静态工厂设计模式
那抽象类怎么实例化呢?抽象类无法直接实例化,只能通过添加静态工厂的方式,在工厂中添加静态方法返回抽象类的getInstance()方法,从而获取实例对象。
这时,我们需要一个抽象类,一个工厂类,而且xml中的配置文件也需要发生变化,<Bean>中class类的加载路径需要指定为工厂类,而且需要指定新的属性为:factory-method=”get抽象类的实例对象方法”。
例:我们需要用util包下的Calendar这个抽象日期类。
静态工厂代码为:
package staticfactory; import java.util.Calendar; public class StaticFactory { public static Calendar getCalendar(){ return Calendar.getInstance(); } }
Xml文件中的Bean应该修改成:
<bean id="cal" class="staticfactory.StaticFactory" factory-method="getCalendar"/>
测试可使用:双击test2方法右键run as》JUnit得到当前电脑的系统时间。
@Test publicvoid test2(){ //启动spring容器 ApplicationContextcontext = newClassPathXmlApplicationContext("staticfactory/applicationContext.xml"); Calendar calendar = (Calendar)context.getBean("cal"); System.out.println(calendar.getTime()); }
无法直接实例化的类就可以使用静态工厂法。
此时Spring中并未创建工厂这个类,而是根据<Bean/>中的factory—method属性,直接调用工厂的getCalendar静态方法,产生的实例只有一个,就是工厂中,getCalendar静态方法通过调用Calendar.getInstance()方法产生的实例。
3. 实例工厂设计模式
静态工厂设计模式不会创建工厂类对象,而是直接调用静态工厂的静态方法。而工厂方法设计模式会创建类,然后通过xml中配置去访问其方法。
例:同样是需要Calendar这个抽象类
package methodfactory; import java.util.Calendar; public class MethodFactory { publicMethodFactory() { System.out.println("如果创建工厂类的实例,必定会打印这句话!"); } publicCalendar getCalendar(){ returnCalendar.getInstance(); } }
Xml中的配置由一个变为两个(这也证明了上述结论,配置文件中<Bean/>的个数影响产生的实例对象的个数)
<bean id="methodFacotry" class="methodfactory.MethodFactory"/> <bean id="cal" factory-bean="methodFacotry" factory-method="getCalendar"/>
测试可使用:双击test2方法右键run as》JUnit得到当前电脑的系统时间。
@Test public void test(){ //启动spring容器 ApplicationContext context = newClassPathXmlApplicationContext("methodfactory/applicationContext.xml"); Calendar calendar = (Calendar)context.getBean("cal"); System.out.println(calendar.getTime()); }
输出结果为上述打印的那句话和系统时间,两个<Bean/>产生两个实例对象。
4. Spring工厂设计模式
这是Spring框架自身提供的工厂,它需要实现FactoryBean接口,实现代码就必须写在getObject()方法中。Spring工厂实现一个接口就可以了,简单方便。
例:
创建SpringFactory类实现FactoryBean接口,定义泛型<Calendar>
package springfactory; import java.util.Calendar; import org.springframework.beans.factory.FactoryBean; public class SpringFactory implements FactoryBean<Calendar>{ public SpringFactory() { System.out.println("我是一个spring工厂类"); } public Calendar getObject()throws Exception { return Calendar.getInstance(); } public Class<?> getObjectType() { return Calendar.class; } public boolean isSingleton() { return false; } }
配置xml文件中的Bean元素:
<bean id="cal"class="springfactory.SpringFactory"/>
测试可使用:双击test3方法右键run as》JUnit得到当前电脑的系统时间。
@Test public void test3(){ //启动spring容器 ApplicationContext context = newClassPathXmlApplicationContext("springfactory/applicationContext.xml"); Calendar calendar = (Calendar)context.getBean("cal"); System.out.println(calendar.getTime()); }
三、Spring创建对象是单例还是多例?+懒加载
单例就是指在在Spring容器中的对象只有一个,每次从spring容器中取出不会产生新的对象。
多例就是每次从Spring容器中取出对象的时候会产生新的对象,内存中存在多个对象。
默认情况下,spring中创建的对象都是单例,并且维护其生命周期。单例对象的生命周期与spring容器共命运,同生共死。
但如果对象是多例的,那么Spring容器只负责对象的创建,不负责维护其生命周期,也就是说如果容器关闭,对象并未销毁,需要用户自行关闭。
具体实现代码是在Bean标签中插入属性
<bean id="Hello" class="com.spring.ioc.Hello"scope="prototype"/>
Scope属性代表了spring创建对象是单例还是多例。
那么单例多例各有什么优缺点呢?
单例的好处就是,spring创建后,那就不会再频繁创建,缓存在map中,省内存。坏处,对象里的数据不安全,即线程不安全
多例的好处就是随时用随时创建,线程安全,缺点是占用内存。
单例多例各有各的特点,思考一个问题,如果Spring容器中要使用很多次单例,那么单例是不是丧失了它的优势呢?即使没有业务调用这些对象,这些对象依然在spring容器加载的时候产生,不仅仅浪费了容器的容量,还延长了加载时间,这时候我们就需要用懒加载,堆单例模式进行改造了。
首先我们可以在全局配置(第一个<Bean/>)中添加default-lazy-init="true",这样就默认使用懒加载。
或者在单例的配置中添加:
<bean id="Hello"class="com.spring.ioc.Hello" scope="singleton"lazy-init="true"/>
另外,多例模式都是懒加载,当多例模式的懒加载被设置为false时,会报错!
四、对象的创建和保管(初始化+销毁)
Spring 容器执行过程
1.new Instance----调用构造方法创建对象
2.Init-method------执行初始化方法
3.对象调用方法。
4.容器关闭,执行销毁方法 ,如果scope=”property”时让其不负责对象的销毁
总结
通过对Spring内对象产生的细节进行探究,了解Spring底层创建实例的方式,通过熟练使用这些方式,掌握对Spring框
架的理解,这样对程序员的自我成长是很好的~
以上为个人经验,希望能给大家一个参考,也希望大家多多支持自学编程网。
- 本文固定链接: https://zxbcw.cn/post/215407/
- 转载请注明:必须在正文中标注并保留原文链接
- QQ群: PHP高手阵营官方总群(344148542)
- QQ群: Yii2.0开发(304864863)