Spring 初识
IoC控制反转
- IoC控制反转,全称Inverse of Control,是一种设计理念
- 由代理人创建与管理对象,消费者通过代理人来获得对象
- IoC的目的是降低程序与程序之间直接耦合
解决什么问题?
对象直接饮用导致对象硬性关联,程序难以扩展。比如:顾客想要吃各种各样的苹果,就需要顾客取世界各地购买苹果,非常麻烦。
加入Ioc容器将对象统一管理,让对象关联变为弱耦合。就像上面的场景:如果出现了摊贩,那么顾客就不用跑到各地去买水果了,而是由摊贩购买好之后,顾客根据自己的需求购买响应的苹果。
DI依赖注入
- IoC是设计理念,是现代程序设计遵循的标准,是宏伟目标
- DI(Dependency Injection) 是具体技术实现,是微观实现
- DI在Java中利用反射技术是心啊对象注入(Injection)
Spring
含义
- Spring可以从狭义与广义两个角度看
- 狭义角度的Spring是指Spring框架(Spring Fremework)
- 广义角度Spring是指Spring生态体系
狭义的Spring框架
- Spring框架是企业开发复杂性的恶一站式解决方案
- SPring框架的核心是IoC容器与AOP面向切面编程
- Spring IoC负责创建与管理系统对象,并在此基础上扩展功能
广义Spring生态体系
Spring IoC容器
IoC容器是Spring生态的地基,用于统一创建与管理对象依赖
如上:Spring IoC容器将A的依赖B对象注入进来,使用者只需要从中提取就可以了。
Spring IoC容器职责
- 对象的控制权交由第三方统一管理(IoC控制反转)
- 利用Java反射技术实现运行时对象创建与关联(DI依赖注入)
- 基于配置提高应用程序的可维护性与扩展性
Spring IoC初体验
eg: 比如三个孩子 Lily、Andy、Luna分别喜欢吃甜的、酸的、软的苹果。盘子里有三个苹果:红富士、青苹果、金帅。
那孩子们如何获得喜欢的苹果呢?
Apple.class
package com.imooc.spring.ioc.entity;
public class Apple {
private String title;
private String color;
private String origin;
public Apple() {
//System.out.println("Apple对象已经创建, " + this);
}
public Apple(String title, String color, String origin) {
this.title = title;
this.color = color;
this.origin = origin;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
}
Child.class
package com.imooc.spring.ioc.entity;
public class Child {
private String name;
private Apple apple;
public Child() {
}
public Child(String name, Apple apple) {
this.name = name;
this.apple = apple;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Apple getApple() {
return apple;
}
public void setApple(Apple apple) {
this.apple = apple;
}
public void eat() {
System.out.println(name + "吃到了" + apple.getOrigin() + "种植的" + apple.getTitle());
}
}
传统方式:
Application.class
public class Application {
public static void main(String[] args) {
Apple apple1 = new Apple("红富士", "红色", "欧洲");
Apple apple2 = new Apple("青苹果", "绿色", "中亚");
Apple apple3 = new Apple("金帅", "黄色", "中国");
Child lily = new Child("莉莉", apple1);
Child andy = new Child("安迪", apple2);
Child luna = new Child("露娜", apple3);
lily.eat();
andy.eat();
luna.eat();
}
}
输出结果:
但是这样会有一个弊端,如果后面需要更改喜欢的苹果的时候,我们就需要在源代码这里修改。在开发过程中,往往源代码涉及到的内容较多,修改复杂且容易影响其他地方,很容易出错。
使用IoC容器方式:
在resources目录下创建applicationontext.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 在IoC容器启动时,自动由Spring实例化Apple对象,取名sweetApple放入到容器中 -->
<bean id="sweetApple" class="com.imooc.spring.ioc.entity.Apple">
<property name="title" value="红富士"></property>
<property name="origin" value="欧洲"></property>
<property name="color" value="红色"></property>
</bean>
<bean id="sourApple" class="com.imooc.spring.ioc.entity.Apple">
<property name="title" value="青苹果"></property>
<property name="origin" value="中亚"></property>
<property name="color" value="绿色"></property>
</bean>
<bean id="softApple" class="com.imooc.spring.ioc.entity.Apple">
<property name="title" value="金帅"></property>
<property name="origin" value="中国"></property>
<property name="color" value="黄色"></property>
</bean>
<bean id="rdApple" class="com.imooc.spring.ioc.entity.Apple">
<property name="title" value="蛇果"></property>
<property name="origin" value="美国"></property>
<property name="color" value="红色"></property>
</bean>
<bean id="lily" class="com.imooc.spring.ioc.entity.Child">
<property name="name" value="莉莉"/>
<property name="apple" ref="softApple"/>
</bean>
<bean id="andy" class="com.imooc.spring.ioc.entity.Child">
<property name="name" value="安迪"/>
<property name="apple" ref="rdApple"/>
</bean>
<bean id="luna" class="com.imooc.spring.ioc.entity.Child">
<property name="name" value="露娜"/>
<property name="apple" ref="sweetApple"/>
</bean>
</beans>
SpringApplication.class
public class SpringApplication {
public static void main(String[] args) {
//创建Spring IoC容器,并根据配置文件在容器中实例化对象
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Apple sweetApple = context.getBean("sweetApple", Apple.class);
System.out.println(sweetApple.getTitle());
//从IoC容器中提取beanId=lily的对象
Child lily = context.getBean("lily", Child.class);
lily.eat();
Child andy = context.getBean("andy", Child.class);
andy.eat();
Child luna = context.getBean("luna", Child.class);
luna.eat();
}
}
执行结果:
这样做的好处是,我们涉及到修改时,只需要修改applicationContent.xml中的内容即可.
使用XML方式实现Spring IoC
上面说的applicationContext.xml就是使用xml文件方式实现Spring IoC的一种。
实现方式:
- 基于构造方法对对象实例化
- 基于动态工厂实例化
- 基于工厂实例方法实例化
Spring框架组成模块
ApplicationContext实现类
- ClassPathXmlApplicationContext
- AnnotationConfigApplicationContext
- WebApplicationContext
基于构造方法对对象实例化
默认构造方法
在 applicationContext.xml文件:
<!--bean标签默认通过默认构造方法创建对象-->
<bean id="apple1" class="com.imooc.spring.ioc.entity.Apple">
Apple.class 中修改为:
public Apple() {
System.out.println("Apple对象已创建," + this);
}
打印输出:
带参构造方法
applicationContext.xml
<!--使用带参构造方法实例化对象-->
<bean name="apple2" class="com.imooc.spring.ioc.entity.Apple">
<constructor-arg name="title" value="红富士"/>
<constructor-arg name="color" value="红色"/>
<constructor-arg name="origin" value="欧洲"/>
<constructor-arg name="price" value="19.8"/>
</bean>
<bean id="apple3" class="com.imooc.spring.ioc.entity.Apple">
<constructor-arg index="0" value="红富士"/>
<constructor-arg index="1" value="欧洲"/>
<constructor-arg index="2" value="红色"/>
<constructor-arg index="3" value="19.8"/>
</bean>
Apple.class
public Apple(String title, String origin, String color, Float price) {
System.out.println("通过带参构造方法创建对象, " + this);
this.title = title;
this.color = color;
this.origin = origin;
this.price = price;
}
打印输出:
基于工厂实例化对象
静态工厂
/**
* 静态工厂通过静态方法创建对象,隐藏创建对象的细节
*/
public class AppleStaticFactory {
public static Apple createSweetApple(){
//logger.info("")
Apple apple = new Apple();
apple.setTitle("红富士");
apple.setOrigin("欧洲");
apple.setColor("红色");
return apple;
}
}
在applicationContext.xml中
<!--利用静态工厂获取对象-->
<bean id="apple4" class="com.imooc.spring.ioc.factory.AppleStaticFactory"
factory-method="createSweetApple"/>
测试:
Apple apple4 = context.getBean("apple4", Apple.class);
System.out.println(apple4.getTitle());
输出:
工厂实例
/**
* 工厂实例方法创建对象是指IoC容器对工厂类进行实例化并调用对应的实例方法创建对象的过程
*/
public class AppleFactoryInstance {
public Apple createSweetApple(){
Apple apple = new Apple();
apple.setTitle("红富士");
apple.setOrigin("欧洲");
apple.setColor("红色");
return apple;
}
}
在applicationContext.xml中
<!--利用工厂实例方法获取对象-->
<bean id="factoryInstance" class="com.imooc.spring.ioc.factory.AppleFactoryInstance"/>
<bean id="apple5" factory-bean="factoryInstance" factory-method="createSweetApple"/>
然后测试输出即可.
从IoC容器中提取 Bean
方式一:
Apple apple = context.getBean("apple", Apple.class);
方式二:
Apple apple = (Apple)context.getBean("apple");
推荐使用方式一
id和name属性相同点
- bean id 与 name 都是设置对象在IoC容器中唯一标识
- 两者在同一个配置文件中都不允许出现重复
- 两者允许在多个配置文件中出现重复,新对象覆盖旧对象
id和name属性不同点
- id要求更为严格,一次只能定义一个对象标识(推荐)
- name更为宽松,一次允许定义多个对象标识
- tips:id与name的命名要求有意义,按驼峰命名书写
<bean name="apple2,apple7" class="com.imooc.spring.ioc.entity.Apple">
<constructor-arg name="title" value="红富士2号"/>
<constructor-arg name="color" value="红色"/>
<constructor-arg name="origin" value="欧洲"/>
<constructor-arg name="price" value="29.8"/>
</bean>
<!-- 没有id与name的bean默认使用类名全称作为bean标识 -->
<bean class="com.imooc.spring.ioc.entity.Apple">
<constructor-arg name="title" value="红富士3号"/>
<constructor-arg name="color" value="红色"/>
<constructor-arg name="origin" value="欧洲"/>
<constructor-arg name="price" value="29.8"/>
</bean>
路径匹配表达式
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
加载多个配置文件:
String[] configLocations = new String[]{"classpath:applicationContext.xml","classpath:applicationContext-1.xml"};
//初始化IoC容器并实例化对象
ApplicationContext context = new ClassPathXmlApplicationContext(configLocations);
路径表达式
对象依赖注入
依赖注入是指运行时将容器内对象利用反射赋给其他对象的操作
- 基于setter方法注入对象
- 基于构造方法注入对象
基于setter方法注入对象
<property name="" value=""/> : 静态属性
<property name=""ref=""/> : 动态属性
<bean id="sweetApple" class="com.imooc.spring.ioc.entity.Apple">
<!-- IoC容器自动利用反射机制运行时调用setXXX方法为属性赋值 -->
<property name="title" value="红富士"/>
<property name="color" value="红色"/>
<property name="origin" value="欧洲"/>
<property name="price" value="19.8"/>
</bean>
<bean id="lily" class="com.imooc.spring.ioc.entity.Child">
<property name="name" value="莉莉"/>
<!-- 利用ref注入依赖对象 -->
<property name="apple" ref="sweetApple"/>
</bean>
依赖注入的优势
举例:
applicationContext-dao.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="com.imooc.spring.ioc.bookshop.dao.BookDaoOracleImpl">
</bean>
</beans>
applicationContext-serice.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookService" class="com.imooc.spring.ioc.bookshop.service.BookService">
<!--id=bookDao-->
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
BookDao
public interface BookDao {
public void insert();
}
BookDaoImpl
public class BookDaoImpl implements BookDao {
public void insert() {
System.out.println("向MySQL Book表插入一条数据");
}
}
BookService
public class BookService {
private BookDao bookDao ;
public void purchase(){
System.out.println("正在执行图书采购业务方法");
bookDao.insert();
}
public BookDao getBookDao() {
return bookDao;
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
BookShopApplication
public class BookShopApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext-*.xml");
BookService bookService = context.getBean("bookService", BookService.class);
bookService.purchase();
}
}
输出:
优点: 比如数据库由MySQL迁移到Oracle.这时候只需要将BookDao这个Bean的class更改为Oracle即可.
注入集合对象
注入List
注入set
注入map
注入Properties
Properties中的key和value只能是String类型
查看容器内对象
//获取容器内所有beanId数组
String[] beanNames = context.getBeanDefinitionNames();
for (String beanName:beanNames){
System.out.println(beanName);
System.out.println("类型:" + context.getBean(beanName).getClass().getName());
System.out.println("内容:" + context.getBean(beanName));
}
Computer computer = context.getBean("com.imooc.spring.ioc.entity.Computer", Computer.class);
当有多个相同类的Bean时:
<bean class="com.imooc.spring.ioc.entity.Computer">
<constructor-arg name="brand" value="微星"/>
<constructor-arg name="type" value="台式机"/>
<constructor-arg name="sn" value="8389280012"/>
<constructor-arg name="price" value="3000"/>
</bean>
<bean class="com.imooc.spring.ioc.entity.Computer">
<constructor-arg name="brand" value="华硕"/>
<constructor-arg name="type" value="笔记本"/>
<constructor-arg name="sn" value="9089380012"/>
<constructor-arg name="price" value="6000"/>
</bean>
Computer computer1 = context.getBean("com.imooc.spring.ioc.entity.Computer#0", Computer.class);
Computer computer1 = context.getBean("com.imooc.spring.ioc.entity.Computer#1", Computer.class);
Bean对像的作用域及生命周期
bean scope属性
- bean scope属性用于决定对象何时被创建与作用范围
- bean scope配置将影响容器内对象的数量
- 默认情况下bean会在IoC容器创建后自动实例化,全局唯一
scope用法
bean scope属性清单
singleton的线程安全问题
singleton在容器是单例多线程执行,存在线程安全风险
在单线程下:
当在多线程中:在A用户操作了a.setNum(1)之后,在另一个线程,B用户操了a.setNum(2) .这个时候,A用户打印a.num 就会出现 2,和A用户设置值不同.
prototype多例
prototype在容器中多实例,占用更多资源,不存在线程安全问题
singleton和prototype对比
bean生命周期
细节调整
- prototype使对象创建与init_method延迟至执行业务
- prototype使对象不再受IoC容器管理,不再触发destroy-method
- 延迟加载lazy-init属性可让对象创建与初始化延迟到执行代码阶段
生命周期在实战中的应用
singleton和prototype的初始化
singleton的初始化:
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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.imooc.spring.ioc.dao.UserDao"/>
</beans>
在SpringApplication中添加:
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
}
打印输出:
也就是说在IoC容器初始化的时候,为我们创建了bean.
prototype初始化:
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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.imooc.spring.ioc.dao.UserDao" scope="prototype"/>
</beans>
然后在SpringApplication中测试:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
System.out.println("======IoC容器已初始化=======");
UserDao userDao1 = context.getBean("userDao", UserDao.class);
}
输出为:
也就是说,在IoC容器创建的时候,并没有为我们初始化bean对像,而是在我们获取对象的时候,才初始化.singleton模式的初始化顺序跟书写顺序一致。
下面会初始化两个bean对象.
<bean id="userDao" class="com.imooc.spring.ioc.dao.UserDao" scope="prototype"/>
<bean id="userService" class="com.imooc.spring.ioc.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
在多数情况下,Dao层,server层,control层都是单例的。
实现极简IoC容器
目录结构:
Apple类
package com.imooc.spring.ioc.entity;
public class Apple {
private String title;
private String color;
private String origin;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
}
然后在applicationContext.xml中添加bean的信息:
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="sweetApple" class="com.imooc.spring.ioc.entity.Apple">
<property name="title" value="红富士"/>
<property name="color" value="红色"/>
<property name="origin" value="欧洲"/>
</bean>
</beans>
自定义记载配置文件的方法:
接口:ApplicationContext
package com.imooc.spring.ioc.context;
public interface ApplicationContext {
public Object getBean(String beanId);
}
实现类ClassPathXmlApplicationContext
public class ClassPathXmlApplicationContext implements ApplicationContext {
private Map iocContainer = new HashMap();
/**
* 读取配置文件
*/
public ClassPathXmlApplicationContext() {
try {
String filePath = this.getClass().getResource("/applicationContext.xml").getPath();
// 进行URL解码
filePath = new URLDecoder().decode(filePath, "UTF-8");
/**
* 引入 dom4j和jaxen
* Dom4j是Java的XML解析组件
* Jaxen是Xpath表达式解释器
*/
SAXReader reader = new SAXReader();
Document document = reader.read(new File(filePath));
// 读取 "bean" 标签
List<Node> beans = document.getRootElement().selectNodes("bean");
for (Node node : beans) {
Element ele = (Element) node;
String id = ele.attributeValue("id");
String className = ele.attributeValue("class");
Class c = Class.forName(className);
Object obj = c.newInstance();
List<Node> properties = ele.selectNodes("property");
for (Node p : properties) {
Element property = (Element) p;
String propName = property.attributeValue("name");
String propValue = property.attributeValue("value");
// set方法
String setMethodName = "set" + propName.substring(0, 1).toUpperCase() + propName.substring(1);
System.out.println("准备执行" + setMethodName + "方法注入数据");
Method setMethod = c.getMethod(setMethodName, String.class);
setMethod.invoke(obj, propValue);//通过setter方法注入数据
}
iocContainer.put(id, obj);
}
System.out.println(iocContainer);
System.out.println("IOC容器初始化完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
public Object getBean(String beanId) {
return iocContainer.get(beanId);
}
}
然后进行测试:
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext();
Apple apple = (Apple)context.getBean("sweetApple");
System.out.println(apple);
}
}
执行结果:
使用注解方式实现Spring IoC
基于注解的优势
- 摆脱繁琐的XML形式的bean与依赖注入配置
- 基于“声明式”的原则,更适合轻量级的现代企业应用
- 让代码可读性变得更好,研发人员拥有更好的开发体验
三类注解:
- 组件类型注解 - 声明当前类的功能与职能
- 自动装配注解 - 根据属性特种自动注入对象
- 元数据注解 - 更细化的辅助IoC容器管理对象的注解
四种组件类型注解
- @Compponent : 组件注解,通用注解,被该注解描述的类将被IoC容器管理并实例化
- @Controller : 语义注解,说明当前类是MVC应用中的控制器类
- @Service. : 语义注解,说明当前类是Service业务服务类
- @Repository. : 语义注解,说明当前类是用于业务持久层,通常描述对应Dao类
两类自动装配注解
- 按类型装配
- @Autowired : 按容器内对象类型动态注入属性,由Spring机构提供
- @Inject : 基于JSR-330(Dependency Injection for Java)标准,其他同@Autowired,但不支持required属性
- 按名称装配
- @Named : 与@Inject配合使用,JSR-330规范,按属性名自动装配属性
- @Resource :基于JSR-330规范,优先按名称、再按类型智能匹配
元数据注解
@Value 的读取属性文件
@Value("com.imooc")
private String config;
就是相当于在初始化的时候,config的值为"com.inooc",主要用户加载配置文件中的数据: @Value("${config}")
使用Java Config方式实现Spring IoC
基于Java Config的优势
- 完全摆脱XML的束缚,使用独立Java类管理对象与依赖
- 注解配置相对分散,利用Java Config可对配置集中管理
- 可以在编译时进行依赖检查,不容易出错
Java Config核心注解
Java Config初始化方式
举例:
package com.imooc.spring.ioc;
import com.imooc.spring.ioc.controller.UserController;
import com.imooc.spring.ioc.dao.EmployeeDao;
import com.imooc.spring.ioc.dao.UserDao;
import com.imooc.spring.ioc.service.UserService;
import org.springframework.context.annotation.*;
@Configuration //当前类是一个配置类,用于替代applicationContext.xml
@ComponentScan(basePackages = "com.imooc")
public class Config {
@Bean //Java Config利用方法创建对象,将方法返回对象放入容器,beanId=方法名
public UserDao userDao(){
UserDao userDao = new UserDao();
System.out.println("已创建" + userDao);
return userDao;
}
@Bean //Java Config利用方法创建对象,将方法返回对象放入容器,beanId=方法名
@Primary
public UserDao userDao1(){
UserDao userDao = new UserDao();
System.out.println("已创建" + userDao);
return userDao;
}
@Bean
//先按name尝试注入,name不存在则按类型注入
public UserService userService(UserDao udao , EmployeeDao employeeDao){
UserService userService = new UserService();
System.out.println("已创建" + userService);
userService.setUserDao(udao);
System.out.println("调用setUserDao:" + udao);
userService.setEmployeeDao(employeeDao);
return userService;
}
@Bean //<bean id="xxx" clas="xxx">
@Scope("prototype")
public UserController userController(UserService userService){
UserController userController = new UserController();
System.out.println("已创建" + userController);
userController.setUserService(userService);
System.out.println("调用setUserService:" + userService);
return userController;
}
}
测试:
public class SpringApplication {
public static void main(String[] args) {
//基于Java Config配置IoC容器的初始化
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
System.out.println("=========================");
String[] ids = context.getBeanDefinitionNames();
for(String id : ids){
System.out.println(id + ":" + context.getBean(id));
}
}
}