Spring入门笔记(一)

Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益!

Spring简述

Spring的一个最大的目的就是使JAVA EE开发更加容易。同时,Spring之所以与Struts、Hibernate等单层框架不同,是因为Spring致力于提供一个以统一的、高效的方式构造整个应用,并且可以将单层框架以最佳的组合揉和在一起建立一个连贯的体系。可以说Spring是一个提供了更完善开发环境的一个框架,可以为POJO(Plain Old Java Object)对象提供企业级的服务

在Spring刚出生的时候,主要的负责两件事情:IOC、AOP IOC(Inversion of Control),即“控制反转”,为什么需要控制反转?谁控制谁?为啥叫做反转?IOC究竟是做什么的? AOP 以后再说,先从IOC容器开始,下图是Spring的几大模块: mark Spring DAO:Spring提供了对JDBC的操作支持:JdbcTemplate模板工具类 。

Spring ORM:Spring可以与ORM框架整合。例如Spring整合Hibernate框架,其中Spring还提供HibernateDaoSupport工具类,简化了Hibernate的操作 。

Spring WEB:Spring提供了对Struts、Springmvc的支持,支持WEB开发。与此同时Spring自身也提供了基于MVC的解决方案 。

Spring AOP:Spring提供面向切面的编程,可以给某一层提供事务管理,例如在Service层添加事物控制 。

Spring JEE:J2EE开发规范的支持,例如EJB 。

Spring Core:提供IOC容器对象的创建和处理依赖对象关系 。

Spring的helloworld

Spring的环境搭建

首先需要说明的是:Spring不仅仅是JavaEE才能用的框架,而是SE同样适用的,所以为了方便起见,我直接新建Java工程来完成Spring的HelloWorld,但是Spring需要的jar包,所以我直接使用Maven工程来演示,假设你不用maven或者想直接使用普通的Java工程来完成Spring的HelloWorld,那么 点击这里 在这里可以下载到Spring核心包的源码,Jar包以及说明文档。

eclipse如何集成Spring相关的工具包在此不再赘述,我直接使用的是Spring官方集成的工具,也是eclipse,但是你可以叫做STS, 下载地址在这里

 1<project xmlns="http://maven.apache.org/POM/4.0.0"
 2	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 4	<modelVersion>4.0.0</modelVersion>
 5	<groupId>com.xpu.maven</groupId>
 6	<artifactId>SpringDemo_01</artifactId>
 7	<version>0.0.1-SNAPSHOT</version>
 8
 9	<dependencies>
10		<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
11		<dependency>
12			<groupId>org.springframework</groupId>
13			<artifactId>spring-core</artifactId>
14			<version>5.0.6.RELEASE</version>
15		</dependency>
16		<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
17		<dependency>
18			<groupId>org.springframework</groupId>
19			<artifactId>spring-context</artifactId>
20			<version>5.0.6.RELEASE</version>
21		</dependency>
22
23		<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
24		<dependency>
25			<groupId>commons-logging</groupId>
26			<artifactId>commons-logging</artifactId>
27			<version>1.1.1</version>
28		</dependency>
29	</dependencies>
30</project>

以上是Spring的HelloWorld所需的Jar包,接下来解释一下这些Jar的作用: mark spring-core是Spring的核心工具包 spring-cji是核心包所需的依赖包, spring-aop:Spring的面向切面编程,提供AOP(面向切面编程)的实现 spring-beans:Spring IOC的基础实现,包含访问配置文件、创建和管理bean等 spring-context:在基础IOC功能上提供扩展服务,此外还提供许多企业级服务的支持,有邮件服务、任务调度、JNDI定位,EJB集成、远程访问、缓存以及多种视图层框架的支持 spring-expression:spring表达式语言,就像EL表达式一样的东西 commons-logging:这个是只是一个日志包,不用理会

Spring的HelloWorld

现有一个JavaBean是Students类:

 1package com.xpu.bean;
 2public class Students {
 3	private int stuNo;
 4	private String stuName;
 5	private int stuAge;
 6	public Students() {
 7		super();
 8	}
 9	public Students(int stuNo, String stuName, int stuAge) {
10		super();
11		this.stuNo = stuNo;
12		this.stuName = stuName;
13		this.stuAge = stuAge;
14	}
15	public int getStuNo() {...}
16	public void setStuNo(int stuNo) {...}
17	public String getStuName() {...}
18	public void setStuName(String stuName) {...}
19	public int getStuAge() {...}
20	public void setStuAge(int stuAge) {...}
21	@Override
22	public String toString() {...}
23}

如果我们需要拿到这个Students类的对象的话其实很简单:

1Students stu = new Students(1, "Tim", 20);

接下来我们使用IOC的方式来获取到Students的对象,在src(如果是maven工程的话直接在resource下新建即可)下新建一个applicationContext.xml文件,如果你已经安装了插件或者直接使用STS的话,会自动生成一些内容: mark

1<!-- 该文件中产生的所有对象,被Spring放入了一个称之为springIOC容器的地方 -->
2<!-- id:唯一标识符 class:JavaBean的class对象 -->
3<bean class="com.xpu.bean.Students" id="students">
4	<property name="stuNo" value="1"></property>
5	<property name="stuName" value="Tim"></property>
6	<property name="stuAge" value="20"></property>
7</bean>

在测试类中写如下代码:

1ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
2Students stu = (Students) ac.getBean("students");

这样得到的Students对象与自己直接new的对象是一样的,都是Students的对象,但是这样可能看不出Spring框架的作用到底在哪里,怎么反倒变得复杂了?

从HelloWorld解释IOC

IOC 发展史

假设有一个课程接口Cource,分别有两个实现类:

 1public interface ICource {
 2	void learn();
 3}
 4
 5class JavaCource implements ICource{
 6	@Override
 7	public void learn() {
 8		System.out.println("学习Java");
 9	}
10}
11
12public class HtmlCource implements ICource{
13	@Override
14	public void learn() {
15		System.out.println("学习HTML");
16	}
17}

接下来有一个学生类和简单工厂类:

 1public class CourceFactory {
 2	public static ICource getCource(String type) {
 3		if("HtmlCource".equals(type)) {
 4			return new HtmlCource();
 5		}else{
 6			return new JavaCource();
 7		}
 8	}
 9}
10
11public void learn(String str) {
12		//通过工厂的方式获取对象
13		ICource cource = CourceFactory.getCource(str);
14		cource.learn();
15}

这样就实现了new操作解耦合,但是本质上还是没变:因为对象还是由客户端创造的,而且这个简单工厂的局限性非常强,只能生产Cource类的对象,如何让这个工厂更强悍呢?或者说如何从根本上解决对象的管理(生产)呢?这个时候Spring诞生了,我们只需要在applicationContext.xml中配置两个具体实例就行了:

1<bean id="HtmlCource" class="com.xpu.history.HtmlCource"></bean>
2<bean id="JavaCource" class="com.xpu.history.JavaCource"></bean>

这样的话学生类的learn可以这样写:

1public void learn(String str) {
2	//通过Spring的IOC容器来获取课程对象
3	ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
4	((ICource)context.getBean(str)).learn();
5}

很多人看到这个HelloWorld可能觉得并没有什么深刻的理解,接下来听我详细到来,首先我们要获得Students对象,最开始是我们自己new的Students对象,但是后来我们拿到的对象是是直接从配置文件里定义的对象,我们暂且把这个配置文件理解成IOC容器,IOC为什么叫做"控制反转",IOC不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

用图例说明一下,传统程序设计和IOC设计,都是主动去创建相关对象然后再组合起来: mark IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

IoC很好的体现了面向对象设计法则之一就是好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。这一段摘自: 《谈谈对Spring IOC的理解》

IOC与DI

IOC在被改名之前叫做控制反转,但是改名之后叫做依赖注入,改名之后 似乎更加好理解了一点,不过有一句话还是经典:控制反转是目的、依赖注入是手段,它提供一种机制,将需要依赖(低层模块)对象的引用传递给被依赖(高层模块)对象,下面我们用一个示例来体现依赖注入,注入就不再解释,在上述例子中你可以理解为把对应的元素值注入到基础属性中,再把基础属性注入到对象中,于是你获得了xml文件中配置的对象,那么什么是依赖呢?

依赖注入底层是根据反射来设计的!

假设现在有一个Teacher类:

 1public class Teacher {
 2	private String teaName;
 3	private int teaAge;
 4	public String getTeaName() {...}
 5	public void setTeaName(String teaName) {...}
 6	public int getTeaAge() {...}
 7	public void setTeaAge(int teaAge) {...}
 8	public Teacher() {...}
 9	public Teacher(String teaName, int teaAge) {...}
10	@Override
11	public String toString() {...}
12}

又有一个课程类,很显然课程是依赖于老师的,所以设计如下:

 1public class Cource {
 2	private String courseName;
 3	private int courseHour;
 4	private Teacher teacher;//授课老师
 5	public Cource() { }
 6	public Cource(String courseName, int courseHour, Teacher teacher) {...}
 7	public String getCourseName() {...}
 8	public void setCourseName(String courseName) {...}
 9	public int getCourseHour() {...}
10	public void setCourseHour(int courseHour) {...}
11	public Teacher getTeacher() {...}
12	public void setTeacher(Teacher teacher) {...}
13	@Override
14	public String toString() {...}
15}

在配置文件中应该这样写:

 1<bean id="Teacher" class="com.xpu.ioc.Teacher">
 2	<property name="teaName" value="Tim"></property>
 3	<property name="teaAge" value="35"></property>
 4</bean>
 5
 6<bean id="Cource" class="com.xpu.ioc.Cource">
 7	<property name="courseName" value="Java"></property>
 8	<property name="courseHour" value="2"></property> 
 9	<property name="teacher" ref="Teacher"></property>
10</bean>

这样便完成了依赖,Cource类依赖于Teacher类,所以我们在配置文件中配置ref属性为Teacher,在通过IOC容器拿到Cource类的对象的时候便会自动生成Teacher类的对象,完成Cource类对象的依赖对象!Spring的依赖注入主要有四中方式:set注入方式、静态工厂注入方式、构造方法注入方式、基于注解的方式:

 1<bean id="Cource" class="com.xpu.ioc.Cource>
 2	<!-- 使用set方法赋值 -->
 3	<property name="courseName" value="Java"></property> 
 4	<property name="courseHour" value="2"></property>
 5	<property name="teacher" ref="Teacher"></property>
 6	
 7	<!-- 通过构造方法赋值(name、type、index等是可选项) --> 
 8	<constructor-arg value="JavaScript" name="courseName"></constructor-arg>
 9	<constructor-arg value="3" type="int"></constructor-arg> 	<constructor-arg ref="Teacher"></constructor-arg>
10</bean>

通过构造方法赋值,如果与构造方法的参数顺序不一致,则要通过type或者index或name指定,通过命名空间注入,引入P命名空间:

1xmlns:p="http://www.springframework.org/schema/p"
1<bean id="Teacher" class="com.xpu.ioc.Teacher" p:teaName="Tom" p:teaAge="23">
2<bean id="Cource" class="com.xpu.ioc.Cource" p:courseName="C++" p:courseHour="5" p:teacher-ref="Teacher">

示例:注入各种集合类:

 1package com.xpu.collection;
 2
 3import java.util.Arrays;
 4import java.util.List;
 5import java.util.Map;
 6import java.util.Properties;
 7import java.util.Set;
 8
 9public class AllCollectionType {
10	private List<String> listElement;
11	private String[] arrayElement;
12	private Set<String> setElement;
13	private Map<String, String> mapElement;
14	private Properties propsElement;
15	public List<String> getListElement() {
16		return listElement;
17	}
18	public void setListElement(List<String> listElement) {
19		this.listElement = listElement;
20	}
21	public String[] getArrayElement() {
22		return arrayElement;
23	}
24	public void setArrayElement(String[] arrayElement) {
25		this.arrayElement = arrayElement;
26	}
27	public Set<String> getSetElement() {
28		return setElement;
29	}
30	public void setSetElement(Set<String> setElement) {
31		this.setElement = setElement;
32	}
33	public Map<String, String> getMapElement() {
34		return mapElement;
35	}
36	public void setMapElement(Map<String, String> mapElement) {
37		this.mapElement = mapElement;
38	}
39	public Properties getPropsElement() {
40		return propsElement;
41	}
42	public void setPropsElement(Properties propsElement) {
43		this.propsElement = propsElement;
44	}
45	@Override
46	public String toString() {
47		return "AllCollectionType [listElement=" + listElement + "\narrayElement=" + Arrays.toString(arrayElement)
48				+ "\nsetElement=" + setElement + "\nmapElement=" + mapElement + "\npropsElement=" + propsElement + "]";
49	}
50}
51

set、list、数组、都有自己的标签,但是list可以混用

 1<!-- 复杂类型的赋值的写法 -->
 2<bean id="collection" class="com.xpu.collection.AllCollectionType">
 3	<property name="listElement">
 4		<list>
 5			<value>足球</value>
 6			<value>排球</value>
 7		</list>
 8	</property>
 9	<property name="arrayElement">
10		<array>
11			<value>111</value>
12			<value>222</value>
13		</array>
14	</property>
15	<property name="setElement">
16		<set>
17			<value>足球</value>
18			<value>排球</value>
19		</set>
20	</property>
21	<property name="mapElement">
22		<map>
23			<entry>
24				<key>
25					<value>foot</value>
26				</key>
27				<value>足球</value>
28			</entry>
29			<entry>
30				<key>
31					<value>basket</value>
32				</key>
33				<value>篮球</value>
34			</entry>
35			<entry>
36				<key>
37					<value>hand</value>
38				</key>
39				<value>排球</value>
40			</entry>
41		</map>
42	</property>
43	<property name="propsElement">
44		<props>
45			<prop key="foot">足球</prop>
46			<prop key="hand">排球</prop>
47		</props>
48	</property>
49</bean>

value=""方式与<value></value>方式的区别:

使用子元素<value>注入 使用value属性注入
参数值位置 写在首尾标签<value></value>之间,不加双引号 写在value属性中的至必须加上双引号
type属性 有(可选),可以通过type指定数据类型 无,但是在构造注入的时候可以使用type
参数值包含的特殊字符 两种处理方式:1、使用<![CDATA{ }]>标记;2、使用XML预定义的实体引用 一种处理方式:使用XML预定义的实体引用

给属性赋空值需要如下写法:

1<!-- 赋值为null -->
2<property name="name"> </null> </property>
3<!-- 赋值为"" -->
4<property name="name"> <value></value> </property>

在IOC容器定义bean,必须定义无参构造

IOC自动装配(只适用于引用类型,基本类型无法装配),这种方式必须满足:在当前的IOC容器中只能有一个Bean是满足要求的:

1<!-- Course类中有一个ref属性,属性名为teacher,并且该IOC容器中恰好有一个id也是teacher -->
2<bean id="Cource" class="com.xpu.ioc.Cource" autowire="byName">
3    <property name="courseName" value="Java"> </property>
4    <property name="courseHour" value="5"></property>
5</bean>

byName:byName是自动寻找其他JavaBean的id值与该类的ref属性名一致的

byType:上面autowire的属性可以是byType,byType会自动寻找其他JavaBean的类型与该类的ref属性类型一致的

constructor:constructor会看其他的JavaBean是否与该类的构造方法参数的类型一致

在根标签设置所有类的自动装配方式:

1default-autowire="byName"

但是字标签的优先级更高,所以灵活度很大,接下来说说自动装配的缺点: 可读性差,容易出错,不建议经常使用

使用注解定义Bean:通过注解的方式将Bean以及相应的属性值放入IOC容器

1//<bean id="StudentDaoImpl" class="com.xpu.dao.StudentDaoImpl"></bean>
2@Component("StudentDaoImpl")
3public class StudentDaoImpl { ... } 

在配置文件中配置:

1<!-- 先加入context -->
2xmlns:context="http://www.springframework.org/schema/context"
3<!-- 配置扫描器,多个包使用","隔开 -->
4<context:component-scan base-package="com.xpu.dao"></context:component-scan>

在启动的时候,扫描器会扫描在配置中的包,而且含有@Component注解的类,如果有则将Bean加入到IOC容器中

@Component的细化,在不知道使用那一层的情况下就用@Component注解:

使用场景 注解
dao层 @Repository
service层 @Service
控制器层(MVC) @Controller

结语

如果文中有错误,希望大家能够及时指出,我的邮箱是: zouchanglin@stu.xpu.cn ,对了,今天是除夕,晒晒家里的年夜饭吧: mark