本篇文章从Spring框架的概念入手,完整介绍Spring框架的特性和一些Spring一些底层的实现原理,详细介绍Spring的特性IOC特性和Spring的三种不听的注入方式,并分析其特点.最后总结Spring IOC的特点和Spring工厂的特性
文章目录
引言
- 传统web开发存在硬编码所造成的过度程序耦合(例如:Service中作为属性Dao对象)
- 邠Java EE API较为复杂,使用效率低(例如:JDBC开发步骤)
- 侵入性强,移植性差(例如:DAO实现的更换,从Connection到SqlSession).
Spring框架
概念
- Spring是一个项目管理框架,同时也是一套Java EE解决方案
- Spring是众多优秀设计模式的组合(工厂,单例,代理,适配器,装饰设计模式,观察者,模板,策略)
- Spring并未代替现有框架产品,而是将众多框架进行有机整合,简化企业级开发,俗称"胶水框架"
Spring架构组成
Spring架构组成 |
---|
搭建一个自定义的工厂
自己搭建一个自定义的工厂,了解一下工厂的运行原理是怎么样的
分析:
- 为了测试我们的Spring的自动创建对象,所以需要创建一个实体类
- 需要配置文件来管理和指明我们要管理的实体类都有哪些
- 需要创建一个工厂提供一个方法来为我们创建实体类的对象,而不是我们手工的去创建
最终的项目结构 |
---|
实体类代码
Userdao接口代码
package per.leiyu.dao;
/** * @author 雷雨 * @date 2020/6/19 15:03 */
public interface UserDao {
void deleteUser(Integer id);
}
UserDaoImpl代码
package per.leiyu.dao.daoImpl;
import per.leiyu.dao.UserDao;
/** * @author 雷雨 * @date 2020/6/19 15:10 */
public class UserDaoImpl implements UserDao {
@Override
public void deleteUser(Integer id) {
System.out.println("User的删除方法");
}
}
service的代码基本上和UserDao相同,因为我们只是为了简单实现spring的自动创建实体类对象功能,这里只列举一个测试对象就可以了
配置文件bean.properties
userDao=per.leiyu.dao.daoImpl.UserDaoImpl
userService=per.leiyu.service.serviceImpl.UserServiceImpl
测试类代码
package per.leiyu.factoryTest;
import per.leiyu.dao.UserDao;
import per.leiyu.factory.Myfactory;
import java.io.IOException;
/** * @author 雷雨 * @date 2020/6/19 15:30 */
public class MyfactoryTest {
public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException {
//1.创建一个工厂对象
Myfactory myfactory = new Myfactory("/bean.properties");
//2.从工厂中获取对象
UserDao userDao = (UserDao)myfactory.getBean("userDao");
userDao.deleteUser(1);
}
}
自定义工厂获取实体类对象的测试结果 |
---|
思路分析:
- 配置文件提供了实体类和给定的一个
name
值,告诉我们用name值可以找到对应实体类的路径- 工厂先加载配置文件,然后提供一个方法来获取配置文件中name值来用
反射机制
创建对应的实体类
在maven项目中搭建spring环境
先创建好一个maven项目
- 第一步需要导入spring的依赖
- 创建Spring的配置文件
- 创建实体类(创建实体类后要在配置文件中对要创建对象的实体类"注册")
- 测试
spring的依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
创建Spring的配置文件
命名无限制,约定俗称的命名有:spring-context.xml applicationContext.xml beans.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-3.0.xsd">
<!--要工厂生产的对象 使用的是xml格式的配置方式 -->
<bean id="userDao" class="per.leiyu.dao.daoImpl.UserDaoImpl"></bean>
</beans>
id
:标识class
:要生产的类的路径
仍然使用上面自定义工厂的实体类就可以
测试
package per.leiyu.factoryTest;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import per.leiyu.dao.UserDao;
/** * @author 雷雨 * @date 2020/6/19 16:01 */
public class SpringFactoryTest {
@Test
public void testSpringFactory(){
//启动工厂
//加载了配置文件(用的实现类) 返回值用接口接收
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
//获取对象
UserDao userDao = (UserDao) context.getBean("userDao");
userDao.deleteUser(1);
}
}
Spring简单实现自动创建实体类对象测试结果 |
---|
- 我们能调到那个实体类的方法并执行,就说明在底层Spring已经自动的帮我们创建了实体类的对象
Spring工厂细节
- 我们在maven项目的依赖中只导入了一个依赖,真的是这样吗?
- schema
Spring依赖管理
虽然我们只在maven项目中导入了spring的一个依赖,但是
jar包之间是彼此依赖
的,当导入一个"上层"依赖时,maven发现"下层"的依赖没有导入就会自动帮我们导入
看这个图就会发现maven实际上并不是导入一个依赖 |
---|
Spring常用功能的jar包依赖关系 |
---|
Schema:规范
配置文件中的顶级标签中包含了语义化标签的相关信息
- xmlns: 语义化标签所在的命名空间
- xmlns:xsi: XMLSchema-instance标签遵循Schema标签标准
- xsi:schemaLocation: xsd文件位置,用以描述标签语义,属性,取值范围等
简单的说就是描述了
- xml文件中可以出现什么样的标签
- 这些标签代表了什么语义
- 这些标签的层级是怎么样的(谁是谁的父标签,谁是子标签)
IOC
IOC简单介绍
IOC
:Inversion of Control控制反转反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为了由工厂推送.(变主动为被动,即反转)
解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健
要了解控制反转,那就首先得了解什么叫依赖关系,这里做一个简单的解释
没使用Spring项目管理之前强耦合和其缺点
在有Spring IOC之前 |
---|
我们service的实现需要使用到UserDaoImpl的功能,那么我们在不使用Spring的情况下,我们只能将该对象new出来.--------这就导致了强耦合
当项目需要发生改变:我们需要使用到另一个UserDaoImpl时,我们不仅需要重新写一个UserDaoImpl,还需要改动service的相关代码
,这种强耦合的关系,在修改一个类的时候还需要改动另一个类的代码,这给代码的维护和开发都带来很大的困难,这就导致了多少程序员同行的加班熬夜和程序员们可怜的发际线逐渐难以保护
使用Spring IOC
- 不再在需要用到其他类时new对象,而是叫给Spring管理,
只需要提供给其接口信息,和get set方法
- 在spring的配置文件中指定
package per.leiyu.service.serviceImpl;
import per.leiyu.dao.UserDao;
import per.leiyu.dao.daoImpl.UserDaoImpl;
import per.leiyu.service.UserService;
/** * @author 雷雨 * @date 2020/6/19 15:13 */
public class UserServiceImpl implements UserService {
// UserDao userDao = new UserDaoImpl() ;
private UserDao userDao;
@Override
public void deleteUser(Integer id) {
System.out.println("UserService的实现方法");
userDao.deleteUser(1);
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
<bean id="userDao" class="per.leiyu.dao.daoImpl.UserDaoImpl"></bean>
<bean id="userService" class="per.leiyu.service.serviceImpl.UserServiceImpl">
<!-- userDao属性赋值 值为id为userDao的一个bean -->
<property name="userDao" ref="userDao"/>
</bean>
- 这里的
标签中的name属性
填写实体类(UserServiceImpl)中的userDao属性
UserServiceImpl实体类中的属性 |
---|
ref
对应的是在spring配置文件中一个bean的一个id值
ref属性对应id值 |
---|
使用Spring执行结果 |
---|
- 成功执行了自己的方法和UserDao接口的实现类的方法
使用Spring IOC的优势
- 不引用任何一个具体的组件(实现类),在需要其他组件的位置预留存取值入口(set/get)
当UserServiceImpl中的使用的Userdao的实现类发生改动时
不需要改动UserServiceImpl中相关代码
只需要在Spring的相关配置文件中重新"注册"实体类的bean,修改UserServiceImpl对应的ref映射的bean的id值即可.
消除了UserServiceImpl和UserDao的强耦合
这就达到了
如果一个类需要修改,那么一定是这个类本身的问题,而不会是因为别的类发生修改而这个类也发生修改
DI
DI
:Dependency Injection 依赖注入
概念
在Spring创建对象的同时,为其属性赋值,称之为依赖注入
可分为三类注入方式:
- set注入
- 构造注入
- 自动注入
set注入(常用)
创建对象时,Spring工厂会通过Set方法为对象的属性赋值
定义目标Bean类型并提供get和set方法
package per.leiyu.entiry;
import java.util.*;
/** * @author 雷雨 * @date 2020/6/19 17:45 */
public class User {
private Integer id;
private String password;
private String sex;
private Date bornDate;
private String[] hobbys;
private Set<String> phones;
private List<String> names;
private Map<String,String> countries;
private Properties properties;
//篇幅原因这里不展示get和set
在Spring配置文件中的映射
<bean id="user" class="per.leiyu.entiry.User">
<!-- 基本类型 String -->
<property name="id" value="1"/>
<property name="password" value="123"/>
<property name="sex" value="男"/>
<property name="bornDate" value="2020/6/18 12:20:03"/>
<!-- 数组 -->
<property name="hobbys">
<array>
<value>bastetball</value>
<value>football</value>
</array>
</property>
<!-- set-->
<property name="phones">
<set>
<value>12321</value>
<value>214214</value>
</set>
</property>
<property name="names">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>
<property name="countries">
<map>
<entry key="zh" value="中国"></entry>
<entry key="en" value="英国"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="url">jdbc:mysql:3306</prop>
<prop key="username">root</prop>
</props>
</property>
</bean>
构造注入(了解)
创建对象时,Spring工厂会通过构造方法为对象的属性赋值
定义目标Bean类型并提供有参的构造函数
package per.leiyu.entiry;
/** * @author 雷雨 * @date 2020/6/19 18:17 */
public class Student {
private Integer id;
private String name;
private String sex;
private Integer age;
public Student(Integer id, String name, String sex, Integer age) {
//这里在构造里面加了一句话是为了后面容易验证
System.out.println("这个方法执行了");
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
}
public Student() {
super();
}
}
Spring配置文件配置Bean
<bean id="student" class="per.leiyu.entiry.Student">
<constructor-arg name="id" value="1"/>
<constructor-arg name="name" value="雷雨想当工程师"/>
<constructor-arg name="age" value="18"/>
<constructor-arg name="sex" value="男"/>
bean>
测试类
package per.leiyu.factoryTest;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import per.leiyu.entiry.Student;
/** * @author 雷雨 * @date 2020/6/19 18:20 */
public class SpringStudentTest {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
Student student = (Student) context.getBean("student");
System.out.println(student);
}
}
结果 |
---|
自动注入(了解)
自动注入方式-byName |
---|
自动注入方式-byType |
---|
- 需要注意的是:在配置文件中使用自动注入,
符合注入条件的必须只是唯一的一个Bean
,如果是多个Bean,那么会报异常
Bean细节
控制简单对象的单例.多例模式
配置
<!-- singleton(默认):每次调用工厂都是得到的同一个对象 prototype:每次调用工厂,都会创建新的对象 -->
<bean id="mc" class="per.leiyu.entiry.Mycalass" scope="singleton"></bean>
- 注意:需要根据场景决定对象的单例和多例模式
- 可以共用:Service.Dao.SqlSessionFactory(或者是所有的工厂)
- 不可共用:Connection.SqlSession.ShoppingCart
FactoryBean创建复杂对象
作用:让Spring可以创建复杂对象.或者无法直接通过反射创建的对象
Spring创建对象的过程:就是需要用反射来调用实体类的构造方法来创建对象,但是有些对象不能通过简单的new(也就是构造方法的方式)来创建对象
复杂对象:简单的说就是不能通过new创建的对象,比如Connection对象.SqlSessionFactory对象
创建复杂对象需要借助FactoryBean,这里演示一个创建Connection对象的工厂Bean方法
package per.leiyu;
import org.springframework.beans.factory.FactoryBean;
import java.sql.Connection;
import java.sql.DriverManager;
/** * @author 雷雨 * @date 2020/6/19 20:21 */
public class myFactoryBean implements FactoryBean {
/** * 创建复杂对象的具体方法 * @return * @throws Exception */
@Override
public Object getObject() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection("jdbc:mysql:localhost:3306:/mybatis","root","123456");
}
@Override
public Class<?> getObjectType() {
return Connection.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
在Spring配置文件中进行注册
<!-- 复杂对象的创建 演示Connection的创建 注意Spring中创建复杂对象(使用FactoryBean)返回的是getObject方法的返回值 -->
<bean id="myFactoryconn" class="per.leiyu.myFactoryBean"></bean>
测试类
package per.leiyu.factoryTest;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.sql.Connection;
/** * @author 雷雨 * @date 2020/6/19 20:32 */
public class TestFactoryConnection {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
Connection conn = (Connection)context.getBean("conn");
System.out.println(conn);
}
}
成功创建了复杂对象 |
---|
Spring工厂特性
饿汉式创建优势
工厂创建之后,会将Spring配置文件中的所有对象都创建完成(饿汉式)
提高程序运行效率.比年多次IO,减少对象创建时间(概念接近连接池,一次性创建好,使用时直接获取)
声明周期方法
- 自定义初始化方法:添加"inint-method"属性,Spring则会在创建对象之后,调用此方法
- 自定义销毁方法:添加"destory-method"属性,Spring则会在销毁对象之前,调用此方法
- 销毁:工厂的close()方法被调用之后,Spring会销毁所有已创建的单例对象
- 分类:Singleton对象由Spring容器销毁,prototype对象由JVM销毁
Spring生命周期过程:(不完全过程)
调用构造方法-----调用set方法-----调用初始化方法-----调用销毁方法销毁
- 如果不是构造注入,那么就调用空参构造,如果是构造注入就直接调用有参的构造方法
生命周期阶段
单例Bean:singleton
随工厂启动
创建
=<mark>构造方法
</mark>>set方法
(注入值)<mark>>init
(初始化)</mark>>构建完成===随工厂关闭销毁
多例Bean:prototype
被使用时
创建
=<mark>构造方法
</mark>>set方法
(注入值)<mark>>init
(初始化)</mark>>构建完成===JVM垃圾回收销毁
我是雷雨,一个
普本科
的学生,主要专注于Java后端和大数据开发
如果这篇文章有帮助到你,希望你给我一个
大大的赞
如果有什么问题,希望你能留言
和我一起研究
,学习靠自觉,分享靠自愿