Spring系列文章
- Spring学习笔记 - 01
- Spring学习笔记 - 02
- Spring学习笔记 - 03
- Spring学习笔记 - 04
- 耦合:简单来说就是程序间的依赖关系,在开发中我们应该尽量做到编译期不依赖,运行时才依赖。
- 解耦:降低程序间的依赖关系。可以通过读取配置文件来获取要创建的对象的全限定类名,并使用反射来创建对象,避免使用
new
关键字。
JDBC
驱动为例子:@Test
public void testJdbc() throws Exception {
// 注册驱动
Class.forName("com.mysql.jdbc.Driver")
// 后续操作...
}
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
的方式来注册驱动呢?DriverManager.registerDriver(new com.mysql.jdbc.Driver());
来注册驱动的时候,我们的测试类就依赖于 MySQL
的驱动 jar 包。此时如果我们的项目中没有引入驱动 jar 包,那么测试类在编译期就会报错 (Error),如下图所示:Class.forName()
来说,此时的 com,mysql.jdbc.Driver
仅仅只是一个字符串,只有当运行时找不到该类才会抛出异常 (Exception),如下图所示:IUserService service = new UserServiceImpl();
userService=cn.ykf.service.impl.UserServiceImpl
/**
* 获取Bean对象的工厂类,对象单例## 标题
*
* @author yukaifan
* @ClassName BeanFactory
* @date 2019/12/13 23:24
*/
public class BeanFactory {
/**
* 用于加载配置文件的的Properties对象
*/
private static Properties prop;
/**
* 容器,用于存放所有的Bean对象,确保对象都是单例的
*/
private static Map<String, Object> beans;
static {
try {
// 实例化Properties对象
prop = new Properties();
// 获取配置文件路径,使用类加载器(不要使用src路径,因为部署后不存在src目录;也不要使用绝对路径,耦合度高)
InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
// 加载配置文件
prop.load(is);
// 初始化所有Bean对象
beans = new HashMap<String, Object>();
// 获取所有BeanName
Enumeration<Object> keys = prop.keys();
while (keys.hasMoreElements()) {
// 取出每个key
String key = keys.nextElement().toString();
// 根据key获取全限定类名
String beanPath = prop.getProperty(key);
// 反射创建对象
Object value = Class.forName(beanPath).getConstructor().newInstance();
// 放进容器
beans.put(key, value);
}
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化Properties失败...");
}
}
/**
* 禁止外界初始化
*/
private BeanFactory() {
}
/**
* 获取bean对象
*
* @param beanName bean名称
* @return bean对象
*/
public static Object getBean(String beanName) {
return beans.get(beanName);
}
}
new
关键字来创建的,这种方式是主动的;而通过 IoC,我们把创建对象的任务交给了它,这种方式是被动的,我们只需要列出一个清单,表明需要什么对象,IoC 就会帮我们创建好对象,当我们需要使用时,只需要向 IoC 容器获取即可。接下来我们通过账户的业务层和持久层间的依赖关系的案例来演示 Spring 的 IoC。
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.0.2.RELEASEversion>
dependency>
注意,因为 Spring 5 版本是用 JDK8 编写的,所以我们的 JDK 版本需要是 8 及以上,同时如果使用 Tomcat 的话, Tomcat 版本要求 8.5 及以上。
// 接口
public interface IAccountService {
/**
* 模拟保存账户的操作
*/
void saveAccount();
}
// 实现类
public class AccountServiceImpl implements IAccountService {
// 这里的依赖关系有待解决
private IAccountDao dao = new AccountDaoImpl();
@Override
public void saveAccount() {
dao.saveAccount();
}
}
// 接口
public interface IAccountDao {
/**
* 模拟将账户持久化
*/
void saveAccount();
}
// 实现类
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("保存账户到数据库...");
}
}
bean.xml
(该文件需要放在类路径下,名称是任意的,但是不能是中文)
<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="cn.ykf.service.impl.AccountServiceImpl">bean>
<bean id="accountDao" class="cn.ykf.dao.impl.AccountDaoImpl">bean>
beans>
public class IocDemo {
public static void main(String[] args) {
// 获取IOC核心容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 获取对象
IAccountService service = (IAccountService) ac.getBean("accountService");
IAccountDao dao = ac.getBean("accountDao", IAccountDao.class);
System.out.println(service);
System.out.println(dao);
service.saveAccount();
}
}
在开发中我们一般使用 BeanFactory
的子接口 ApplicationContext
,它的常用实现类如下表:
类名称 | 作用 |
---|---|
ClassPathXmlApplicationContext | 从类的根路径加载配置文件,推荐 |
FileSystemXmlApplicationContext | 从磁盘任意路径加载配置文件,必须要有访问权限,不推荐 |
AnnotationConfigApplicationContext | 使用注解配置容器对象时,需要使用此类来创建 Spring 容器。它用来读取注解 |
BeanFactory
才是 Spring 核心容器的顶级接口。
ApplicationContext
是 BeanFactory
的子接口。
id
:对象的唯一标识,用于获取对象。class
: 对象的全限定类名,用于反射创建对象。scope
: 指定对象的作用范围,有以下取值:
singleton
: 默认值,单例的。prototype
: 多例的。request
: WEB 项目中,Spring 创建一个 Bean 对象,并将对象存入 request 域中。session
: WEB 项目中,Spring 创建一个 Bean 对象,并将对象存入 session 域中。global-session
: WEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境则相当于 session。init-method
: 指定类中的初始化方法名称,创建对象前调用。destroy-method
: 指定类中的销毁方法名称,销毁对象前调用。factory-bean
: 指定工厂 Bean 对象的唯一标识 (Id),将通过工厂创建对象。factory-method
: 指定工厂中创建对象的方法,Spring 会通过该方法来创建对象。scope = "singleton"
scope = "prototype"
<bean id="accountService" class="cn.ykf.service.impl.AccountServiceImpl">bean>
在默认情况下,它会根据无参构造方法来创建类对象。如果类中没有无参构造方法,将会创建失败。
/**
* 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
*
* @author yukaifan
* @ClassName InstanceFactory
* @date 2019/12/14 11:36
*/
public class InstanceFactory {
public IAccountService getAccountService() {
return new AccountServiceImpl();
}
}
<bean id="instanceFactory" class="cn.ykf.factory.InstanceFactory">bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService">bean>
先把工厂的创建交给 Spring 来管理,然后再使用工厂中的方法来创建对象。
public class StaticFactory {
public static IAccountService getAccountService() {
return new AccountServiceImpl();
}
}
<bean id="accountService" class="cn.ykf.factory.StaticFactory" factory-method="getAccountService">bean>
因为是静态工厂,所以无需先创建工厂,直接通过工厂的静态方法来创建对象。
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date date;
public AccountServiceImpl(String name, Integer age, Date date) {
this.name = name;
this.age = age;
this.date = date;
}
@Override
public void saveAccount() {
System.out.println(name + ", " + age + ", " + date);
}
}
<bean id="accountService" class="cn.ykf.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="test">constructor-arg>
<constructor-arg name="age" value="18">constructor-arg>
<constructor-arg name="date" ref="date">constructor-arg>
bean>
<bean id="date" class="java.util.Date">bean>
标签用于构造函数注入,属性如下:
index
: 指定要注入的参数在构造方法参数列表中索引位置,索引从0开始。type
: 指定要注入的参数的数据类型,该数据类型也是构造方法中某个或某些参数的类型。name
: 指定要注入的参数的名称,这个是最方便也是最常用的。value
: 用于提供基本类型和 String 类型的数据。ref
: 用于指定其他 Bean 类型数据,该 Bean 必须是 IoC 容器中的。
setXxx()
方法给成员变量赋值。具体代码如下:public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date date;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setDate(Date date) {
this.date = date;
}
@Override
public void saveAccount() {
System.out.println(name + ", " + age + ", " + date);
}
}
<bean id="accountService" class="cn.ykf.service.impl.AccountServiceImpl">
<property name="name" value="test">property>
<property name="age" value="23">property>
<property name="date" ref="date">property>
bean>
标签用于 set 方法注入,属性如下:
name
: 指定注定时所调用的 set 方法名称。value
: 用于提供基本类型和 String 类型的数据。ref
: 用于指定其他 Bean 类型数据,该 Bean 必须是 IoC 容器中的。
Array、List、Set、Map、Properties
。具体代码如下:public class AccountServiceImpl implements IAccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
@Override
public void saveAccount() {
System.out.println(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}
<bean id="accountService" class="cn.ykf.service.impl.AccountServiceImpl">
<property name="myStrs">
<array>
<value>aaavalue>
<value>bbbvalue>
<value>cccvalue>
array>
property>
<property name="myList">
<list>
<value>dddvalue>
<value>eeevalue>
<value>fffvalue>
list>
property>
<property name="mySet">
<set>
<value>asdvalue>
<value>ddwvalue>
<value>fefevalue>
set>
property>
<property name="myMap">
<map>
<entry key="a">
<value>aaavalue>
entry>
<entry key="b" value="fwewf">entry>
map>
property>
<property name="myProps">
<props>
<prop key="adas">dasdasdasprop>
<prop key="222">deadprop>
props>
property>
bean>
- 对于集合类型的注入,可以将所涉及的标签分为两组:
- 用于给 List 结构(单列)注入的:
、
、 - 用于给 Map 结构(键值对)注入的 :
- 当结构相同时,使用的标签可以互换,也就是说,给 List 集合注入数据时,使用
或者
也是可以的。