Spring特性之IoC容器
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。这里,我们先探索一下IoC,下一节探索AOP。
一、IoC容器概念简介
几个名词概念:
- IoC(Inversion Of Control,控制反转)
在传统的开发中,当我们遇到需要使用一个类时,往往使用 new 关键字来产生一个对象。而“控制反转”的意思是,我们把对象的创建,即new这一步,交给第三方,需要用到时,通过第三方来调取。 - DI(Dependency Injection,依赖注入)
IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)。
我们以较为常见的service中使用dao为场景来尝试说明:
依赖注入的几种实现方式:
1.构造器注入
/**
* 模拟数据库保存用户
*/
public interface UserDao {
void save();
}
public class UserDaoImpl implements UserDao{
@Override
public void save(){
System.out.println("保存用户数据!");
}
}
public class UserService {
public UserDao userDao;
/**
* 通过构造函数,将所要用到的对象注入进来
* @param userDao
*/
public UserService(UserDao userDao){
this.userDao = userDao;
}
public void addUser(){
userDao.save();
}
}
使用
public class Main {
// 模拟保存用户场景
public static void main(String[] args){
UserDao userDao = new UserDaoImpl();
// 这里,在service中使用到的userDao,是从构造器传入的
UserService service = new UserService(userDao);
service.addUser();
}
}
2.属性注入
userDao不变,将UserService 稍作改变
public class UserService {
public UserDao userDao;
/**
* 这里使用set方法,将所用对象传入
* @param userDao
*/
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void addUser(){
userDao.save();
}
}
使用
public class Main {
public static void main(String[] args){
// 模拟保存用户场景
UserDao userDao = new UserDaoImpl();
UserService service = new UserService();
// 这里,在service中使用到的userDao,是从构造器传入的
service.setUserDao(userDao);
service.addUser();
}
}
- IoC容器
依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)。IoC容器在运行期间,动态地将某种依赖关系注入到对象之中。
二、spring中的实现
spring实现DI的两种方式:
- 注解
分二步走:
1.xml开启注解
spring-annotation.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解 -->
<context:component-scan base-package="cn.nep"/>
</beans>
2.类上加注解
UserDao.java
@Component("userDao")
public class UserDao {
public void add(){
System.out.println("UserDao add ... ");
}
}
@Component spring注解,标注该类为spring组件,其动态创建、依赖注入、生命周期等交由spring管理。
("userDao"),标注该对象在容器中的id,即在容器中的唯一标识。
UserService.java
@Component
public class UserService {
/**
* 通过注解注入,不需要set方法
*/
@Autowired
private UserDao userDao;
public void add(){
userDao.add();;
System.out.println("UserService add...");
}
}
@Autowired spring注解,对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过使用来消除 set ,get方法。
使用:
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("cn/nep/di/annotation/spring-annotation.xml");
UserService userService = (UserService)context.getBean("userService");
userService.add();
}
- xml配置
分二步走:
1.xml配置
首先,我们再xml中配置好所要的bean
spring-xml.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="userDao" class="cn.nep.di.xml.UserDao">
</bean>
<!-- 属性注入,pojo中必须要有set方法.且pojo中必须要有无参构造函数 -->
<bean id="userService"
class="cn.nep.di.xml.UserService">
<property name="userDao" ref="userDao"></property>
</bean>
<!-- 构造器注入,pojo中必须有 对应的构造函数 -->
<bean id="userService2"
class="cn.nep.di.xml.UserService2">
<constructor-arg ref="userDao"/>
</bean>
</beans>
这里用到的几个类
UserDao.java
public class UserDao {
public void add(){
System.out.println("UserDao add ... ");
}
}
UserService.java
public class UserService {
private UserDao userDao;
/**
* 通过xml配置,属性注入,必须要有set方法
* @param userDao
*/
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
public void add(){
userDao.add();;
System.out.println("UserService add...");
}
}
UserService2.java
public class UserService2 {
private UserDao userDao;
/**
* 通过xml配置,构造器注入
* @param userDao
*/
public UserService2(UserDao userDao){
this.userDao = userDao;
}
public void add(){
userDao.add();;
System.out.println("UserService2 add...");
}
}
2.从容器中获取
public class Main {
public static void main(String[] args){
// 容器解析xml,创建并管理bean
ApplicationContext context = new ClassPathXmlApplicationContext("cn/nep/di/xml/spring-xml.xml");
// 从容器中获取bean
UserService u1 = (UserService)context.getBean("userService");
u1.add();
System.out.println("=========================");
UserService2 u2 = (UserService2)context.getBean("userService2");
u2.add();
}
}
三、自定义实现IoC容器
知道了IoC、DI的概念,我们可以动手自己撸一个简单版的IoC容器。思路:1.定义xml,2.解析xml,反射生成类,3将类放入一个存储介质中,如map,4.从自定义的容器中获取
下面依次实现:
1.定义xml
在xml文件中,为了方便取用,我们一般会设置一个唯一标识id;我们还要设置其属性值,用以注入对象;当然,类的全路径也是不可少的,反射时用以确定哪个类。
beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="nep" class="cn.nep.beans.User">
<property name="id" value="01"></property>
<property name="name" value="nep"></property>
</bean>
<bean id="jj" class="cn.nep.beans.User">
<property name="id" value="9527"></property>
<property name="name" value="njj"></property>
</bean>
<bean id="book" class="cn.nep.beans.Book">
<property name="id" value="XN002"></property>
<property name="name" value="删库跑路"></property>
</bean>
</beans>
2.解析xml,反射生成类
当我们有了bean属性的xml文件,接下来就是对文件的解析了。解析我们这里用到dom4j。
/**
* dom4j解析
*/
public static Map<String,Object> createByDom4j(String path) throws Exception{
Map<String,Object> beans = new HashMap<>();
File file = new File(path);
if(!file.exists()){
System.out.println("系统找不到文件!==="+path);
return null;
}
SAXReader reader = new SAXReader();
Document document = reader.read(file);
Element root = document.getRootElement();
List<Element> childElements = root.elements();
for (Element child : childElements) {
List<Attribute> attributeList = child.attributes();
// for (Attribute attr : attributeList) {
// System.out.println(attr.getName() + ": " + attr.getValue());
// }
// 利用反射,生成对象
String cls = child.attributeValue("class");
// 根据类的全路径 获取class
Class clz = Class.forName(cls);
// 生成对象
Object obj = clz.newInstance();// 需要用到无参构造函数
// 循环获取属性值,并设置
List<Element> proElements = child.elements();
for (Element proEl : proElements){
Field f = clz.getDeclaredField(proEl.attributeValue("name"));
f.setAccessible(true);
f.set(obj,proEl.attributeValue("value"));
}
beans.put(child.attributeValue("id"),obj);
}
return beans;
}
3.将类放入一个存储介质中,如map
我们设计一个类作为容器。由于map是以键值对形式存储变量的,而变量的类型为Object,这样很符合我们存取对象的需求,所以,在类中我们用到map来存储对象。
private Map<String,Object> beans;
xml的解析,反射生成对象,存入map,这些步骤都是在容器类内完成的。下面是容器类。
IContext.java
public class IContext {
// 用map存储对象
private Map<String,Object> beans;
/**
* 根据id获取对象
* @param id
* @return
*/
public Object getBean(String id){
return beans.get(id);
}
public IContext(String path){
path = IContext.class.getResource("/").getPath()+path;
try {
// dom4j 解析xml,反射生成对象
Map<String,Object> beans = createByDom4j(path);
System.out.println("dom4j 解析xml:");
if (beans != null&& beans.size()>0){
this.beans = beans;
}
}catch (Exception e){
e.printStackTrace();
}
}
/**
* dom4j解析
*/
public static Map<String,Object> createByDom4j(String path) throws Exception{
Map<String,Object> beans = new HashMap<>();
File file = new File(path);
if(!file.exists()){
System.out.println("系统找不到文件!==="+path);
return null;
}
SAXReader reader = new SAXReader();
Document document = reader.read(file);
Element root = document.getRootElement();
List<Element> childElements = root.elements();
for (Element child : childElements) {
List<Attribute> attributeList = child.attributes();
// for (Attribute attr : attributeList) {
// System.out.println(attr.getName() + ": " + attr.getValue());
// }
// 利用反射,生成对象
String cls = child.attributeValue("class");
// 根据类的全路径 获取class
Class clz = Class.forName(cls);
// 生成对象
Object obj = clz.newInstance();// 需要用到无参构造函数
// 循环获取属性值,并设置
List<Element> proElements = child.elements();
for (Element proEl : proElements){
Field f = clz.getDeclaredField(proEl.attributeValue("name"));
f.setAccessible(true);
f.set(obj,proEl.attributeValue("value"));
}
beans.put(child.attributeValue("id"),obj);
}
return beans;
}
}
用到的实体类:
Book.java
public class Book {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Book{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
}
User.java
public class User {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
}
测试:
public static void main(String[] args){
try {
IContext iContext = new IContext("beans.xml");
User user = (User)iContext.getBean("nep");
System.out.println(user.toString());
user = (User)iContext.getBean("jj");
System.out.println(user.toString());
Book book = (Book)iContext.getBean("book");
System.out.println(book.toString());
}catch (Exception e){
e.printStackTrace();
}
}
至此,一个简单版的IoC容器就完成了。