目录
一、Spring简介
Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核。
提供了展现层 SpringMVC 和持久层 Spring JDBCTemplate 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架。
1.1Spring的优点
Spring框架具有以下几个优势:
1、方便解耦,简化开发
通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度耦合。(简单来说就是通过IoC统一创建对象)
用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
2、支持AOP编程
通过 Spring的 AOP 功能,方便进行面向切面编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松实现。
3、支持声明式事务
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务管理,提高开发效率和质量。
4、方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
5、方便继承各种优秀框架
Spring对各种优秀框架(Struts、Hibernate、Mybatis、Hessian、Quartz等)的支持
6、降低JavaEE API的使用难度
Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
7、Java源码是经典学习范例
Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java 设计模式灵活运用以及对 Java技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。
1.2Spring体系结构
Spring的体系结构可以用下图表示:
我们在学习过程中是从下往上学习,先学底层,然后是上层应用。所以我们先要学习核心容器Core Container。
二、Spring快速入门
2.1Spring程序开发步骤
传统的项目开发是先写Dao层的方法,然后在业务逻辑层Service里创建Dao对象,调用相关方法。
UserDao userDao = new UserDaoImpl();
但是Spring将对象的创建权限放在了框架内,如果想要获得对象,不用自己new,其步骤是:
- 实现Dao层的对象中的具体方法
- 在xml配置文件中把Dao对象关联到一个id标识上
- Spring读取xml配置文件,根据id标识获得Bean全限定名称
- Spring通过反射机制创建对象,然后将对象返回
从上图可知,Spring开发的基本步骤如下:
- 导入Spring开发的基本包坐标
- 编写 Dao 接口和实现类
- 创建 Spring 核心配置文件
- 在 Spring 配置文件中配置 UserDaoImpl
- 使用 Spring 的 API 获得 Bean 实例
2.2Spring快速入门
2.2.1创建Maven项目并导入Spring包
我们首先创建一个Spring文件夹,用IDEA打开该文件夹,打开后右键Spring新建一个Module,
左边选择maven项目,不点击使用模板创建,直接点击next,设置好公司和项目名,
然后后面直接点击next和finish创建好项目。现在创建的项目没有webapp,我们要进入项目设置Project Structure,设置SDK的一些参数,
然后我们左边选择Facets项,点击+号,找到Web点击,选择spring_ioc进行添加,
然后修改web模块的路径,
然后我们在pom.xml中导入spring框架用的jar包,
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
</dependencies>
至此项目创建完成。
2.2.2编写Dao接口和实现类
我们创建一个Dao的接口和简单实现类,
interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("saving.........");
}
}
2.2.3创建Spring核心配置文件并配置DaoImpl
我们在src\main\resources目录下创建一个xml文件applicationContext.xml,
我们在xml中配置dao的实现类id,
<?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="userDao" class="Dao.UserDaoImpl"></bean>
</beans>
2.2.4获取Bean实例
我们新建一个Demo类,用来获取UserDao对象,
package Demo;
import Dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserDaoDemo {
public static void main(String[] args) {
ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");//获取ApplicationContext对象
UserDao userDao = (UserDao) app.getBean("userDao");//获取UserDao的实例化对象
userDao.save();
}
}
运行结果:
三、Spring配置文件
3.1Bean标签基本配置
我们可以通过反射来获取对象,而反射是通过类的无参构造来获取对象,所以我们实现的类必须要存在无参构造。
<bean id="userDao" class="Dao.UserDaoImpl"></bean>
基本属性包括:
- id:Bean实例在Spring容器中的唯一标识
- class:Bean的全限定名称
3.2Bean标签范围配置
我们还可以通过scope属性配置bean对象的作用范围:
取值范围 | 说明 |
---|---|
singleton | 默认值,单例的 |
prototype | 多例的 |
request | WEB项目中,Spring创建一个Bean对象,并将对象存储到request域中 |
session | WEB项目中,Spring创建一个Bean对象,并将对象存储到session域中 |
global session | WEB项目中,应用在Portlet环境 如果没有Portlet环境那么globalSession相当于session |
我们将scope分别设置为singleton和prototype,查看得到的两个对象地址是否相同,
public void test1(){
ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");//获取ApplicationContext对象
UserDao userDao1 = (UserDao) app.getBean("userDao");//获取UserDao的实例化对象
UserDao userDao2 = (UserDao) app.getBean("userDao");//获取UserDao的实例化对象
System.out.println(userDao1==userDao2);//查看两个对象是否为同一个地址,即是否为单例模式
}
下图左边是singleton的输出,右边是prototype的输出,
我们可以得到以下结论:
- 当scope的取值为singleton时:
- Bean的实例化个数为1个
- Bean的实例化时机为:当Spring核心配置文件加载时,就实例化了配置的Bean对象
- Bean的生命周期:
- 对象创建:当应用加载,创建容器时,对象就被创建了
- 对象运行:只要容器在,对象就一直存活
- 对象销毁:当应用卸载,销毁容器时,对象就被销毁了
- 当scope的取值为prototype时:
- Bean的实例化个数为多个
- Bean的实例化时机为:当调用getBean()方法时实例化Bean
- Bean的生命周期:
- 对象创建:当使用对象时,创建新的对象实例
- 对象运行:只要对象在使用中,就一直存活
- 对象销毁:当对象长时间不用时,被Java的垃圾回收器回收
3.3Bean生命周期配置
我们可以指定Bean对象的初始化和销毁方法,
- init-method:指定类中的初始化名称
- destory-method:指定类中的销毁方法名称
我们在UserDaoImpl对象中定义初始化和销毁的方法:
public void init(){ System.out.println("UserDaoImpl对象初始化"); }
public void destory(){ System.out.println("UserDaoImpl对象销毁"); }
然后在applicationContext.xml中配置好对应关系:
<bean id="userDao" class="Dao.UserDaoImpl" init-method="init" destroy-method="destory"></bean>
public void test2(){
ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");//获取ApplicationContext对象
UserDao userDao = (UserDao) app.getBean("userDao");//获取UserDao的实例化对象
app.close();//关闭容器对象,此时会执行对象中销毁的方法
}
结果如下:
3.4Bean实例化的三种方式
- 无参构造方法实例化:默认的方法(重要)
- 工厂静态方法实例化
- 工厂实例方法实例化
这里我们演示一下后面两种方法,首先我们要新建一个工厂类StaticFactory,里面定义一个静态方法获取UserDaoImpl对象,
public class StaticFactory {
public static UserDao getUserDao(){
return new UserDaoImpl();
}
}
然后我们修改一下Spring的配置文件,告诉Spring我们要走工厂静态方法实例化对象,
<bean id="userDao" class="Factory.StaticFactory" factory-method="getUserDao"></bean>
后面就和无参构造的方法一样,通过应用容器中查找id获取Bean对象。
接下来我们看看工厂实例方法如何实例化,还是新建一个工厂类DynamicFactory,定义一个非静态方法,获取UserDaoImpl对象,
public class DynamicFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
修改配置文件,
<bean id="factory" class="Factory.DynamicFactory"></bean>
<bean id="userDao" factory-bean="factory" factory-method="getUserDao"></bean>
同样地利用id获取Bean对象。
3.5Bean的依赖注入
假设我们现在要在Service类中调用dao层的方法,我们同样要写一个UserService的接口和UserServiceImpl的实现类,在UserServiceImpl中调用dao层的方法。
package Service;
import Dao.UserDao;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserServiceImpl implements UserService{
public void save() {
ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");//获取ApplicationContext对象
UserDao userDao = (UserDao) app.getBean("userDao");//获取UserDao的实例化对象
userDao.save();//调用dao层的方法
}
}
那我们如何创建UserService类呢,我们同样可以在Spring的配置文件中给UserService类配置id。
<bean id="userDao" class="Dao.UserDaoImpl"></bean>
<bean id="userService" class="Service.UserServiceImpl"></bean>
然后同样通过id获取Bean对象,
package Demo;
import Service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserServiceDemo {
public static void main(String[] args) {
ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");//获取应用
UserService userService = (UserService) app.getBean("userService");//获取UserService对象
userService.save();//调用方法
}
}
启动可以正常运行。但是我们发现UserService和UserDao对象都封装到了Spring容器中。
程序调用时,首先为了获取UserSerice对象,根据id找到UserService对象,但是其中save方法中用到了UserDao对象,程序又去找Spring要UserDao对象,找到了后将UserDao对象返回给UserService进行使用。这样无疑是比较繁琐的,实现一个方法找Spring容器要了两次对象。
所以我们希望的是在Spring容器内部,将UserDao就设导入给UserService对象(可以通过构造方法或者set方法),
这里就可以引入我们的依赖注入的概念,它是 Spring 框架核心 IOC 的具体实现。
在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。
那这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
3.5.1Set方法实现依赖注入
我们先用set方法将UserDao导入给UserService对象,完成依赖注入,
package Service;
import Dao.UserDao;
public class UserServiceImpl implements UserService{
private UserDao userDao;//定义一个私有UserDao对象
public void setUserDao(UserDao userDao) {//通过set方法对该对象进行赋值
this.userDao = userDao;
}
public void save() {
userDao.save();//调用dao层的方法
}
}
然后在Spring配置文件中对set方法进行配置,将UserDao设置为传入的参数(注意这里property中的name属性填的是set方法后面的字符串,并且将第一个字符改为小写,即serUserDao->userDao)
<bean id="userDao" class="Dao.UserDaoImpl"></bean>
<bean id="userService" class="Service.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
我们还可以引入命名空间P,直接在bean标签内设置,
xmlns:p="http://www.springframework.org/schema/p"
<bean id="userDao" class="Dao.UserDaoImpl"></bean>
<bean id="userService" class="Service.UserServiceImpl" p:userDao-ref="userDao"></bean>
3.5.2构造方法实现依赖注入
首先我们在UserServiceImpl类中添加一个有参构造函数,
package Service;
import Dao.UserDao;
public class UserServiceImpl implements UserService{
private UserDao userDao;//定义一个私有UserDao对象
public UserServiceImpl(UserDao userDao) {
this.userDao=userDao;//通过有参构造进行赋值
}
public void save() {
userDao.save();//调用dao层的方法
}
}
同样我们对Spring配置文件进行配置,设定构造方法传入的参数值,
<bean id="userDao" class="Dao.UserDaoImpl"></bean>
<bean id="userService" class="Service.UserServiceImpl">
<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
3.5.3Bean的依赖注入的数据类型
上述操作都是注入了引用Bean,除了对象的引用可以注入,普通数据类型、集合等都可以在容器中进行注入。主要包括:
- 普通数据类型
- 引用数据类型
- 集合数据类型
接下来我们看看普通数据类型和集合数据类型如何进行注入,同样地还是使用set和构造的方法。
首先看看普通数据类型,我们在UserDaoImpl中写入两个普通数据类型数据,生成set方法,并在save方法中对两个数据进行打印。
package Dao;
public class UserDaoImpl implements UserDao {
private String username;
private int age;
public void setUsername(String username) {
this.username = username;
}
public void setAge(int age) {
this.age = age;
}
public void save() {
System.out.println(username+"="+age);//打印用户名和年龄
System.out.println("saving.........");
}
}
我们在Spring配置文件中对这两个数据进行赋值,
<bean id="userDao" class="Dao.UserDaoImpl">
<property name="username" value="tom"></property>
<property name="age" value="18"></property>
</bean>
然后调用UserDaoImpl的save方法,输出结果为:
然后是集合数据类型,我们在UserDaoImpl中创建几个集合数据,设置好set方法,
import Domain.User;
import java.util.List;
import java.util.Map;
import java.util.Properties;
public class UserDaoImpl implements UserDao {
private List<String> strList;
private Map<String, User> userMap;
private Properties properties;
public void setStrList(List<String> strList) {
this.strList = strList;
}
public void setUserMap(Map<String, User> userMap) {
this.userMap = userMap;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void save() {
System.out.println(strList);
System.out.println(userMap);
System.out.println(properties);
System.out.println("saving.........");
}
}
我们同样要在Spring配置文件中对这些集合进行赋值,
<bean id="userDao" class="Dao.UserDaoImpl">
<property name="strList">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
<property name="userMap">
<map>
<entry key="u1" value-ref="user1"></entry>
<entry key="u2" value-ref="user2"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="address">wuhan</prop>
<prop key="major">cs</prop>
</props>
</property>
</bean>
<bean id="user1" class="Domain.User">
<property name="username" value="tom"></property>
<property name="age" value="18"></property>
</bean>
<bean id="user2" class="Domain.User">
<property name="username" value="jack"></property>
<property name="age" value="22"></property>
</bean>
然后调用UserDaoImpl的save方法对这些集合进行打印,
3.6引入其他配置文件
实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,而在Spring主配置文件通过import标签进行加载。
<import resource="applicationContext-xxx.xml"/>
我们将user的对象配置到applicationContext-user.xml中,
<bean id="user1" class="Domain.User">
<property name="username" value="tom"></property>
<property name="age" value="18"></property>
</bean>
<bean id="user2" class="Domain.User">
<property name="username" value="jack"></property>
<property name="age" value="22"></property>
</bean>
然后在主配置文件中导入,
<import resource="applicationContext-user.xml"></import>
这样就做到了分模块开发,主配置文件就比较精简。
四、Spring相关API
4.1ApplicationContext继承体系
ApplicationContext:接口类型,代表应用上下文,可以通过其实例获得 Spring 容器中的 Bean 对象
紫色的为接口,浅绿色的为抽象类,深绿色的为实现类,所以我们一般创建实现类对象。
4.2ApplicationContext的实现类
1、ClassPathXmlApplicationContext
它是从类的根路径下(相对路径)加载配置文件(推荐使用)
ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("XXXX.xml");
app.getBean("");//输入id标识获取Bean对象
2、FileSystemXmlApplicationContext
它是从磁盘路径上(绝对路径)加载配置文件,配置文件可以在磁盘的任意位置。
FileSystemXmlApplicationContext app=new FileSystemXmlApplicationContext("");//输入xml文件的磁盘地址即绝对路径
app.getBean("");//输入id标识获取Bean对象
3、AnnotationConfigApplicationContext
当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
4.3getBean()方法使用
getBean有两种方式获取Bean对象,一个是传入id标识,还有一种是传入字节码对象,
public Object getBean(String name) throws BeansException {
this.assertBeanFactoryActive();
return this.getBeanFactory().getBean(name);
}
public <T> T getBean(Class<T> requiredType) throws BeansException {
this.assertBeanFactoryActive();
return this.getBeanFactory().getBean(requiredType);
}
前面我们使用的都是id标识的方法,我们看看如何传入字节码对象来获取Bean对象,
public void test4(){
ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");//获取ApplicationContext对象
UserDao userDao = app.getBean(UserDao.class);//获取UserDao的实例化对象
userDao.save();
}
两种方法都可以获取到Bean对象,但是两者主要有以下几点不同:
- id标识:可以区分同类的多个对象,即可以在配置文件中创建该类的多个对象,设置不同的属性,分别指定不同标识
- 字节码:不能区分多个对象,如果只需要类的一个对象时可以使用该方法