一、Spring概述
Spring 是分层的Java SE/EE应用 full-stack(服务端的全栈)轻量级(跟EJB比)开源框架,以IoC(Inversion of Control控制反转,目的是解耦)和AOP(面向切面编程,本质是动态代理,目的是增强)为内核
-
Spring家族有很多的框架,涉及到所有层(web |service |dao)
-
今天学的Spring仅仅是Spring家族里面的其中一个框架 Spring Framework (IOC + AOP)
提供了:
-
表现层(web层): Spring MVC
-
业务层(service层) : Spring
-
持久层(Dao层):Spring JDBCTemplate, Spring Data JPA
-
能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。
2. Spring的优势
方便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。 创建对象更简单!
AOP编程的支持
通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
比如:要求面面项目里,每个方法被调用时,都输出日志到控制台“2020-03-20 11:20:31执行了xxx.xx方法”
声明式事务的支持(第3天) 在xml里面配置事务,在方法上或者类上 打一个注解即可。
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。@Transactional
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
例如:Spring整合了Junit
方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。
降低JavaEE API的使用难度
Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。
Java源码是经典学习范例
Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源代码无疑是Java技术的最佳实践的范例。
3. Spring的体系结构
二、工厂模式解耦
1. 耦合性问题
耦合性:程序之间(代码间)的依赖性。代码与代码之间的联系。
编译期依赖:编译时必须提供依赖的类,否则编译不通过
-
UserService us = new UserService();
- UserService us02 = new UserServiceImpl();
-
运行期依赖:运行时必须提供依赖的类,否则不能运行。
-
接口和实现的写法:
UserService us02 = Class.forName(“com.execise.service.impl.UserServiceImpl”).newInstance();
-
-
应当减少编译期依赖,使用运行期依赖
耦合性越强,维护成本(时间成本&精力成本)就越高
开发时要求:高内聚,低耦合
-
低耦合 : 耦合度很低,代码与代码之间耦合度很低。
-
高内聚: 把具有一样功能的代码,尽可能靠拢起来。 一个业务有很多的方法,这些方法要尽可能靠在一块。方便管理,维护。
类与类之间的内聚 模块化servlet
-
注册 ---- RegisterServlet
-
登录 — LoginServlet
-
更新用户 — UpdateUserServlet
-
用户 ----- UserServlet
-
方法与方法之间的内聚
-
每个方法里面都有乱码解决…两句话 ---- 过滤器
-
req.setCharacterEncoding();
-
resp.setContentType();
耦合性问题现象
在web开发中,服务端通常分为三层:web层、service层、dao层
-
web层调用service层完成功能:需要new一个Service对象
-
以前的写法,直接new对象
UserService userService = new UserService();
-
真正开发的时候是面向接口编程。
UserService userService = new UserServiceImpl();
-
service层调用dao层操作数据库:需要new一个dao对象
-
以前的写法, 直接new对象
UserDao userDao = new UserDao();
-
真正开发的时候是面向接口编程。
UerDao userDao = new UserDaoImpl();
三层之间的耦合性比较强:存在编译期依赖
-
service里写死了创建某一个dao对象:一旦dao对象换了,就需要修改service的源码
-
web里写死了创建某一个service对象:一旦service对象换了,就需要修改web的源码
解耦的思路
可以使用反射技术,代替 new
创建对象,避免编译期依赖
bject obj = Class.forName("全限定类名").newInstance();
再把全限定类名提取到配置文件中(xml或properties),读取配置文件信息来反射创建对象
//读取配置文件,得到要创建对象的全限定类名;再通过反射技术创建对象
String className = ...;
Class clazz = Class.forName(className);
Object obj = clazz.newInstance();
2. 使用工厂模式解耦
1. 创建项目,导入依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2. dao层代码
接口
package com.execise.dao;
public interface UserDao {
void add();
}
实现类
package com.execise.dao.impl;
import com.execise.dao.UserDao;
public class UserDaoImpl implements UserDao {
public void add() {
System.out.println("调用了UserDaoImpl的add方法~!");
}
}
3. service层代码
接口
package com.execise.service;
public interface UserService {
void add() throws Exception;
}
实现类
package com.execise.service.impl;
import com.execise.dao.UserDao;
import com.execise.dao.impl.UserDaoImpl;
import com.execise.factory.BeanFactory;
import com.execise.service.UserService;
public class UserServiceImpl implements UserService {
public void add() throws Exception {
System.out.println("调用了UserServiceImpl的add方法~!");
//以前的写法:
/*UserDao userDao = new UserDao();
UserDao userDao = new UserDaoImpl();
userDao.add();*/
//现在的写法:
UserDao userDao = (UserDao) BeanFactory.getBean("ud");
userDao.add();
}
}
4. 配置文件 beans.properties
在resource下面创建 beans.properties
us=com.execise.service.impl.UserServiceImpl
ud=com.execise.dao.impl.UserDaoImpl
5. 工厂类 BeanFactory
package com.execise.factory;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
/*
这是专门用来创建对象的工厂类
1. 使用静态代码块来读取beans.properties
2. 提供一个静态方法,供外面的人调用获取对象
*/
public class BeanFactory {
static Map<String , String> map = new HashMap<String , String>();
//1. 在这里读取beans.properties文件
static{
//1. 读取外部的properties文件,只要写名字即可
ResourceBundle resourceBundle = ResourceBundle.getBundle("beans");
//2. 读取里面的内容
Enumeration<String> keys = resourceBundle.getKeys();
//3. 遍历每一个key value
while(keys.hasMoreElements()){
//4. 取出每一个key
String key = keys.nextElement();
//5. 得到每一个value
String value = resourceBundle.getString(key);
//6. 把key和value保存到map集合
map.put(key , value );
}
}
public static Object getBean(String name) throws Exception {
//1. 从map集合里面获取全路径
String className = map.get(name);
//2. 判定
if(className != null){
return Class.forName(className).newInstance();
}
return null;
}
}
测试
package com.itexeciseeima.test;
import com.execise.factory.BeanFactory;
import com.execise.service.UserService;
import org.junit.Test;
public class TestUserServiceImpl {
@Test
public void testAdd() throws Exception {
//1. 问工厂要对象
UserService us = (UserService) BeanFactory.getBean("us");
us.add();
}
}
小结
首先得有接口和实现类 : UserDao 和 UserDaoImpl , UserService 和 UserServiceImpl
使用properties配置文件来记录 ,别名和实现类的全路径
定义一个工厂类
-
在静态代码块里面读取配置文件,使用map集合来存映射关系
-
定义一个静态方法,只要有人来获取实例,那么就从map集合里面取出来全路径
-
使用反射技术来构建实例返回。
三、控制反转IOC
什么是IOC inversion of control
控制反转,把对象的创建工作交给框架(工厂 Spring),我们不需要自己去new这个对象,只管问工厂要。由原来的主动创建对象,变成自己被动接收 框架创建的对象。
IOC的作用
IOC是Spring的核心之一,作用就是为了解耦,降低程序,代码间的耦合度。
1. 创建Maven项目,导入依赖坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2. 编写dao接口 UserDao
及实现 UserDaoImpl
接口 UserDao
package com.execise.dao;
public interface UserDao {
void add();
}
实现类 UserDaoImpl
package com.execise.dao.impl;
import com.execise.dao.UserDao;
public class UserDaoImpl implements UserDao {
public void add() {
System.out.println("调用了UserDaoImpl的add方法~!~");
}
}
3. 创建Spring核心配置文件,并配置 UserDaoImpl
这个步骤的作用就是告诉spring,要创建哪个类的对象,并且给这个类起一个别名,方便以后我们问spring要对象。它的作用等于我们前面写的
beans.properties
配置文件名称,通常叫 applicationContext.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">
<!--
在这里告诉spring要创建哪个类的对象,并且给这个对象起一个别名
bean标签:
作用: 用来托管(类)对象
属性:
id: 唯一标识,不能出现重复!
class: 托管类的全路径
-->
<bean id="ud" class="com.execise.dao.impl.UserDaoImpl" />
</beans>
4. 使用Spring的API,获取Bean实例对象
编写测试类
package com.execise.test;
import com.execise.dao.UserDao;
import com.execise.dao.impl.UserDaoImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestUserDao {
@Test
public void testAdd(){
//1. 创建工厂
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2. 问工厂要对象
UserDao userDao = (UserDao) context.getBean("ud");
//3. 调用方法
userDao.add();
}
}
小结
-
首先编写UserDao 和 UserDaoImpl
-
在pom.xml里面添加依赖
-
在resources下面,创建一个xml文件,名字随意。不要手动创建文件的方式。要选择xml配置文件的方式
-
在xml文件里面登记|注册|托管实现类
-
问工厂要实例
配置文件详解
1. bean标签的基本配置
<bean id="userDao" class="com.execise.dao.impl.UserDaoImpl"></bean>
介绍
-
用于配置:把对象交给Spring进行控制 , spring会帮助我们创建对象。
-
默认情况下,Spring是调用类的无参构造来创建对象的;如果没有无参构造,则不能创建成功
基本属性
-
id
:唯一标识 -
class
:bean的全限定类名
了解:bean的id和name的区别
- 一个bean只能有一个id;一个bean可以有多个name
- bean的name值:多个name之间以
,
;
空格
隔开,第1个name作为id,其它作为别名
2. bean标签的作用范围配置
<bean id="userDao" class="com.execise.dao.impl.UserDaoImpl" scope="singleton"></bean>
scope属性取值如下:
取值 | 说明 |
---|---|
singleton | 默认,表示单例的,一个Spring容器里,只有一个该bean对象 |
prototype | 多例的,一个Spring容器里,有多个该bean对象 |
request | web项目里,Spring创建的bean对象将放到 request 域中:一次请求期间有效 |
session | web项目里,Spring创建的bean对象将放到 session 域中:一次会话期间有效 |
globalSession | web项目里,应用在Portlet环境/集群环境;如果没有Portlet/集群环境,那么globalSession相当于session(新版本中已删除) |
不同scope的bean,生命周期:
-
singleton:bean的生命周期和Spring容器的生命周期相同
- 整个Spring容器中,只有一个bean对象
- 何时创建:加载Spring配置文件,初始化Spring容器时,bean对象创建
- 何时销毁:Spring容器销毁时,bean对象销毁
-
prototype:bean的生命周期和Spring容器无关。Spring创建bean对象之后,交给JVM管理了
- 整个Spring容器中,会创建多个bean对象,创建之后由JVM管理
- 何时创建:调用
getBean
方法获取bean对象时,bean对象创建 - 何时销毁:对象长时间不用时,垃圾回收
3. bean生命周期相关方法的配置
<bean id="userDao" class="com.execise.dao.impl.UserDaoImpl"
init-method="" destroy-method=""></bean>
init-method
:指定类中初始化方法名称,该方法将在bean对象被创建时执行
destroy-method
:指定类中销毁方法名称,该方法将在bean对象被销毁时执行
注意:
- prototype类型的bean:Spring容器销毁时,也不会执行销毁方法,因为Spring不负责它的销毁
- singleton类型的bean:在Spring容器显式关闭时,会执行destroy-method指定的方法
dao
package com.execise.dao.impl;
import com.execise.dao.UserDao;
public class UserDaoImpl implements UserDao {
public UserDaoImpl(){
System.out.println("创建对象了额~!!~");
}
public void add() {
System.out.println("调用了UserDaoImpl的add方法~!");
}
//创建好对象的时候调用这个方法
public void init(){
System.out.println("调用了UserDaoImpl的init方法~!");
}
//销毁对象的时候调用这个方法
public void destroy(){
System.out.println("调用了UserDaoImpl的destroy方法~!");
}
}
单元测试
package com.execise.test;
import com.execise.dao.UserDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestUserDaoImpl {
//测试单例多例
@Test
public void testAdd2(){
//1. 创建工厂
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2. 问工厂要对象
UserDao userDao = (UserDao) context.getBean("ud");
//3. 关闭工厂!
context.close();
}
}
xml配置
<!--
在这里告诉spring要创建哪个类的对象,并且给这个对象起一个别名
bean标签:
作用: 用来托管(类)对象
属性:
id: 唯一标识,不能出现重复!
class: 托管类的全路径
name : 用于给对象起别名,标识以后可以用这个别名来找到对象
可以写多个值,使用 分号 , 逗号, 空格 ,tab 来间隔!【一般不用它!】
scope: 用来设置创建的对象是单例的还是多例
singleton: 单例,默认是单例! 【大多数情况下,使用的都是单例对象】
何时创建:创建工厂的时候创建了对象
何时销毁:销毁工厂的时候,销毁对象
prototype: 多例
何时创建:问工厂要对象的时候,才创建对象
何时销毁:长时间不用之后,就由GC回收对象!
init-method:
当对象初始化的时候,调用这个方法
destroy-method:
当对象销毁的时候,调用这个方法
spring创建类的对象,默认会执行该类的无参构造方法!
-->
<bean id="ud" class="com.execise.dao.impl.UserDaoImpl" scope="prototype" init-method="init" destroy-method="destroy"/>
4. bean实例化的三种方式
我们通常都是问Spring要对象,那么Spring怎么整出来对象的呢?有三种方式。算起来就只有两种办法创建对象:
- 由Spring来创建对象
- 由我们自己来创建对象,然后spring来拿我们的对象给需要的人。
无参构造方法实例化,默认的:让Spring调用bean的无参构造,生成bean实例对象给我们 【由Spring创建】
工厂静态方法实例化:让Spring调用一个我们自己写好的工厂类的静态方法,得到一个bean实例对象 【由咱们自己创建】
工厂非静态方法实例化(实例化方法):让Spring调用一个工厂对象的非静态方法,得到一个bean实例对象 【由咱们自己创建】
无参构造方法实例化 【spring创建对象】
UserDaoImpl 是由Spring创建的。
<bean id="ud" class="com.execise.dao.impl.UserDaoImpl"></bean>
工厂静态方法实例化
UserDaoImpl的由我们写好的StaticFactory的类来创建 , Spring工厂没干活,只是问我们的工厂要对象而已。
工厂类如下:com.execise.factory.StaticFactory
package com.execise.factory;
import com.execise.dao.UserDao;
import com.execise.dao.impl.UserDaoImpl;
/*
使用工厂的静态方法来创建对象
*/
public class StaticFactory {
/**
* 创建UserDaoImpl的对象
* @return
*/
public static UserDao getBean(){
System.out.println("来问StaticFactory要对象了~");
return new UserDaoImpl();
}
}
配置如下:
<!--
使用工厂的静态方法来创建对象
1. spring工厂并不会去创建UserDaoImpl的对象。
2. 当有方法拿着ud02来问spring的工厂要对象的时候,spring的工厂会
找StaticFactory的getBean方法得到对象
3. 然后把这个对象返回给我们的方法
-->
<bean id="ud02" class="com.execise.factory.StaticFactory" factory-method="getBean"/>
工厂非静态方法实例化
UserDaoImpl的由我们写好的InstanceFactory的类来创建 ,, Spring工厂没干活,只是问我们的工厂要对象而已。
工厂类如下:com.execise.factory.InstanceFactory
package com.execise.factory;
import com.execise.dao.UserDao;
import com.execise.dao.impl.UserDaoImpl;
public class InstanceFactory {
/**
* 创建UserDaoImpl的对象
* @return
*/
public UserDao getBean(){
System.out.println("来问InstanceFactory要对象了~");
return new UserDaoImpl();
}
}
配置如下:
<!--
使用工厂的非静态方法来创建对象:
1. spring的工厂并不会去创建UserDaoImpl的对象。
2. 它创建了我们工厂的对象
3. 当有方法拿着ud03来问spring的工厂要对象的时候,spring会拿着instanceFactory去找到工厂对象
(这个工厂对象是由spring创建出来的),然后调用getBean方法,返回我们创建好的UserDaoImpl对象
-->
<bean id="instanceFactory" class="com.execise.factory.InstanceFactory"/>
<bean id="ud03" factory-bean="instanceFactory" factory-method="getBean"/>
实现FactoryBean<T>方式
定义UserDaoFactoryBean实现FactoryBean<UserDao>
UserDaoFactoryBean中实例化什么类型的对象泛型就是该类型。
//FactoryBean创建对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
public Class<?> getObjectType() {
return UserDao.class;
}
}
applicationContext.xml配置
<!--方式四:使用FactoryBean实例化bean-->
<bean id="userDao" class="com.execise.factory.UserDaoFactoryBean"/>
注意配置文件中id="userDao"是否重复。
小结
-
Spring工厂创建实例有三种方式: 默认的无参构造方式 | 静态工厂方式 | 实例工厂方式
-
只有无参构造的那种方式是spring创建对象,其他两种都是由我们自己来创建对象
-
我们使用spring的IOC ,目的就是为了把对象的创建工作交给Spring,后面这种工厂的方式,反而是我们来创建对象,所以一般不用这两种。
-
既然如此,都不怎么用后面得的两种方式了,为什么spring还要提供这两种入口呢?
这个其实就是为了兼容, 就是为了兼容以前的旧项目,有的旧项目50年前的旧项目,那个没有spring,但是那个时候已经使用了工厂来创建实例了。
IOC小结
-
IOC是什么? 控制反转,把对象的创建工作交给spring的工厂完成,只管问spring要对象即可
-
在applicationContext.xml里面注册。
-
默认创建的实例是单例,如果想要多例,需要配合scope属性,设置成prototype
四、依赖注入DI
托管类里面有什么属性需要完成赋值工作,把这个赋值的工作交给spring来做。由spring把属性需要用到的值赋值(注入)进来就称之为依赖注入。
我们通过Ioc把bean对象交给了Spring容器进行管理,降低了耦合性。
但是耦合性不能彻底消除,bean之间还是有一些依赖关系。比如:业务层userService要依赖于持久层userDao。
这样的依赖关系,可以交给Spring帮我们进行依赖的注入,而不用我们自己注入依赖
1. 创建Maven项目,导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2. 编写dao层和service层代码
dao层接口 UserDao
package com.execise.dao;
public interface UserDao {
void add();
}
dao层实现类 UserDaoImpl
package com.execise.dao.impl;
import com.execise.dao.UserDao;
public class UserDaoImpl implements UserDao {
public void add() {
System.out.println("调用了UserDaoImpl的add方法~!");
}
}
service层接口 UserService
package com.execise.service;
public interface UserService {
void add();
}
service层实现类 UserServiceImpl
package com.execise.service.impl;
import com.execise.dao.UserDao;
import com.execise.service.UserService;
/*
需求: service里面的add方法要调用dao的add方法
分析:
1. 要想调用dao的add方法,必须持有UserDaoImpl的对象。
2. 要想拥有UserDaoImpl的对象有两种办法:
2.1 自己创建 ,自己new 【以前的做法】
UserDao userDao = new UserDaoImpl();
userDao.add();
2.2 让spring把userDaoImpl的对象给注入进来,注入给UserServiceImpl!【采用这种方式】
步骤:
1. 在UserServiceImpl里面定义属性 : private UserDao userDao;
2. 提供这个属性的set方法!
3. 把UserServiceImpl和UserDaoImpl这两个类都交给spring托管。
4. 在xml里面配置,告诉spring要把UserDao的对象注入到UserServiceImpl里面的这个属性 userDao身上!
*/
public class UserServiceImpl implements UserService {
//1. 定义属性
private UserDao userDao;
//2. 提供set方法
public void setUserDao(UserDao userDao) {
System.out.println("来调用Set方法了·~");
this.userDao = userDao;
}
public void add() {
System.out.println("调用了UserServiceImpl的add方法~!");
userDao.add();
}
}
3. 创建Spring核心配置文件,并配置bean和依赖注入
<?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">
<!--1. 把UserDaoImpl交给spring管理-->
<bean id="userDao" class="com.execise.dao.impl.UserDaoImpl"/>
<!--2. 把UserServiceImpl交给spring管理-->
<bean id="us" class="com.execise.service.impl.UserServiceImpl">
<!--告诉spring,把id名字叫做ud的对象,赋值给UserServiceImpl里面的userDao属性-->
<property name="userDao" ref="userDao"/>
</bean>
</beans>
4. 使用Spring的API,测试
package com.execise.test;
import com.execise.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestUserServiceImpl {
@Test
public void testAdd(){
//1. 创建工厂
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2. 问工厂要对象
UserService us = (UserService) context.getBean("us");
//3. 调用方法
us.add();
}
}
小结
-
有接口,有实现类
-
给属性提供set方法
-
在托管|登记类的时候,要添加property标签。如果注入的是对象,那么要使用ref属性,如果注入的是普通的数据,那么要使用value属性。
三种常见注入方式
1. set方法注入(最常用)
1) 介绍
在类中提供需要注入的成员(依赖项)的set方法,在配置文件中注入属性的值
<bean id="" class="">
<property name="属性名" value="属性值"></property>
<property name="属性名" ref="bean的id"></property>
</bean>
property
标签:用在bean标签内部,表示要给某一属性注入数据
-
name
:属性名称 -
value
:要注入的属性值,注入简单类型值 -
ref
:要注入的属性值,注入其它bean对象
2) 示例
service
package com.execise.service.impl;
import com.execise.dao.UserDao;
import com.execise.service.UserService;
/*
三种注入方式之一: set方法
要求:一定要提供属性的set方法!
*/
public class UserServiceImpl01 implements UserService {
private String address;
private UserDao userDao;
public void setAddress(String address) {
this.address = address;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
System.out.println("调用了UserServiceImpl01的add方法~!"+address);
userDao.add();
}
}
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">
<!--1. 把UserDaoImpl交给spring管理-->
<bean id="userDao" class="com.execise.dao.impl.UserDaoImpl"/>
<!--
2. 把UserServiceImpl01交给spring管理
2.1 采用set方法完成属性的注入工作。
property : 用来告诉spring,有哪些属性要赋值,代码里面一定要提供set方法
name :属性名
ref : 注入值,针对的是对象类型属性
value : 注入值,针对的是普通的属性(基本数据类型 & 字符串)
-->
<bean id="us" class="com.execise.service.impl.UserServiceImpl01">
<property name="userDao" ref="userDao"/>
<property name="address" value="深圳"/>
</bean>
</beans>
2. 构造方法注入
1) 介绍
在类中提供构造方法,构造方法的每个参数就是一个依赖项,通过构造方法给依赖项注入值。
<bean id="" class="">
<constructor-arg name="构造参数名称" value="构造参数的值"></constructor-arg>
<constructor-arg name="构造参数名称" ref="bean的id"></constructor-arg>
</bean>
-
name
:构造参数的名称 -
type
:构造参数的类型 -
index
:构造参数的索引 -
value
:要注入的值,注入简单类型值 -
ref
:要注入的值,注入其它bean对象
2) 示例
service
package com.execise.service.impl;
import com.execise.dao.UserDao;
import com.execise.service.UserService;
/*
三种注入方式之一: 有参构造
要求:一定要提供有参构造方法!
*/
public class UserServiceImpl02 implements UserService {
private String address;
private UserDao userDao;
public UserServiceImpl02(String address, UserDao userDao) {
this.address = address;
this.userDao = userDao;
}
public void add() {
System.out.println("调用了UserServiceImpl02的add方法~!"+address);
userDao.add();
}
}
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">
<!--1. 把UserDaoImpl交给spring管理-->
<bean id="userDao" class="com.execise.dao.impl.UserDaoImpl"/>
<!--
2. 把UserServiceImpl02交给spring管理
constructor-arg : 用于匹配有参构造函数,
name: 参数名
value : 给参数赋值,针对的是普通的参数(基本类型 & 字符串)
ref : 给参数赋值, 针对的是对象的参数
-->
<bean id="us" class="com.execise.service.impl.UserServiceImpl02">
<constructor-arg name="address" value="深圳" />
<constructor-arg name="userDao" ref="userDao"/>
</bean>
</beans>
3. p名称空间注入
1) 介绍
p名称空间注入,本质仍然是set方法注入
在xml中引入p名称空间的约束
然后通过 p:属性名称=""
来注入简单数据、使用 p:属性名称-ref=""
注入其它bean对象,它的本质仍然是set方法注入
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="" class="" p:属性名="简单值" p:属性名-ref="bean的id"></bean>
</beans>
2) 示例
service
package com.execise.service.impl;
import com.execise.dao.UserDao;
import com.execise.service.UserService;
/*
三种注入方式之一: p名称空间
要求:一定要提供属性的set方法!
*/
public class UserServiceImpl03 implements UserService {
private String address;
private UserDao userDao;
public void setAddress(String address) {
this.address = address;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
System.out.println("调用了UserServiceImpl03的add方法~!"+address);
userDao.add();
}
}
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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--1. 把UserDaoImpl交给spring管理-->
<bean id="userDao" class="com.execise.dao.impl.UserDaoImpl"/>
<!--
2. 把UserServiceImpl03交给spring管理
-->
<bean id="us" class="com.execise.service.impl.UserServiceImpl03" p:address="中粮商务公园" p:userDao-ref="userDao"/>
</beans>
依赖注入方式选择
-
强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
-
可选依赖使用setter注入进行,灵活性强
-
Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
-
如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
-
实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
-
自己开发的模块推荐使用setter注入
小结
-
注入方式有三种,setter注入 ,构造方法注入,p名称空间
-
最常用的是setter注入。
-
以后如果使用注解了,方法也不需要写了。
依赖自动装配
如何配置按照类型自动装配?
自动装配概念
IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
自动装配方式
按类型(常用)
按名称
按构造方法
不启用自动装配
自动装配类型
依赖自动装配
配置中使用bean标签autowire属性设置自动装配的类型
<bean id="bookDao" class="com.execise.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.execise.service.impl.BookServiceImpl" autowire="byType"/>
依赖自动装配特征
-
自动装配用于引用类型依赖注入,不能对简单类型进行操作
-
使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
-
使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
-
自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
注入集合数据
前边我们介绍了如何注入简单数据类型和bean对象,但是在实际开发中,可能会需要给集合属性注入数据,比如:给数组、List、Set、Map等注入数据
示例代码
package com.execise.service.impl;
import com.execise.dao.UserDao;
import com.execise.service.UserService;
import java.util.*;
/*
注入集合数据: 数组、list、set、map、properties
*/
public class UserServiceImpl04 implements UserService {
private String [] array;
private List<String> list;
private Set<String> set;
private Map<String , String> map;
private Properties properties;
public void setArray(String[] array) {
this.array = array;
}
public void setList(List<String> list) {
this.list = list;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void add() {
System.out.println("调用了UserServiceImpl04的add方法~!");
System.out.println("array="+Arrays.toString(array));
System.out.println("list = " + list);
System.out.println("set = " + set);
System.out.println("map = " + map);
System.out.println("properties = " + properties);
}
}
配置注入数据
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--1. 把UserDaoImpl交给spring管理-->
<bean id="userDao" class="com.execise.dao.impl.UserDaoImpl"/>
<!--
2. 把UserServiceImpl04交给spring管理
-->
<bean id="userviceImpl" class="com.execise.service.impl.UserServiceImpl"/>
<bean id="us" class="com.execise.service.impl.UserServiceImpl04">
<!--1. 数组-->
<property name="array">
<array>
<value>array01</value>
<value>array02</value>
<value>array03</value>
</array>
</property>
<!--2. list-->
<property name="list">
<list>
<value>list01</value>
<value>list02</value>
<value>list03</value>
</list>
</property>
<!--3. set-->
<property name="set">
<set>
<value>set01</value>
<value>set02</value>
<value>set03</value>
</set>
</property>
<!--4. map-->
<property name="map">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
<entry key="key3" value="value3"/>
</map>
</property>
<!--5. properties-->
<property name="properties">
<props>
<prop key="username">张三</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
</beans>
所有单列结构的数据集合,标签可以互换使用。例如:List、Set、数组等
所有键值对结构的数据集合,标签可以互换使用。例如:Map、Properties等
小结
-
所有的DI数据类型里面,最常用的是对象数据。
-
最常用的方式 set方法。
-
数组 、 list 、set写法基本一样, map 和 properties基本一样。
引入properties文件
如果需要在 applicationContext.xml
中引入properties文件:
- 准备一个properties文件放在resources目录里:
db.properties
db.driverClass=com.mysql.jdbc.Driver
db.jdbcUrl=jdbc:mysql://localhost:3306/spring01
db.user=root
db.password=root
在 applicationContext.xml
中引入并使用 db.properties
- Spring的名称空间(建议使用idea自动生成的,如果idea抽风了,就自己手写)
<beans
xmlns:名称空间="http://www.springframework.org/schema/名称空间"
xsi:scehmaLocation="
http://www.springframework.org/schema/名称空间
http://www.springframework.org/schema/名称空间/spring-名称空间.xsd">
</beans>
- 使用context名称空间提供的标签,引入外部的properties文件
context的标签,硬着头皮写出来就可以了,不要害怕!
<!--导入进来db.properties-->
<context:property-placeholder location="db.properties"/>
<!--在下面使用 ${}来取值-->
<bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}"/>
<property name="jdbcUrl" value="${jdbcUrl}"/>
<property name="user" value="${user}"/>
<property name="password" value="${password}"/>
</bean>
分模块提供配置文件
在大型项目开发中,如果把所有的配置都写在一个配置文件 applicationContext.xml
中,会导致:
-
配置文件过于臃肿
-
不利于分模块开发,不利于模块之间的解耦
Spring提供了分模块配置的方式,即:每个模块|层提供一个配置文件,在核心配置文件中引入模块配置:
-
dao模块有一个配置文件:
applicationContext-dao.xml
只配置dao相关的对象 -
service模块有一个配置文件:
applicationContext-service.xml
只配置service相关的对象 -
有一个总的核心配置文件:
applicationContext-all.xml
如下
<import resource="classpath:applicationContext-service.xml"/>
<import resource="classpath:applicationContext-dao.xml"/>
五、相关API介绍
1. ApplicationContext的继承体系
ApplicationContext
:接口,代表应用上下文,可以通过其实例对象获取Spring容器中的bean对象
2. ApplicationContext
XmlBeanFactory 和 ApplicationContext
的区别
-
ApplicationContext 是现在使用的工厂
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
-
XmlBeanFactory 是老版本使用的工厂,目前已经被废弃【了解】
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
两者的区别:
-
ApplicationContext加载方式是框架启动时就开始创建所有单例的bean,存到了容器里面
-
XmlBeanFactory加载方式是用到bean时再加载(目前已经被废弃)
ApplicationContext的实现类
-
ClassPathXmlApplicationContext
-
是从类加载路径里,加载xml配置文件的
-
什么是类加载路径:代码编译之后的那个classes文件夹,
- 开发中可以认为Maven项目的:Java文件夹、resources文件夹,都是类加载路径
-
-
FileSystemXmlApplicationContext
- 从磁盘路径里,加载xml配置文件的
-
AnnotationConfigApplicationContext
- 用注解配置Spring时,通过此类加载配置类创建Spring容器,它用于读取类上的注解配置
getBean()
方法
ApplicationContext提供了多种getBean方法的重载,常用的如下:
方法 | 参数 | 返回值 |
---|---|---|
getBean(String beanId) | bean的id | Object ,bean对象 |
getBean(String beanId,Class beanType) | bean的Class类型 | bean对象 |
getBean(Class beanType) | bean对象 | |
getBeanDefinitionNames | String[] 获取工厂管理的对象的名字 |
package com.execise.test;
import com.execise.service.UserService;
import com.execise.service.impl.UserServiceImpl04;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestApplicationContext {
@Test
public void testGetBean(){
//1. 创建工厂
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext04.xml");
//2. 问工厂要对象=getBean()
/**/ //1. 按照id来找对象,没有问题的!
UserService us = (UserService)context.getBean("us");
us.add();
//2. 按照真实的自己的类型来找对象!
UserService us2 = context.getBean(UserServiceImpl04.class);
us2.add();
//3. 按照接口的类型来找对象!
UserService us3 = context.getBean(UserService.class);
us3.add();
}
// 得到所有对象的id值
@Test
public void testGetBeanNames(){
//1. 创建工厂
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext04.xml");
//2. 获取spring工厂里面的所有对象的id值。
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println("name = " + name);
}
}
}