计算机系统应用教程网站

网站首页 > 技术文章 正文

Spring之IoC详解 spring的ioc底层实现原理

btikc 2024-10-15 09:04:23 技术文章 13 ℃ 0 评论

一、Spring介绍

1. 什么是Spring

Spring是分层的Java SE/EE应用 full-stack轻量级开源框架,以IoC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层Spring MVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。

2. Spring优势

方便解耦,简化开发

通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。

AOP编程的支持

通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP(面向对象的程序设计)实现的功能可以通过AOP轻松应付。

声明式事务的支持

可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。

方便程序的测试

可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

方便集成各种优秀框架

Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。

降低JavaEE API的使用难度

Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。

3. spring的体系结构

二、Spring的IOC实战

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

IoC有两种实现方式,一种是配置文件(bean管理)方式,具体包括使用类的无参数构造方法(重点)、使用静态工厂创建、使用实例工厂创建。第二种是注解方式。

1. 创建项目

2. 添加jar包(实际开发中使用maven)

将jar包复制到以下文件夹中

设置依赖

3. 配置文件(bean管理)方式实现

方式一:使用id配置方法--常用(重要)

创建测试类User.java

public class User {
 
 public void add() {
 
 System.out.println("add.......");
 }
 public void add(String haha)
 {
 
 System.out.println("add.."+haha);
 
 }
}

src下创建配置文件myXml.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" >
<!--
 本来在该配置文件中还有一个属性name,其作用和id一样,id属性值不能包含特殊符号(比如@#),但是name属性可以,不过name属性是很早版本时候使用的,现在都被替换成id了 
 scope属性,总共有以下的值,但是最主要用的是前两个,后面两个不用记得,在该配置文件中我们没有写scope属性,默认使用的是singleton
 singleton:默认值、单例的
 prototype:多例的:
 request:创建对象,把对象放在request域里面
 session:创建对象,把对象放在session域里面
 globalSession:创建对象,把对象放在globalSession域里面
 -->
<!-- 第一种方法:ioc的配置文件 id是类的标志 class是对象类全路径-->
<bean id="user" class="ioc1.User" scope="singleton"/>
</beans>

编写测试代码

/**
 * 测试配置方式实现IOC三种方法
 * 
 * @author 吴晓畅
 *
 */
public class TestIoc {
 
 @Test
 public void testUser() {
 
 //加载spring配置文件,根据内容创建对象
 ApplicationContext context = new ClassPathXmlApplicationContext("myXml.xml");
 //方法1 :使用id配置方法--常用
 User user = (User) context.getBean("user");
 System.out.println(user);
 user.add("尼玛");
 
 }
 
}

运行结果如下:

方式二:静态工厂(了解就好)

创建测试类User2.java

package ioc2;
public class User2 {
 
 public void add() {
 
 System.out.println("user2........");
 }
}

创建测试工厂类User2Factory.java

package ioc2;
public class User2Factory {
 
 public static User2 getUser2()
 {
 
 return new User2();
 
 }
}

src下创建配置文件myXml.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" >
<!--
 本来在该配置文件中还有一个属性name,其作用和id一样,id属性值不能包含特殊符号(比如@#),但是name属性可以,不过name属性是很早版本时候使用的,现在都被替换成id了 
 scope属性,总共有以下的值,但是最主要用的是前两个,后面两个不用记得,在该配置文件中我们没有写scope属性,默认使用的是singleton
 singleton:默认值、单例的
 prototype:多例的:
 request:创建对象,把对象放在request域里面
 session:创建对象,把对象放在session域里面
 globalSession:创建对象,把对象放在globalSession域里面
 -->
<!-- 第二种方法:使用静态工厂创建对象 -->
<bean id="user2" class="ioc2.User2Factory" factory-method="getUser2"/>
</beans>

测试代码如下:

package introduction;
import ioc1.User;
import ioc2.User2;
import ioc3.User3;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * 测试配置方式实现IOC三种方法
 * 
 * @author 吴晓畅
 *
 */
public class TestIoc {
 
 @Test
 public void testUser() {
 
 //加载spring配置文件,根据内容创建对象
 ApplicationContext context = new ClassPathXmlApplicationContext("myXml.xml");
 
 //方法2:使用静态工厂方法--了解就好
 User2 user2 = (User2) context.getBean("user2");
 System.out.println(user2);
 
 }
 
}

运行结果如下:

方式三:实例工厂(了解就好)

创建测试类User3.java

package ioc3;

public class User3 {

public void add() {

System.out.println("user3........");

}

}

创建测试工厂类User3Factory.java

package ioc3;
public class User3Factory {
 
 //普通方法,返回User3对象
 public User3 getUser3()
 {
 
 return new User3();
 
 }
}

src下创建配置文件myXml.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" >
<!--
 本来在该配置文件中还有一个属性name,其作用和id一样,id属性值不能包含特殊符号(比如@#),但是name属性可以,不过name属性是很早版本时候使用的,现在都被替换成id了 
 scope属性,总共有以下的值,但是最主要用的是前两个,后面两个不用记得,在该配置文件中我们没有写scope属性,默认使用的是singleton
 singleton:默认值、单例的
 prototype:多例的:
 request:创建对象,把对象放在request域里面
 session:创建对象,把对象放在session域里面
 globalSession:创建对象,把对象放在globalSession域里面
 -->
<!-- 第三种方法:使用实例工厂创建对象 -->
<!-- 创建工厂类的对象 -->
<bean id="User3Factory" class="ioc3.User3Factory"></bean>
<bean id="user3" factory-bean="User3Factory" factory-method="getUser3"></bean>
</beans>

测试代码如下:

package introduction;

import ioc3.User3;

import org.junit.Test;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**

* 测试配置方式实现IOC三种方法

*

* @author 吴晓畅

*

*/

public class TestIoc {

@Test

public void testUser() {

//加载spring配置文件,根据内容创建对象

ApplicationContext context = new ClassPathXmlApplicationContext("myXml.xml");

//方法3:使用实例工厂创建对象--了解就好

User3 user3 = (User3) context.getBean("user3");

System.out.println(user3);

}

}

运行结果如下:

4. 注解方式实现

方式一:实现对象创建

创建测试类:UserBean1.java

/**
 * 采用注解方式完成ioc
 *
 * @author 吴晓畅
 *
 */
//目前spring有四个注解,功能都是一样的,都是创建对象用的
//@Component @Controller @Service @Repository
@Component(value="userBean1")//这个相当于<bean id="user" class=""/>
public class UserBean1 {
 public void add() {
 System.out.println("UserBean1....add");
 }
}

src下创建配置文件bean.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:context="http://www.springframework.org/schema/context" 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"> <!-- bean definitions here -->
 <!-- 
 开启注解扫描 base-package写的包名 如果类在多个包里面,那么写的方式如下
 ioc_bean1,ioc_bean2,ioc_bean3...
 或者采用cn 这样表示加载cn开头的所有包 cn.ioc则表示加载 cn.ioc开头的所有包
 -->
 
 <!-- 到包里面扫描类、方法、属性上面注解 -->
 <context:component-scan base-package="ioc_bean1"></context:component-scan>
 <!-- 只扫描属性上面的注解 -->
 <!--<context:annotation-config></context:annotation-config> -->
 
</beans>

测试代码如下

package introduction;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ioc_bean1.UserBean1;
import ioc_bean1.UserService;;
/**
 * 测试bean方式使用IOC
 * 
 * @author 吴晓畅
 *
 */
public class TestBean {
 
 @Test
 public void testUser() {
 
 //加载spring配置文件,根据内容创建对象
 ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
 
 //测试bean配置方式创建对象
 UserBean1 userBean1 = (UserBean1) context.getBean("userBean1");
 System.out.println(userBean1);
 userBean1.add();
 }
}

运行结果如下

方式二:bean配置方式注入对象属性

创建测试类:UserDao.java

import org.springframework.stereotype.Component;
//这一步相当于创建了UserDao对象
@Component(value="userDao")
public class UserDao {
 
 public void add() {
 
 System.out.println("UserDao....add");
 }
}

编写测试类UserService.java

package ioc_bean1;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
//@Service(value="userService")等同于@Service("userService")
//这一步相当于创建了UserService对象
@Service(value="userService")
public class UserService {
 
 //得到UserDao对象 使用注解方式时候不需要使用set方法,直接到UserDao对象上面使用注解,完成对象注入
 //即@Autowired用于注入属性
 @Autowired
 private UserDao userDao;
 
 //这种方式与上面方式能达到同样效果 常用的是下面这种
 //name的值要与UserDao的注解的value一致
// @Resource(name="userDao")
// private UserDao userDao;
 
 public void add() {
 
 System.out.println("UserService...add....");
 
 userDao.add();
 }
}

src下配置方式与方式一一致

测试代码如下:

package introduction;

import ioc_bean1.UserBean1;

import ioc_bean1.UserService;

import org.junit.Test;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

;

/**

* 测试bean方式使用IOC

*

* @author 吴晓畅

*

*/

public class TestBean {

@Test

public void testUser() {

//加载spring配置文件,根据内容创建对象

ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

//测试bean配置方式注入对象属性

UserService userService = (UserService) context.getBean("userService");

userService.add();

}

}

运行结果如下

温馨提示:

上面注解方式需要在使用类上面加上@Component(value="userDao"),在对象变量上使用@Autowired才能实现。

方式三:配置文件与注解方式混合使用

创建测试类BookDao.java

package ioc_bean2;
public class BookDao {
 
 public void book() {
 
 System.out.println("BookDao....book");
 }
}

创建测试类OrderDao.java

package ioc_bean2;

public class OrderDao {

public void buy() {

System.out.println("OrderDao....buy");

}

}

创建测试服务BookService.java,在该服务中通过注解方式注入对象属性

package ioc_bean2;

import javax.annotation.Resource;

public class BookService {

//得到BookDao和OrderDao的对象

@Resource(name="bookDao")

private BookDao bookDao;

@Resource(name="orderDao")

private OrderDao orderDao;

public void add() {

System.out.println("service");

bookDao.book();

orderDao.buy();

}

}

src下配置文件如下

<?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:context="http://www.springframework.org/schema/context" 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"> <!-- bean definitions here -->
 <!-- 
 开启注解扫描 base-package写的包名 如果类在多个包里面,那么写的方式如下
 ioc_bean1,ioc_bean2,ioc_bean3...
 或者采用cn 这样表示加载cn开头的所有包 cn.ioc则表示加载 cn.ioc开头的所有包
 -->
 
 <!-- 到包里面扫描类、方法、属性上面注解 -->
 <context:component-scan base-package="ioc_bean2"></context:component-scan>
 <!-- 配置对象 -->
 <bean id="bookService" class="ioc_bean2.BookService"></bean>
 <bean id="bookDao" class="ioc_bean2.BookDao"></bean>
 <bean id="orderDao" class="ioc_bean2.OrderDao"></bean>
 
</beans>

测试代码如下

package ioc_bean2;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ioc1.User;
import ioc2.User2;
import ioc3.User3;
public class TestMixBean {
 
 @Test
 public void testService() {
 
 //加载spring配置文件,根据内容创建对象
 ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
 BookService bookService = (BookService) context.getBean("bookService");
 
 bookService.add();
 
 }
}

运行结果如下

方式四:带参数的属性注入

创建数组、集合、properties属性注入测试类Person.java

package property;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
 * 测试数组、集合、properties属性注入
 * 
 * @author 吴晓畅
 *
 */
public class Person {
 
 private String pName;
 
 private String[] arrs;
 
 private List<String> list;
 
 private Map<String, String> map;
 
 private Properties properties;
 public void setpName(String pName) {
 this.pName = pName;
 }
 public void setArrs(String[] arrs) {
 this.arrs = arrs;
 }
 public void setList(List<String> list) {
 this.list = list;
 }
 public void setMap(Map<String, String> map) {
 this.map = map;
 }
 public void setProperties(Properties properties) {
 this.properties = properties;
 }
 
 public void test1() {
 
 System.out.println("pName--"+pName);
 
 System.out.println("arrs--"+arrs);
 
 System.out.println("list--"+list);
 
 System.out.println("map--"+map);
 
 System.out.println("properties--"+properties);
 }
 
}

创建构造方法注入属性测试类PropertyDemo1.java

package property;

/**

* 通过构造方法注入属性

*

* @author 吴晓畅

*

*/

public class PropertyDemo1 {

private String userName;

public PropertyDemo1(String userName) {

this.userName = userName;

}

public void test1() {

System.out.println("demo1......"+userName);

}

}

创建set方法注入属性测试类PropertyDemo2.java

package property;
/**
 * 通过set方法注入属性
 * 
 * @author 吴晓畅
 *
 */
public class PropertyDemo2 {
 
 private String bookName;
 
 //set方法 该方法一定要符合set/get方法命名规则
 public void setBookName(String bookName) {
 this.bookName = bookName;
 }
 
 public void demoBookName()
 {
 
 System.out.println("book..."+bookName);
 
 }
}
 

对象属性注入测试类UserDao.java

package property;
/**
 * 对象属性注入
 * 
 * @author 吴晓畅
 *
 */
public class UserDao {
 public void add() {
 
 System.out.println("UserDao.....dao");
 }
}

对象属性注入测试类UserService.java

package property;
/**
 * 对象属性注入
 * 
 * @author 吴晓畅
 *
 */
public class UserService {
 
 //定义UserDao属性
 private UserDao dao;
 //定义set方法
 public void setDao(UserDao dao) {
 this.dao = dao;
 }
 
 public void add() {
 
 System.out.println("UserService...service");
 
 //使用dao对象
 dao.add();
 }
 
 
}

src下配置文件myXml.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" >
<!--
 本来在该配置文件中还有一个属性name,其作用和id一样,id属性值不能包含特殊符号(比如@#),但是name属性可以,不过name属性是很早版本时候使用的,现在都被替换成id了 
 scope属性,总共有以下的值,但是最主要用的是前两个,后面两个不用记得,在该配置文件中我们没有写scope属性,默认使用的是singleton
 singleton:默认值、单例的
 prototype:多例的:
 request:创建对象,把对象放在request域里面
 session:创建对象,把对象放在session域里面
 globalSession:创建对象,把对象放在globalSession域里面
 -->
<!-- 使用有参数构造注入属性 -->
<bean class="property.PropertyDemo1" id="demo">
<!-- 表示给userName属性注入value为小王的值 -->
<constructor-arg value="小王" name="userName"> </constructor-arg>
</bean>
<!-- 使用set方法注入属性 -->
<!--这一步相当于创建了 property.PropertyDemo2类的对象-->
<bean class="property.PropertyDemo2" id="demo2">
<!-- 表示给userName属性注入value为小王的值 -->
<property value="易筋经" name="bookName"> </property>
</bean>
<!-- 注入对象类型的属性 -->
<!-- 配置UserService和UserDao的对象 -->
<bean id="userDao" class="property.UserDao"></bean>
<bean id="userService" class="property.UserService">
 <!-- 注入dao对象 现在不要写value属性,因为上面是字符串,现在是对象,使用ref属性,dao配置bean标签中的id值-->
 <property name="dao" ref="userDao"></property>
</bean>
<!-- 注入复杂类型属性-》包括数组、集合等 -->
<bean id="person" class="property.Person">
 
 <!-- 数组 -->
 <property name="arrs">
 <list>
 <value>小王</value>
 <value>小马</value>
 <value>小宋</value>
 </list>
 </property>
 
 <!-- list集合 -->
 <property name="list">
 <list>
 <value>小奥</value>
 <value>小金</value>
 <value>小普</value>
 </list>
 </property>
 
 <!-- map集合 -->
 <property name="map">
 <map>
 <entry key="aa" value="Lucy"></entry>
 <entry key="bb" value="Mary"></entry>
 <entry key="cc" value="Tom"></entry>
 </map>
 </property>
 
 <!-- properties -->
 <property name="properties">
 <props>
 <prop key="driverclass">com.mysql.jdbc.Driver</prop>
 <prop key="username">root</prop>
 </props>
 </property>
</bean>
</beans>

测试代码如下

package property;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * 测试属性注入
 * 
 * @author 吴晓畅
 *
 */
public class TestIoc {
 
 @Test
 public void textUser() {
 
 //加载spring配置文件,根据内容创建对象
 ApplicationContext context = new ClassPathXmlApplicationContext("myXml.xml");
 
 //测试构造注入属性的方法
 //得到配置创建的对象
 PropertyDemo1 demo1 = (PropertyDemo1)context.getBean("demo");
 demo1.test1();
 
 //测试set方法注入属性值
 PropertyDemo2 demo2 = (PropertyDemo2)context.getBean("demo2");
 demo2.demoBookName();
 
 //测试使用set方法注入对象属性
 UserService userService = (UserService) context.getBean("userService");
 userService.add();
 
 //使用set方法注入复杂属性对象
 Person person = (Person) context.getBean("person");
 person.test1();
 }
}

运行结果如下

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
demo1......小王
book...易筋经
UserService...service
UserDao.....dao
pName--null
arrs--[Ljava.lang.String;@28ac3dc3
list--[小奥, 小金, 小普]
map--{aa=Lucy, bb=Mary, cc=Tom}
properties--{driverclass=com.mysql.jdbc.Driver, username=root}
Process finished with exit code 0

三、IoC常见注解总结

  1. 创建对象(相当于:<bean id="" class="">)
  2. (1)@Component
  3. 作用:把资源让spring来管理。相当于在xml中配置一个bean。
  4. 属性:value:指定bean的id。如果不指定value属性,默认bean的id是当前类的类名。首字母小写。

(2)@Controller @Service @Repository

他们三个注解都是针对一个的衍生注解,他们的作用及属性都是一模一样的。

他们只不过是提供了更加明确的语义化。

@Controller:一般用于表现层的注解。

@Service:一般用于业务层的注解。

@Repository:一般用于持久层的注解。

细节:如果注解中有且只有一个属性要赋值时,且名称是value,value在赋值是可以不写。

  1. 用于注入数据(相当于:<property name="" ref=""> <property name="" value="">)
  2. (1)@Autowired
  3. 作用:自动按照类型注入。当使用注解注入属性时,set方法可以省略。它只能注入其他bean类型。当有多个类型匹配时,使用要注入的对象变量名称作为bean的id,在spring容器查找,找到了也可以注入成功。找不到就报错。
  4. (2)@Resource
  5. 作用:直接按照Bean的id注入。它也只能注入其他bean类型。
  6. 属性:name:指定bean的id。
  7. (3)@Value
  8. 作用:注入基本数据类型和String类型数据的
  9. 属性:value:用于指定值

欢迎工作一到五年的Java工程师朋友们加入Java程序员开发: 721575865

群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表