什么是IOC
今天开始学习Spring了,先接触一下IOC和AOP,OK,又是不懂的词,先看看维基百科的解释:
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫「依赖查找」(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递(注入)给它。技术描述:Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式的new一个B的对象。采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。
well,如果在我没学习耦合和解耦之前,这个解释我肯定会给个白眼,不过在今天的我看来,似乎有点能理解了(还是想打个黑人问号,依赖注入什么鬼???),继续深入学习一下
把我上篇博文的案例代码扒过来,这个片段:
public class AccountServiceImpl implements IAccountService {
// private IAccountDao accountDao = new AccountDaoImpl();
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
public void saveAccount() {
accountDao.saveAccount();
}
}
生成accountDao可以分为以下两种截然不同的创建方式,一个通过new方式来加载类,一个通过工厂对象来加载类
// new AccountDaoImpl();
// BeanFactory.getBean("accountDao");
通过new来找对象时,每个当前有new关键字的程序都是主动的,需要自己去找类是否存在,整个程序的对应关系是这样:
而当我们换成工厂模式时,则对象之间的关系变成这样:
此时的应用已经和资源断开联系,变成找工厂要一个资源,由工厂找到资源后在返回给应用,这时就断开了应用对资源的依赖,这种思想,就是我们所说的IOC
上面这个代码片段所在的类,它自己有方法可以加载类,但它却吧这个过程放弃了,让BeanFactory去执行,完完全全交给BeanFactory,自己所有的控制权转交给BeanFactory,这就叫控制反转,这就是IOC的思想。
这带来的好处,可以减低程序间的依赖,也叫削减计算机的耦合
Spring中的IOC
回到上篇文章最初的例子
还是可以运行,不过有很强的依赖性就是了,开始使用Spring
maven配置文件导入依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
待下好jar包,我们就可以写代码了
先在resource下面创建一个XMl文件,就叫bean.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="accountService" class="com.karasawa.service.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.karasawa.dao.AccountDaoImpl"></bean>
</beans>
bean标签的内容是不是很熟悉??是的,跟上次讲过的用propertise文件配置是一样,只不过key变成id,value变成class,但对应的值都一样的,
在servlet中,修改代码:
public class TestSaveAccount extends HttpServlet {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService ias = ac.getBean("accountService", IAccountService.class);
IAccountDao iac = (IAccountDao) ac.getBean("accountDao");
System.out.println(ias);
System.out.println(iac);
}
}
运行后可以看到控制台打印了两个首地址,成功拿到对应的类,感觉很熟悉啊,跟上篇博文中的方法一样嘛,是的,Spring把解析配置文件、获取对应的类、放进Map里等操作全做了,我们就不用去做了
这里拿了两个类,是为了提及一嘴获取类的时候有两种方式,getBean接收两个参数,第一个参数是配置的id,而第二个参数可以指定对应文件的字节流来让生成对应的类,这样就不用去强转了,推荐这种
这里用了AppLicationcontext这个类,没听过更别说见过,来看一下这个这个类,这类的三个常用实现类是:
CLassPathxmLAppLicationcontext
它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。
FileSystemXmlAppLicationContext
它可以加载磁盘任意路径下的配置文件(必须有访问权限)
AnnotationConfigAppLicationContext
它是用于读取注解创建容器的
我们来用用第二个,把bean.xml放到桌面去,修改servlet中的代码
public class TestSaveAccount extends HttpServlet {
public static void main(String[] args) {
ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\karasawa\\Desktop\\bean.xml")
IAccountDao iac = (IAccountDao) ac.getBean("accountDao");
System.out.println(iac);
}
}
运行,成功运行,不过这个路径问题...想想就知道这个FileSystemXmlApplicationContext很少用
在IDEA下把光标移到AppLicationcontext这个关键词上,右键进入show Diagrams
这家伙既是BeanFactory的孙子,用BeanFactory写一个相同效果的
public static void main(String[] args) {
Resource resource = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(resource);
IAccountDao iac = (IAccountDao) factory.getBean("accountDao");
System.out.println(iac);
}
成功运行,那爷孙俩有啥不同呢?
ApplicationContext:它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。
BeanFactory:则构建核心容器时,创建对象采取的策略是采用延迟加载的方式。即什么时候要用再去创建
可以自己实验一下,实验方式为:在beans标签下的任意类中的代码里添加一个无参构造函数,随便打印点啥,在servlet除了获取核心容器对象,其他注释上,运行的时候就会打印出来了,没有打印是BeanFactory,因为没有用到对应的类
.是不是觉得很熟悉 ? ?
ApplicationContext不就是我上篇博文最后的模样嘛,也就是单例模式,增加执行效率,但有线程问题,怪不得都说要理解耦合和解耦再去学spring,那什么时候用BeanFactory什么时候用ApplicationContext,就看你是单例模式还是多例模式了,另外,BeanFactory是顶层接口,功能啥的肯定没有ApplicationContext多,实际开发还是用ApplicationContext较多。Spring可以通过配置文件,让ApplicationContext在创建的时候采用单例还有多例。嗯???不对劲,那还要BeanFactory干啥,像极了孙子拆了老爷子的台hhhh....
Spring对Bean的管理细节
让我们把Dao去掉,留下Service和Servlet
通过三个方面来说Spring对Bean的管理细节
创建bean的三种方式
第一种:上面举例的就是,且没有其他属性和标签时。必须要有默认构造函数,没有的话会报错,像Service类改一下像这样,没有了默认构造函数,运行Servlet后会报错:
package com.karasawa.service;
public class AccountServiceImpl implements IAccountService {
public AccountServiceImpl(String name){
System.out.println(name);
}
public void saveAccount() {
System.out.println("AccountServiceImpl被调用了");
}
}
第二种方式:当我们使用别人的工厂类且该类没有默认构造函数(jar包形式),无法修改的情况下,用这种方式(例如我们要调用一个工厂类中某个方法来创建对象,此时该工厂类没有默认构造函数):
<Bean id="instanceFactory" class="该工厂类的全限定类名"></Bean>
<Bean id="accountService" factory-bean="instanceFactory" factory-method="工厂类中要调用的方法"></Bean>
第三种的情况和第二种类似,不同的是要调用的方法是一个静态方法,对此应该这样用:
<Bean id="accountService" class="该工厂类的全限定类名" factory-method="工厂类中要调用的方法"></Bean>
bean对象的作用范围
ApplicationContext默认创建的Beans对象是一个单例的,但可以通过scope属性来控制
scope属性:用于指定bean的作用范围
取值:
- singleton:单例的(默认值)
- prototype:多例的e
- request:作用于web应用的请求范围session:作用于web应用的会话范围
- g1obal-session:作用于集群环境的会话范围(全局e会话范围),当不是集群环境时,它就是session
常用的是1和2,但开发中具体还是得看情况
bean对象的生命周期
当scope值为singleton是,生命周期和容器一样,当容器创建时创建,随容器销毁时销毁,而当值为prototype时,bean的生命周期等到用到时才创建,销毁时需要自行close,或者长时间没用到(包括没被引用)时,由java的垃圾回收机制进行回收。
依赖注入
依赖注入,英文名Dependency Injection,前面说过Spring的IOC是减低耦合,耦合不能消除,依赖关系还是会存在,用了Spring之后,依赖关系就交给Spring来维护了
通俗点讲,以后我们在一个类中要到其他类、变量、集合等,直接由Spring来提供,嘛,在这之前还需先配置好文件的说明,这种方式呢,就叫做依赖注入!
依赖注入能注入的数据有三类(将在注入方式体现出来):
- 基本类型和String
- 其他bean类型(在配置文件中或者注解配置过的bean)
- 复杂类型/集合类型
注入的方式:有三种
使用构造函数提供
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
五个属性:点击查看
public class AccountServiceImpl implements IAccountService { private String name; private Integer age; private Date brithDay; public AccountServiceImpl(String name, Integer age, Date brithDay) { this.name = name; this.age = age; this.brithDay = brithDay; } public void saveAccount() { System.out.println("我是"+name+",出生于"+brithDay+"今年"+age+"岁"); } }
运行Servlet,查看控制台:<bean id="accountService" class="com.karasawa.service.AccountServiceImpl"> <constructor-arg name="age" value="21"></constructor-arg> <constructor-arg name="name" value="karasawa"></constructor-arg> <constructor-arg name="brithDay" ref="Date"></constructor-arg> </bean> <bean id="Date" class="java.util.Date"></bean>
注意!!这里只是拿name,age,birthDay来测试,实际中这种经常改变的变量是不适用于注入的,再者,使用这种方式会必须得提供参数(因为改变的默认构造函数),参数缺一不可,不然会报错,这也导致了我们有些时候只是要调用改类中的一个方法而已,就多了传参这个固定步骤,开发的时候除了必须采用这种方式,否则不会用到,更多是用以下方法
使用set方法提供
代码依旧是上一步的代码,我们改一下,去掉service中的含参构造函数,生成每个成员的setter,然后修改一下bean.xml文件,把constructor-arg标签都换成propertise标签,属性照样用,运行,发现效果一样,把方法setName改成setUserName,发现propertise的name值需要改成userName才行,所以这里的name值是对应的setter后面带的名字
set方法解决了第一种的弊端,但也产生了新的弊端,如果某个成员必须要有值,那么获取对象时有可能不会执行set方法(因为调用了默认构造函数),两种方式各有利弊,但开发中的绝大多数场景的都是使用set方式
使用注解提供:看下面的Spring IOC注解模块
上面无论是构造函数提供,还是set方法提供,都只用到了基本类型和String、其他Bean类型,这里记录一下复杂类型的注入:
修改一下Service中的代码,删掉name、age、brithDay和对应的setter,写上Map、List、String[]、Set、Propertise等类型的参数和对应的setter
public class AccountServiceImpl implements IAccountService {
private Map<String,String> myMap;
private Properties myProps;
private List<String> myList;
private Set<String> mySet;
private String[] myStr;
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyStr(String[] myStr) {
this.myStr = myStr;
}
@Override
public void saveAccount() {
System.out.println(Arrays.toString(myStr));
System.out.println(myList);
System.out.println(myMap);
System.out.println(myProps);
System.out.println(mySet);
}
}
然后再修改一下bean.xml配置文件
<bean id="accountService" class="com.karasawa.service.AccountServiceImpl">
<property name="myList">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="myStr">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="mySet">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="karasawa" value="he is fucking handsome"/>
</map>
</property>
<property name="myProps">
<props>
<prop key="karasawa">she is fucking beautiful</prop>
</props>
</property>
</bean>
运行后成功打印出注入的数据
偷偷告诉记一个知识点,集合类型的注入,其实就两种,一种是只记录值的list、array、set,另一种是记录key=value的propertise和map,也就是说他们配置的时候,只要结构一样,写乱标签也是可以!!!举个例子:
配置里的props标签和map标签互换一下(name不用改),运行后发现效果还是一样
`constructor-arg的五个属性,点击返回 | 属性 | 作用 | | ----- | --------------------------------------------------------------------------------------------- | | type | 用于指定要注入的数据的数据类型 | | index | 要注入的数据在构造函数中指定索引位置的参数赋值,从0开始 | | name | 用于指定构造函数的参数名字赋值 | | value | 用于提供基本类型和String类型的数据 | | ref | 用于指定其他的bean类型数据(非基本类型时需要先用Bean标签把其类型创建放入核心容器,再用ref引用) |
.type、index、name都是给构造函数的参数赋值,三个选一个就行了,通常我们命名不会重复,所以使用name会比较准确,开发中比较常用
Spring IOC注解
注解是IOC是另一种方式,学起来很容易懂,就像servlet可以用xml配置访问路径,也可以在servlet类中用WebServlet直接写,来起到和xml配置一样的效果
当初在学servlet的时候我就喜欢用WebServlet,学Spring IOC注解的时候,不用问也还是会更喜欢注解方式,不过据说每个公司的方式都不一样,所以两种方式都掌握起来比较好,上面用到的都是XML,下面开始IOC注解的学习记录
基本的注解分四类(非纯注解),对应着XML配置中的< Bean >标签、< propertise >标签、Scope属性、Bean标签的生命周期,比较简单就不过多的讲述了
注解 | 作用 |
---|---|
@Component | 和Bean标签一样,有value属性,形同Bean中的id,默认值为类名(首字母小写) |
@Controller | 同Component,用于表现层,Spring提供名称让三层对象更加清晰 |
@Service | 同Component,用于业务层 |
@Repository | 同Component,用于持久层 |
@Autowrited | 依赖注入,自动按照类型注入,需要匹配上核心容器里已存在的类型,否则报错,当容器存在多个一样的类型Bean时,则按变量名来匹配 |
@Qualifier | 依赖注入,在按照类注入的基础上加上变量名注入,即在Autowrited的基础上使用,在给类成员注入时不能单独使用 |
@Resource | 依赖注入,直接按照所给的name属性值(即变量名)去核心容器找匹配的 ,以上三个依赖注入,只能注入其他Bean类型的数据 |
@Value | 依赖注入,用于基本类型和String的依赖注入,value属性用spring的EL表达式(${}),另外,集合的注入的必须用XML方式 |
@Scope | 用于指定Bean的作用范围,属性value所取的值有singleton和prototype,不用此注解,默认是单例 |
@PreDestroy | 指定销毁方法,类似Bean标签中的destroy-method |
@PostContruct | 指定初始化方法,类似Bean标签中的init-method |
以上就是Spring的IOC内容了。。。其实注解还有好多,但是看一下就懂得怎么使用了,没写例子是因为功能和XML类似,自己上网搜一搜注解的使用方法就知道怎么用了,懒得记录....