Spring01_IOC、DI和Beans配置

一、Spring概述

(一)Spring简介

​ Spring 为企业应用的开发提供了一个轻量级的解决方案。该解决方案包括:基于依赖注入的核心机制、基于 AOP (Aspect Oriented Programming,面向切面的程序设计)的声明式事务管理、与各种持久层技术的整合,以及优 秀的Web MVC框架等。Spring致力于JavaEE应用各层的解决方案,而不是仅仅专注于某一层的方案。可以说:Spring 是企业应用开发的“一站式”解决方案,Spring 贯穿表现层、业务层、持久层。然而,Spring 并不想取代那些已有的框架,而是以高度的开放性与它们无缝整合。

(二)Spring的优点

​ 1.低侵入式设计,代码的污染极低。

​ 2.独立于各种应用服务器,基于 Spring 框架的应用,可以实现 Write Once,Run Anywhere。

​ 3.Spring 的 IoC 容器降低了业务对象替换的复杂性,提高了组件之间的解耦。

​ 4.Spring 的 AOP 支持允许将一些通用任务如安全、事务、日志等进行集中处理,从而提供了更好的复用。

​ 5.Spring 的 ORM 和 DAO 提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问。

​ 6.Spring 的高度开放性,并不强制应用完全依赖 Spring,开发者可以自由选用 Spring 框架的部分或全部。

(三)Spring体系结构

image-20230411103056026

​ 1.Spring核心容器

​ Core模块:框架的核心,封装框架的最底层依赖,包括资源访问、类型转换以及一些工具。

​ Beans模块:Spring的基础,包括控制反转和依赖注入,以 BeanFactory 为核心(工厂模式、单例模式);所有的应用程序对象和对象间的关系由Spring框架管理。

​ Context模块:以 Core 和 beans为基础,继承 Beans 功能并添加资源绑定、验证数据、国际化等,核心接口是 ApplicationContext。

​ El模块:表达式语言支持。

​ 2.AOP和ASpects

​ Aop模块:提供面向切面编程的实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,降低业务逻辑和通用功能的耦合。

​ Aspects模块:AspectJ 是一个面向切面的框架。

​ 3.数据访问/集成模块

​ 事务模块:用于Spring管理事务。

​ JDBC模块:提供JDBC的样例模板,消除传统JDBC编码。

​ ORM模块:提供 “对象--关系” 的无缝集成。

​ 4.Web模块

​ Web模块 :提供基础的 web 功能。

​ Web-Servlet模块:提供 SpringMVC 框架实现。

二、Spring 容器

(一)控制反转IoC

​ 1.概念:Ioc(Inversion of control)直译过来就是控制反转,控制反转是一个比较笼统的设计思想,并不是一种具体的实现方法,一般用来指导 框架层面的设计。这里所说的“控制”指的是对程序执行流程的控制,而“反转”指的是在 没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流 程通过框架来控制。流程的控制权从程序员“反转”给了框架。在 Spring中最能体现 IOC 的就是由Spring框架去创建对象并放在Spring容器中管理。**

​ 2.案例

​ (1)所需依赖

copy
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.context-version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit-version}</version> <scope>test</scope> </dependency> </dependencies>

​ 由于Spring的各个模块是相互依赖的,所以只需要一个context就可以了。

image-20230411110054244

​ (2)Spring的核心配置文件

​ 主要是这个xsd的校验,里面的配置稍后再说

copy
<?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"> </beans>

​ (3)包结构如下

copy
com/qlu/dao/impl/UserDaoImpl.java com/qlu/dao/UserDao.java com/qlu/service/impl/UserServiceImpl.java com/qlu/service/UserService.java

​ (4)配置bean并加载Spring的配置文件

​ 在spring-beans.xml添加配置

copy
<bean id="userService" class="com.qlu.service.impl.UserServiceImpl"></bean>

​ 在测试类中进行测试

copy
package com.qlu.test1; import com.qlu.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class BeanTest { public static void main(String[] args) { //加载核心配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml"); //获得bean对象getBean默认返回Object UserService userService = (UserService) applicationContext.getBean("userService"); userService.say(); } }

​ 在上面的测试类中我们用ClassPathXmlApplicationContext的父类来接受子类对象,实际上这个 ApplicationContext 就是一个 BeanFactory 的子接口,而 BeanFactory 就是 Spring 容器类的一个最基本接口,所以联合一下就能知道这儿就把对象创建了。

image-20230411111224649

​ 其实在上面加载核心配置文件的时候对应的对象就已经被创建了,我们可以重写 UserServiceImpl 的构造方法来证明一下

copy
public UserServiceImpl() { System.out.println("这里是UserServiceImpl的构造方法"); } @Override public void say() { System.out.println("这里是UserServiceImpl"); }

image-20230411114116588

​ 此时对象就放在了Spring的容器里面,下一步就是取出对象,使用applicationContext的getBean(String name),此方法默认返回一个Object对象(或者你也可以使用getBean(Class type)方法),参数name是在配置文件中 bean 中的 id 或 name,为了返回UserService的对象我们再传一个class对象进去(即getBean(String var1,Class var2)方法),就能把获取到的 Object 对象转换成传入的 class 对象。

​ 获得了该对象就可以调用对象的方法了,如下。

image-20230411115249801

​ 在实际开发中我们都是使用服务(service)来调用数据持久层(dao)来操作数据,所以下一步就是要在UserServiceImpl中来调用Dao的方法。

​ 在 UserServiceImpl 中声明一个 userDao 并为 userDao 提供 set 方法,UserDaoImpl 中有一个 sayHi() 方法,我们在 UserServiceImp 类的 say() 方法中调用它。

copy
public class UserServiceImpl implements UserService { //set注入 private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } //重写构造器 public UserServiceImpl() { System.out.println("这里是UserServiceImpl的构造方法"); } @Override public void say() { System.out.println("这里是UserServiceImpl的say方法"); userDao.sayHi(); } }

​ 同样在 spring-beans.xml 中需要把 dao 配置到 service 中

copy
<bean id="userDao" class="com.qlu.dao.impl.UserDaoImpl"></bean> <bean id="userService" class="com.qlu.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"></property> </bean>

image-20230411141859034

(二)依赖注入DI

​ 1.概念:依赖注入和控制反转恰恰相反,它是一种具体的编码技巧。我们不通过 new 的方式在类内部创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等 方式传递(或注入)给类来使用

​ 2.依赖注入的方式

20200521235846480

(1)set 注入

​ 基于set方法实现的,底层会通过反射机制调用属性对应的set方法然后给属性赋值。这种方式要求属性必须对外提供set方法。

image-20230411151311051

(2)构造注入

​ 通过调用构造方法来给属性赋值。

​ 我们需要更改构造函数的内容,在 UserService 的构造器中把 UserDao 作为参数传入;

copy
//重写构造器 public UserServiceImpl(UserDao userDao) { this.userDao = userDao; System.out.println("这里是UserServiceImpl的构造方法"); }

​ 同时 xml 文件中的配置也需要更改

copy
<!-- 构造注入--> <!-- 这里的第一个userDao就是java类中构造器的参数,后面的ref就是配置文件上面声明的userDao对象--> <constructor-arg name="userDao" ref="userDao"></constructor-arg>

image-20230411152445667

​ 当然你也可以在构造器中写多个参数,并在 xml 文件中注入多个值,无论是 index 方式 还是 name 方式都可以。

copy
<!-- 这里的第一个userDao就是java类中构造器的参数,后面的ref就是配置文件上面声明的userDao对象--> <constructor-arg name="userDao" ref="userDao"></constructor-arg> <constructor-arg name="mesg" value="这里是通过构造注入的megs值"></constructor-arg> <!-- <constructor-arg index="0" ref="userDao"></constructor-arg>--> <!-- <constructor-arg name="mesg" value="这里是通过构造注入的megs值"></constructor-arg>-->
copy
//重写构造器 public UserServiceImpl(UserDao userDao,String mesg) { this.userDao = userDao; //上面别忘记声明 this.mesg = mesg; System.out.println("这里是UserServiceImpl的构造方法"); System.out.println(mesg); }

image-20230411153355937

(3)注入集合

​ 如果需要 Spring 容器来管理集合对象,则可以使用集合元素标签:list、set、map 和 props 来创建 List 对象、 Set 对象、Map 对象和 Properties 对象。以下面的 UtilBean 类为例,该类中包含了常用的集合。

copy
import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; @Data public class UtilBean { private List<String> schools; private Map<String, Integer> scores; private Properties health; private Set<Object> set; private String[] books; }

​ 在上面的类中使用 lombok 提供了 get、set方法,Spring默认是构造注入的,所以下面的 property name="schools" 相当于调用了 utilBean.setSchools() 方法,而 value 中的值就是 set 方法中的参数值了,在外层标签我们可以指定泛型(放入什么)。

copy
<bean id="utilBean" class="com.qlu.bean.UtilBean"> <!--测试向容器中注入集合,Untilbean放在容器中进行管理--> <property name="schools"> <list value-type="java.lang.String"> <value>齐鲁工业大学</value> <value>齐鲁工业大学(长清校区)</value> <value>齐鲁工业大学(千佛山校区)</value> </list> </property> <!--注入集合--> <property name="scores"> <map value-type="java.lang.Integer"> <entry key="yuwen" value="99"></entry> <entry key="shuxue" value="90"></entry> <entry key="yingyu" value="96"></entry> </map> </property> <!--注入properties--> <property name="health"> <props> <prop key="血压">正常</prop> <prop key="身高">正常</prop> </props> </property> <!--注入map--> <property name="set"> <set> <value>1</value> <value>2</value> <value>3</value> </set> </property> <!--注入String数组--> <property name="books"> <array> <value>java入门到入坟</value> <value>TCP/IP实战</value> <value>OS实战</value> </array> </property> </bean>

​ 编写测试(这里可能不规范,你完全可以使用快捷键ctrl +shift +t 进行生成测试)

copy
public class BeanTest { @Test public void test1() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml"); UtilBean utilBean = applicationContext.getBean(UtilBean.class); //属性一 List List<String> schools = utilBean.getSchools(); for (String school : schools) { System.out.println(school); } //属性二 map System.out.println(utilBean.getScores()); //属性三 properties System.out.println(utilBean.getHealth()); //属性四 set System.out.println(utilBean.getSet()); //属性五 arrays for (String book : utilBean.getBooks()) { System.out.print(book+"-------"); } } }

image-20230411161037474

(三)创建 Bean 的方式

1.构造器创建

​ 默认通过类的无参构造器生成,这点在上面已经证实。

2.静态工厂方法

​ 下面的程序定义了工厂类 CarFactory 和 工厂产品 Car,其中 Car 有三个属性,当创建时我们指定其中的 carName 和 color。

copy
public class CarFactory { /** * 静态工厂方法 * @param carName * @param color * @return */ public static Car getCar(String carName , String color) { return new Car().setCarName(carName).setColor(color); } } import lombok.Data; import lombok.experimental.Accessors; @Data @Accessors(chain = true) public class Car { private String carName; private String color; private int money; }

​ 使用静态工厂方法创建 Bean 实例时,class 属性指定并不是指定 Bean 实例的实现类,而是静态工厂类。除此之外,还需要使用 factory-method 属性来指定静态工厂方法,Spring 将调用静态工厂方法返回一个 Bean 实例,得到 Bean 实例后,Spring 后面的处理步骤与采用普通方法创建 Bean 完全一样。

copy
<!--配置工厂方法--> <bean id="car" class="com.qlu.bean.CarFactory" factory-method="getCar"> <constructor-arg name="carName" value="高级轿车"></constructor-arg> <constructor-arg name="color" value="黑色"></constructor-arg> <property name="money" value="30"></property> </bean>

​ 需要注意的是,当工厂方法 factory-method 需要参数时,应该用 constructor-arg 来指定参数的值,而对于想给对象的属性注入信息时可以使用 property 来完成值的注入。在本示例中,工厂方法需要我们传入两个参数 carName 和 color ,而 money 并为在工厂方法中出现。为方便记忆,你可以把工厂方法当成构造器来看。

copy
public class BeanTest02 { @Test public void test02 () { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml"); Car car = applicationContext.getBean(Car.class); System.out.println(car); } }

image-20230411191922709

3.实例工厂方法

​ 实例工厂方法与静态工厂方法只有一点不同:调用静态工厂方法只需要工厂类即可,而调用实例工厂方法则需要工厂实例。所以配置实例工厂方法与配置静态工厂方法基本相似,只有一点区别:配置静态工厂方法使用 class 指定静态工厂类,而配置实例工厂方法则使用 factory-bean 指定工厂实例。以下为实例工厂的实例。

copy
public class StudentFactory { public Student newStudent(String name , String address) { Student student = new Student().setName(name).setAddress(address); return student; } } import lombok.Data; import lombok.experimental.Accessors; @Data @Accessors(chain = true) public class Student { private String name; private String address; private int age; }

​ 采用实例工厂方法创建 Bean 的 bean 元素时需要指定如下两个属性:

​ factory-bean:该属性的值为工厂 Bean 的 id。

​ factory-method:该属性指定实例工厂的工厂方法。

​ 同样区分 constructor-arg 和 property 分别用于指定和赋值。

copy
<!--配置实例工厂方法--> <bean id="studentFactory" class="com.qlu.bean.StudentFactory"/> <bean id="student" factory-bean="studentFactory" factory-method="newStudent"> <constructor-arg name="name" value="烟芜镜"></constructor-arg> <constructor-arg name="address" value="yahoooo"></constructor-arg> <property name="age" value="500"></property> </bean>
copy
@Test public void test03() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml"); Student student = applicationContext.getBean(Student.class); System.out.println(student); }

image-20230411202639540

(四)命名空间简化

​ 从 Spring 2.0 版本开始,Spring 允许使用基于 XML Schema 的约束来简化 Spring 配置。

​ 1.P 命名空间

​ P命名空间不需要特定的 Schema 定义,直接存在 Spring 内核中。主要用于简化设值注入,相当于把这个当成 property。

copy
xmlns:p="http://www.springframework.org/schema/p"
copy
import lombok.Data; import lombok.experimental.Accessors; @Data @Accessors(chain = true) public class Person { private String name; private int age; }
copy
<!--p命名空间--> <bean id="person" class="com.qlu.bean.Person" p:name="烟芜镜" p:age="500"></bean>
copy
@Test //不写了,反正都知道啥意思 //下面就是结果

image-20230411205242803

​ 2.C 命名空间

​ C命名空间同样不需要特定的 Schema 定义,直接存在 Spring 内核中。主要用于简化构造注入,相当于写 constructor-arg。

copy
xmlns:c="http://www.springframework.org/schema/c"
copy
import lombok.Data; import lombok.experimental.Accessors; @Data @Accessors(chain = true) public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; System.out.println("这里是重写的构造方法"); } }
copy
<!--c命名空间--> <bean id="person" class="com.qlu.bean.Person" c:name="烟芜呼镜" c:age="500"></bean>
copy
@Data //那自然是不写了,下面是结果,重写了有参构造 //或许能c和p混用? //破案了,混用相当于property 和 constructor-arg 各算各的,截图放上,代码不写了 //你也可以用_index的方式获取

image-20230411210129320

image-20230411210409735

​ 3.Util 命名空间

​ 使用 util 命名空间主要是简化对集合类 List、Set、Map 等 Java 对象的配置,util 命名空间对应的 XML Schema 校验并不在 Spring 的内核中,需要在 Spring 配置文件的 beans 标签中引入 util 命名空间的 XML Schema 文件。

copy
xmlns:util="http://www.springframework.org/schema/util" http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"

​ 我们以上面的 [UtilBean](# (3)注入集合) 类为实例。

​ 使用p:schools-ref 来参照上面的bean

copy
<util:list id="schools" value-type="java.lang.String"> <value>小学</value> <value>中学</value> <value>高中</value> <value>大学</value> </util:list> <bean id="utilBean" class="com.qlu.bean.UtilBean" p:schools-ref="schools"></bean>
copy
@Test public void test() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans2.xml"); UtilBean utilBean = applicationContext.getBean(UtilBean.class); System.out.println(utilBean); }

image-20230411212559560

(五)Bean 作用域

​ 当通过 Spring 容器创建一个 Bean 实例时,不仅可以完成 Bean 实例的实例化,还可以为 Bean 指定特定的作用域。Spring 支持如下 4 中作用域。

​ 1.singleton:单例模式,在整个 Spring IoC 容器中,singleton 作用域的 Bean 将只生成一个实例。

​ 在 bean 标签中有 scop 属性,值默认是 singleton,我们可以测试一下在 singleton 下是否为同一个对象

copy
<bean id="utilBean" class="com.qlu.bean.UtilBean" p:schools-ref="schools" scope="singleton"></bean>

image-20230411213321449

​ 2.prototype:每次通过容器的 getBean 方法获取 prototype 作用域的 Bean 时,都将生成一个新的 Bean 实例。

​ 更改 scop 的值为prototype 即可

copy
<bean id="utilBean" class="com.qlu.bean.UtilBean" p:schools-ref="schools" scope="prototype"></bean>

image-20230411213446517

​ 3.request:对于一次 HTTP 请求,request 作用域的 Bean 将只生成一个实例,这意味着,在同一次 HTTP 请求内,程序每次请求该 Bean,得到的是同一个实例。只有在 Web 应用中使用 Spring 时,该作用域才 有效。

​ 4.session:对于一次 HTTP 会话,session 作用域的 Bean 将只生成一个实例,这意味着,在同一次 HTTP 会话内,程序每次请求该 Bean,得到的是同一个实例。只有在 Web 应用中使用 Spring 时,该作用域才 有效。

​ 如果不指定 Bean 的作用域,Spring 默认使用 singleton 作用域。Java 在创建 Java 实例时,需要进行内存申请; 销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此 prototype 作用域的 Bean 的创建、销毁代价比较大。而 singleton 作用域的 Bean 实例一旦创建完成可以反复使用。因此尽量避免将 Bean 设置成 prototype 作用域。

(六)Bean 的自动配置

​ Spring 能自动装配 Bean 与 Bean 之间的依赖关系,即无须使用 ref 显式指定依赖 Bean,而是由 Spring 容器检 查 XML 配置文件内容,根据某种规则,为调用者 Bean 注入被依赖的 Bean。

​ 1.ByName

​ 根据 setter 方法名进行自动装配。Spring 容器查找容器中的全部 Bean,找出其 id 与 setter 方法名去掉 set 前缀,并小写首字母后同名的 Bean 来完成注入。如果没有找到匹配的 Bean 实例,则 Spring 不会进行任何注入。

​ 开启 ByName 配置,需要在 beans 标签加入 default-autowire 设值为 byName

copy
import lombok.Data; @Data public class User { private int age; private String name; private Role role; } import lombok.Data; @Data public class Role { private String roleName; private String roleCode; public Role(String roleName, String roleCode) { this.roleName = roleName; this.roleCode = roleCode; } }

​ 在不使用自动装配时,我们会在 user 的 bean 中加入 role-ref 让其值等于被参照的那个 bean。

image-20230412094354212

copy
<bean id="role" class="com.qlu.bean.Role" c:roleName="经验加三" c:roleCode="3"></bean> <!--<bean id="user" class="com.qlu.bean.User" p:name="孙笑川" p:age="100" p:role-ref="role"></bean>--> <bean id="user" class="com.qlu.bean.User" p:name="孙笑川" p:age="100"></bean>
copy
@Test //不写了

​ 我们可以看到即使没有让 user 的 bean 指向 role 仍然能获取到 role 的值,我们可以打开编译后的代码

image-20230412094629850

​ Bean 的 ByName 配置会在 User 中找到 setRole 方法,并把 set 前缀去掉 ,字母小写获得 role,进而找到一个叫 role 的bean

image-20230412094728326

​ 2.ByType

​ 根据 setter 方法的形参类型来自动装配。Spring 容器查找容器中全部的 Bean,如果正好有一个 Bean 类型与 setter 方法的形参类型匹配,就自动注入这个 Bean;如果找到多个这样的 Bean,就抛出一 个异常,如果没有找到这样的 Bean,则什么都不操作,setter 方法也不会被调用。

​ 同样需要在 beans 标签加入 default-autowire 设值为 byType。现在把 role 的 bean 改名为 role123.

copy
<bean id="role123" class="com.qlu.bean.Role" c:roleName="经验加三" c:roleCode="3"></bean> <!--<bean id="user" class="com.qlu.bean.User" p:name="孙笑川" p:age="100" p:role-ref="role"></bean>--> <bean id="user" class="com.qlu.bean.User" p:name="孙笑川" p:age="100"></bean> </beans>

​ 在更改后仍然能获得 role 的值

image-20230412095434323

​ 在指定 byType 后会在 User 中找到参数为 Role 的 set 方法,进而进行注入。

image-20230412095624331

posted @   Purearc  阅读(148)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
🚀