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体系结构
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就可以了。
(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)包结构如下
copycom/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>
在测试类中进行测试
copypackage 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 容器类的一个最基本接口,所以联合一下就能知道这儿就把对象创建了。
其实在上面加载核心配置文件的时候对应的对象就已经被创建了,我们可以重写 UserServiceImpl 的构造方法来证明一下
copy
public UserServiceImpl() {
System.out.println("这里是UserServiceImpl的构造方法");
}
@Override
public void say() {
System.out.println("这里是UserServiceImpl");
}
此时对象就放在了Spring的容器里面,下一步就是取出对象,使用applicationContext的getBean(String name),此方法默认返回一个Object对象(或者你也可以使用getBean(Class
获得了该对象就可以调用对象的方法了,如下。
在实际开发中我们都是使用服务(service)来调用数据持久层(dao)来操作数据,所以下一步就是要在UserServiceImpl中来调用Dao的方法。
在 UserServiceImpl 中声明一个 userDao 并为 userDao 提供 set 方法,UserDaoImpl 中有一个 sayHi() 方法,我们在 UserServiceImp 类的 say() 方法中调用它。
copypublic 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>
(二)依赖注入DI
1.概念:依赖注入和控制反转恰恰相反,它是一种具体的编码技巧。我们不通过 new 的方式在类内部创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等 方式传递(或注入)给类来使用。
2.依赖注入的方式
(1)set 注入
基于set方法实现的,底层会通过反射机制调用属性对应的set方法然后给属性赋值。这种方式要求属性必须对外提供set方法。
(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>
当然你也可以在构造器中写多个参数,并在 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);
}
(3)注入集合
如果需要 Spring 容器来管理集合对象,则可以使用集合元素标签:list、set、map 和 props 来创建 List 对象、 Set 对象、Map 对象和 Properties 对象。以下面的 UtilBean 类为例,该类中包含了常用的集合。
copyimport 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 进行生成测试)
copypublic 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+"-------");
}
}
}
(三)创建 Bean 的方式
1.构造器创建
默认通过类的无参构造器生成,这点在上面已经证实。
2.静态工厂方法
下面的程序定义了工厂类 CarFactory 和 工厂产品 Car,其中 Car 有三个属性,当创建时我们指定其中的 carName 和 color。
copypublic 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 并为在工厂方法中出现。为方便记忆,你可以把工厂方法当成构造器来看。
copypublic class BeanTest02 {
@Test
public void test02 () {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml");
Car car = applicationContext.getBean(Car.class);
System.out.println(car);
}
}
3.实例工厂方法
实例工厂方法与静态工厂方法只有一点不同:调用静态工厂方法只需要工厂类即可,而调用实例工厂方法则需要工厂实例。所以配置实例工厂方法与配置静态工厂方法基本相似,只有一点区别:配置静态工厂方法使用 class 指定静态工厂类,而配置实例工厂方法则使用 factory-bean 指定工厂实例。以下为实例工厂的实例。
copypublic 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);
}
(四)命名空间简化
从 Spring 2.0 版本开始,Spring 允许使用基于 XML Schema 的约束来简化 Spring 配置。
1.P 命名空间
P命名空间不需要特定的 Schema 定义,直接存在 Spring 内核中。主要用于简化设值注入,相当于把这个当成 property。
copyxmlns:p="http://www.springframework.org/schema/p"
copyimport 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
//不写了,反正都知道啥意思
//下面就是结果
2.C 命名空间
C命名空间同样不需要特定的 Schema 定义,直接存在 Spring 内核中。主要用于简化构造注入,相当于写 constructor-arg。
copyxmlns:c="http://www.springframework.org/schema/c"
copyimport 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的方式获取
3.Util 命名空间
使用 util 命名空间主要是简化对集合类 List、Set、Map 等 Java 对象的配置,util 命名空间对应的 XML Schema 校验并不在 Spring 的内核中,需要在 Spring 配置文件的 beans 标签中引入 util 命名空间的 XML Schema 文件。
copyxmlns: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);
}
(五)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>
2.prototype:每次通过容器的 getBean 方法获取 prototype 作用域的 Bean 时,都将生成一个新的 Bean 实例。
更改 scop 的值为prototype 即可
copy<bean id="utilBean" class="com.qlu.bean.UtilBean" p:schools-ref="schools" scope="prototype"></bean>
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
copyimport 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。
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 的值,我们可以打开编译后的代码
Bean 的 ByName 配置会在 User 中找到 setRole 方法,并把 set 前缀去掉 ,字母小写获得 role,进而找到一个叫 role 的bean
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 的值
在指定 byType 后会在 User 中找到参数为 Role 的 set 方法,进而进行注入。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步