荐 Spring Ioc概念简单理解及DI一部分设置
文章目录
说明
因为是个人复习java的总结,所以结构稍显杂乱,有些语句过于口语化.
Spring
其实就是一个分层的框架,主要就是Ioc反转控制技术和Aop
面向切面编程两个内核技术.然后提供了像Spring MVC
面向表现层的技术和Spring JDBC
面向持久层的技术
Ioc实现原理的基本理解
在了解Ioc控制反转之前,需要了解为什么要时使用Ioc以及Ioc的作用.
Ioc其实主要就是用来削减代码的耦合性,那么什么叫做代码的耦合性,那就是代码和代码之间的关联性太强,关联性太强就会导致代码不易进行修改或者是更改配置.如果之前有看过我使用Servlet写个人网页的时候的思考,就会知道这个问题之前一直也困扰着我.像表现层和业务层,业务层和持久层.这些之间的联系太过紧密,因为之前都是直接通过在上一个层面创建下一个层面的实例,然后调用方法的.这就注定了两者之间形成了不可或缺的关系.
如果自行解耦的话,一般就是像持久层中对于数据库连接解耦的操作一样.通过一个类去读取配置文件,然后使用反射返回需要的对象.这样做就能减少两者之间的依赖关系,之后如果想要修改,完全可以修改配置的信息,使上层调用不同的下层对象,从而达到不同的功能.
当然这里具体的实现,当然可以采用工厂模式,因为最终目的是生成一个下层的对象,但是又期望能够减少上下之间的关联.
但是需要注意,这里做到的只是减少关联,完全消除关联是不存在的,两者不存在关联,那么就是独立的两个,根本不需要考虑两者之间的关系.
Ioc原理简单的实例
这里其实主要的就是创建服务层和持久层对象的工厂类,但是没必要细究整体的思路,感受一下解耦上的思路,其实写着有一点偏题,变到设计模式去了.之后会再研究Spring的源码.
工厂类
public class BeanFactory {
//创建Propertis流对象,其实这里可以采用xml的解析方式配置,但是自己实现简单一点,感受一下,使用了properties
private static Properties properties;
//单例,初始化存放所有的对象,有一点点浪费
private static Map<String,Object> beans;
static {
properties = new Properties();
InputStream in = BeanFactory.class.getClassLoader().getSystemResourceAsStream("bean.properties");
try {
properties.load(in);
} catch (IOException e) {
throw new ExceptionInInitializerError("Bean.properties初始化失败");
}
beans = new HashMap<String, Object>();
Set<Object> keys = properties.keySet();
for (Object key : keys){
String k = key.toString();
Object v = null;
try {
v = Class.forName(properties.getProperty(k)).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
beans.put(k,v);
}
}
//修改之后获取的是同一个资源,但是这种实现方式还是存在问题,应该还可以再解耦
public static Object getBean(String accountDao) {
return beans.get(accountDao);
}
// //修改之前的获取实例的方法,会产生多例
// public static Object getBean(String accountDao) {
// String beanPath = properties.getProperty(accountDao);
// Object bean = null;
// try {
// bean = Class.forName(beanPath).newInstance();
// } catch (ClassNotFoundException e) {
// e.printStackTrace();
// } catch (IllegalAccessException e) {
// e.printStackTrace();
// } catch (InstantiationException e) {
// e.printStackTrace();
// }
// return bean;
// }
}
表现层
public class Client {
public static void main(String[] args) {
//这里就是模拟多线程访问服务层,如果不对工厂设置单例,那么这里就会形成多例对象,调用的资源也各自独立
//但是使用工厂模式,那配合上单例资源是很常见的情况,那么这时候就需要对工厂类进行一些补充
for(int i=0; i<5; i++){
AccountService service = (AccountService) BeanFactory.getBean("accountService");
System.out.println(service);
service.saveAccount();
}
}
}
服务层
public class AccountServiceImpl implements AccountService {
// //之前采用的就是这种方式,直接在业务层创建持久层的对象,然后调用方法.
// private AccountDao dao = new AccountDaoImpl();
private AccountDao dao = (AccountDao) BeanFactory.getBean("accountDao");
private int i=0;
public void saveAccount() {
dao.saveAccount();
System.out.println(i);
i++;
}
}
bean.properties
accountService=cjlu.cct.survice.impl.AccountServiceImpl
accountDao=cjlu.cct.dao.impl.AccountDaoImpl
上面总的来说其实就是理解Ioc控制反转,将控制具体对象创建的权力从上层的移到了工厂或者是框架,而工厂通过配置和反射将设置暴露给外部.从而实现了上层和下层的解耦.
简单的使用一下spring中的Ioc
其实使用跟之前Mybatis差不多,首先就是需要使用maven配置spring,然后写一个配置xml文件,使用限定头如下:
<?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="accountDao" class="cjlu.cct.dao.impl.AccountDaoImpl"></bean>
<bean id="accountService" class="cjlu.cct.survice.impl.AccountServiceImpl"></bean>
</beans>
上面主要配置了方法层和持久层的对象和id的对应关系,为什么这样写,其实spring的思路和上面我们提到的使用mapper初始化所有需要使用到的资源的思路差不多.这里就是设置对应关系,然后就是读取配置文件信息,然后获取对应的对象,比如说下面:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
AccountDao accountDao = applicationContext.getBean("accountDao",AccountDao.class);
Spring Ioc实现的容器
其实从上面的使用来看,最主要的就是ApplicationContext
这个用来获取对象的容器。其实Spring Ioc
还有一种容器BeanFactory
。
区别一下这两个容器,前者是之后开发主要使用的容器,其实前者是后者的一个子类,具有更加完善的功能,默认情况下在加载容器的时候就创建了配置的资源对象。
后者其实是Spring框架内部使用的一种容器,在使用对象的时候才会去初始化对象资源。
虽然看起来前者的方式会浪费一些资源,但是从整体上来说,在用户使用资源前创建好资源,而不是在使用的时候去临时消耗时间创建,更加符合用户的需求。
ApplicationContext
首先这个接口的整体继承结构是这样的,可以看出其顶层为三个就很像我们之前的思路,读取配置文件,通过工厂进行创建对象,然后还有一个功能的注解,目前还不明确是什么用的.
现在主要的问题是对于ApplicationContext
的使用,明显我们不能直接使用这个接口,应该使用其实现类.有一点需要先明确,这个类使用来加载配置根据id获取对象的核心容器,所以其实现类应该是不同的读取配置方式生成对象,其实现类常用的为三种:
-
ClassPathXmlApplicationContext
其实从名字可以有一点想象,就是加载类目录下的配置文件,我们上面使用的就是这种方式,所以配置文件存放在resources下. -
FileSystemXmlApplicationContext
这个类中而是加载磁盘路径下的配置文件,但是需要注意磁盘路径的访问权限 -
AnnotationConfigApplicationContext
用于加载注解配置.
了解完容器之后,需要着重了解的肯定是对于容器中创建的Bean对象的管理问题。
基于xml的Bean对象创建的三种方式
- 使用无参的构造函数创建Bean对象,其实就是上面使用的配置方式,
<bean id="accountDao" class="cjlu.cct.dao.impl.AccountDaoImpl"></bean>
- 使用指定的类的指定方法获取对象,常见的其实就是工厂类创建对象,因为这时候一般代码都被封装到了jar包中,我们想要修改代码使用第一种方式也不太可能.
<bean id="accountDaoFactory" class="cjlu.cct.factory.AccountDaoFactory"></bean>
<bean id="accountDao" factory-bean="accountDaoFactory" factory-method="getAccountDao"></bean>
- 使用某个类的静态方法创建对象,其实如果理解了xml的配置,那么很容易明白上面的案例中实际上是通过实例化工厂类对象,然后再通过这个对象去调用方法创建真正的对象.但是工厂类也有可能是通过静态方法创建对象,那么配置中就不需要去实例化工厂类的对象了.
<bean id="accountDao" class="cjlu.cct.factory.AccountDaoFactory" factory-method="getAccountDao"></bean>
其实总结来说,就是在xml中使用<bean>
标签并且添加属性,从而配置Bean对象的创建。
Bean的作用范围
其实之前我们自己考虑解耦的时候也考虑到Ioc方式获取的对象不应该每次都创建新的,当时是通过map来初始化了所有对象,从而保证单例.但是这样做明显过于简单的.Spring中对这部分进行了考虑,表现形式就是Bean对象的作用域,在哪个范围内的对象会使得共享同一个对象资源.
使用需要修改<bean>
中的scope属性,主要取值如下:
singleton
单例的模式,也是默认模式,对所有范围都是单例的
prototype
多例的模式,每次都会创建新的对象
request
看到这个,很容易联想到就是相对于web中的request域,一次请求会保证相同的对象资源
session
对应于web中的session,一次会话范围的对象资源
global-session
针对的是集群环境下的会话,但是集群环境之前都还没有接触到过.其实就是针对多台物理设备运行同一个服务的时候,当多次访问这个服务的时候,很可能出现访问的不是同一个物理设备,那就存在前面存储session
的数据却读不出来的额问题,global-session
就是解决这个问题,实际上一般都是通过额外的物理设备去存储数据,使用软件实现不太实惠.这个问题有一个名称就做负载均衡.
Bean对象的生命周期
单例的时候,创建的对象随容器而初始化,随容器销毁而销毁.其实比较好理解,这里就像是之前自己写的简单案例一样,在创建容器的时候初始化对象,那么这个对象因为被容器引用着,所以一直存在,但是随着容器销毁,对象也会销毁.
多例的时候,这时候就不一样,是随着对象被调用到的时候创建对象,而销毁对象是在对象长时间不使用销毁.也就是被垃圾回收机制给回收.
依赖注入
其实就是针对上面通过Ioc管理对象的时候,对象肯定不可能都是单纯的没有依赖的对象。如果管理存在依赖的对象的时候,不可能全都有无参的构造器去调用。那么就需要依赖注入来实现管理的对象的依赖的注入。或者说是给对象注入依赖属性。
依赖注入一般分为两种方式注入:
- 使用set方法进行注入
使用的话肯定首先对应对象需要有setter方法,然后就在xml的对应<bean>
中添加<property>
进行属性的注入。例子如下:
<bean id="person" class="cjlu.cct.domain.Person">
<property name="name" value="tom"></property>
</bean>
- 使用有参构造器注入
很明显,先得有有参构造器。然后再<bean>
中添加<constructor-arg>
标签,这样就会根据配置的构造器传入参数注入属性。如下:
<bean>
<constructor-arg name="name" value="tom"></constructor-arg>
<constructor-arg name="address" value="home"></constructor-arg>
</bean>
p名称空间注入
其实就是使用p名称空间去简化地配置属性,概念和c中地内容有点像。使用的时候首先需要再限制头中添加p空间的限制
xmlns:p="http://www.springframework.org/schema/p"
然后就可以直接再<bean>
的配置中,使用p属性,来添加属性注入。实例如下:
<bean id="person" class="cjlu.cct.domain.Person" p:name="tom"
p:address="home"></bean>
注入其他属性
其实上面的内容,存在一些问题,就是目前涉及到的都是能够直接传入的数据,但是注入的属性中肯定会涉及到集合,具有依赖的对象。
注入字面量
其实这就是我们上面使用到的注入属性,这里需要注意两个点:
- 如果需要注入null的话,使用
<null/>
标签,嵌套在<property>
中实现。 - 如果需要注入存在特殊字符的字面量的话,可以使用html转译字符来实现,当然稍微麻烦了一点。还可以使用xml中的CDATA来实现,具体示例如下:
<property name="name">
<value><![CDATA[ 带有特殊符号的内容 ]]></value>
</property>
注入外部Bean对象
其实就是基于<bean>
标签是用来创建控制的对象的,那么如果在这个对象中的属性存在依赖其他对象的时候。那么肯定也需要创建外部的对象注入当前对象。
如果注入的对象是当前Bean外部创建的对象,就是外部Bean对象注入。简单点说就是在xml的配置中表现为两个相同级别的<bean>
,在其中一个<bean>
引用到了另外一个<bean>
,实例如下:
<bean id="personService" class="cjlu.cct.service.PersonService">
<property name="personDao" ref="personDaoImpl"></property>
</bean>
<bean id="personDaoImpl" class="cjlu.cct.dao.impl.PersonDaoImpl"></bean>
就是创建了持久层的对象,而在服务层创建对象的时候使用到了这个持久层对象。因为持久层对象不是在服务层对象内部创建的,所以是注入外部Bean对象。
内部Bean和级联的注入
其实相当于外部Bean对象,在<bean>
标签中创建的<bean>
对象,其实本质上和外部Bean区别不大,只是内部Bean不用写id,因为是服务于某个对象的对象。
区别一下外部Bean,外部Bean是可以被重复调用查找到的,因为相当于是创建的一个资源。
级联方式,其实本质上是基于外部Bean注入,就是先设置一个属性为外部对象引用,再设置这个属性对象中的具体属性。概念说着复杂,实例很好理解:
<bean id="emp" class="cjlu.cct.domain.Emp">
<property name="name" value="jack"></property>
<property name="dept" ref="dept"></property>
<property name="dept.name" value="sale"></property>
</bean>
<bean id="dept" class="cjlu.cct.domain.Dept">
<property name="name" value="technology"></property>
</bean>
感觉实际操作中意义不大。而且需要注意一点,使用级联的时候,对于下面那条设置具体属性的配置需要在实体类中存在getter方法的情况下使用。其实也好理解,不获取怎么判断赋值。
注入集合属性
其实很简单,主要就是几类集合,数组,list,map,set。首先当然需要在实体类中设置。
然后配置中主要使用一下几个标签<array>
,<list>
,<map>
,<set>
配套<value>
或者<entry>
标签配置。具体例子如下:
<properties name="maps">
<map>
<entry key="" value=""></entry>
</map>
</properties>
<properties name="maps">
<list>
<value>需要设置的内容</value>
</list>
</properties>
写两个差不多,配置没有什么大的区别,但是对于需要注入对象的属性,存在一点区别,就是使用的是标签作为参数。
但是上面这种方式其实非常繁琐,需要创建很多外部对象,然后还要一个个传到对应的对象中去。有一种简化方式,使用util命名空间
util命名空间
首先需要添加限制头
xmlns:util="http://www.springframework.org/schema/util"
另外需要修改
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/uitl
http://www.springframework.org/schema/uitl/spring-uitl.xsd">
其实util相当于是能够帮助管理多个需要注入的属性形成一个类似集合的存在,从而对应需要注入属性中的集合。主要是增加了代码的复用。实例如下:
<util:list id="bookList">
<ref bean=""></ref>
<value>需要设置的内容</value>
</util:list>
<bean>
<property name="list" ref="bookList"></property>
</bean>
如有错误欢迎读者批评指正!!
本文地址:https://blog.csdn.net/weixin_43913376/article/details/107370202
下一篇: Python30之文件2(文件系统)