一、Spring IOC简介
Spring IOC(Inverse of Control,控制反转)是Spring框架的核心特性之一。在传统的应用程序中,对象的创建和它们之间的依赖关系通常由程序员手动管理(new Object())。而Spring IOC可以自动管理对象的创建和依赖注入(Dependency Injection,即DI),降低代码耦合。
注:Spring中的Bean,大家理解为Java中的Object(对象)即可,两者只是语境不同。
1.HelloWorld
1.创建Java类
public class HelloWorld {
public void sayHello(){
System.out.println("helloworld");
}
}
2.创建配置文件
在resources目录创建一个 Spring 配置文件 beans.xml
其中我们只需要关注<bean>标签即可
<?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">
<!--
配置HelloWorld所对应的bean,即将HelloWorld的对象交给Spring的IOC容器管理
通过bean标签配置IOC容器所管理的bean
属性:
id:设置bean的唯一标识
class:设置bean所对应类型的全类名
-->
<bean id="helloWorld" class="com.spring6.bean.HelloWorld"></bean>
</beans>
3.测试
public class HelloWorldTest {
@Test
public void testHelloWorld(){
//Spring中用于读取配置文件信息的类
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
//getBean(String id)方法通过id找到配置文件中id相同的bean标签并创建实例对象
HelloWorld helloworld = (HelloWorld) ac.getBean("helloWorld");
helloworld.sayHello();
}
}
从测试类中我们可以看出,对象并不是被new出来的,而是由Spring的getBean方法创建的(原理为反射)。
2.IOC容器在Spring中的实现
IOC容器是用来存放Bean的,Spring提供了其基本实现BeanFactory(Bean工厂),在开发中我们使用子接口ApplicationContext(见上文测试类)
二、基于XML管理Bean
1.获取bean
//1 根据id获取
Object getBean(String name);
//2 根据class获取
<T> T getBean(Class<T> requiredType);
//3 根据id和class获取
<T> T getBean(String name, Class<T> requiredType);
例:
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
User user1 = (User) ac.getBean("user");
User user2 = (User) ac.getBean(User.class);
User user3 = (User) ac.getBean("user", User.class);
注1:当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个
当IOC容器中一共配置了两个时会出现异常
<bean id="helloworld1" class="com.spring6.bean.HelloWorld"></bean>
<bean id="helloworld2" class="com.spring6.bean.HelloWorld"></bean>
注2:bean的默认作用域(scope)为单例,意味着默认情况下user1、user2、user3为同一对象
注3:接口实现 bean 唯一时,根据接口类型可以获取 bean(spring会自动找到其唯一实现类)
UserDao userDao1 = context.getBean(UserDaoImpl.class);
UserDao userDao2 = context.getBean(UserDao.class); //√
//对比
UserDao userDao3 = new userDao(); //×
2.依赖注入
对象创建完成了,怎么给对象属性赋值?
在Spring中,IOC通过依赖注入(DI)实现,有两种常见方式:set注入和构造注入
2.1 set注入
<bean id="student1" class="com.spring6.bean.Student">
<property name="id" value="1001"></property>
<property name="name" value="张三"></property>
<property name="age" value="23"></property>
<property name="sex" value="男"></property>
</bean>
property标签:表明通过组件类的setXxx()方法给组件对象设置属性
name属性:指定属性名xxx(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关)
value属性:指定属性值
2.2 构造注入
<bean id="student2" class="com.spring6.bean.Student">
// 根据构造器参数的顺序注入
<constructor-arg value="1002"></constructor-arg>
// 属性index表示构造器参数索引(从0开始)
<constructor-arg index="1" value="李四"></constructor-arg>
// 属性name表示参数名
<constructor-arg name="age" value="33"></constructor-arg>
<constructor-arg name="gender" value="女"></constructor-arg>
</bean>
2.3 特殊值处理
// age为null
<property name="age">
<value>null</value>
</property>
// xml中'<'、'>'属于特殊字符,value="a < b"是错误的
<property name="expression">
<!-- 使用CDATA节 -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<value><![CDATA[a < b]]></value>
</property>
3.为对象类型属性赋值
与基本数据类型不同,为对象类型属性赋值时需要使用ref属性而不是value属性
3.1 引入外部bean
// 配置Clazz类型的bean:
<bean id="clazz1" class="com.spring6.bean.Clazz">
<property name="clazzId" value="1111"></property>
<property name="clazzName" value="财源滚滚班"></property>
</bean>
//为Student中的clazz属性赋值:
<bean id="studentFour" class="com.spring6.bean.Student">
<!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
<property name="clazz" ref="clazz1"></property>
</bean>
3.2 内部bean
内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性
<bean id="studentFour" class="com.spring6.bean.Student">
<property name="clazz">
// 不用设置ref属性(ref是用来引用外部bean的)
<bean class="com.spring6.bean.Clazz">
<property name="clazzId" value="2222"></property>
<property name="clazzName" value="远大前程班"></property>
</bean>
</property>
</bean>
3.3 级联属性赋值
<bean id="studentFour" class="com.spring6.bean.Student">
<property name="clazz" ref="clazz1"></property>
<property name="clazz.clazzId" value="3333"></property>
<property name="clazz.clazzName" value="最强王者班"></property>
</bean>
4.为其他类型属性赋值
4.1 数组
<bean id="studentFour" class="com.spring.bean6.Student">
<property name="hobbies">
<array>
<value>看电影</value>
<value>喝酒</value>
<value>睡觉</value>
</array>
</property>
</bean>
4.2 List / Set集合
属性为:List / Set<Student>
<bean id="clazzTwo" class="com.spring6.bean.Clazz">
<property name="students">
// set集合只需将list改为set
<list>
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
<ref bean="studentThree"></ref>
</list>
</property>
</bean>
4.3 Map集合
属性为:Map<String, Teacher>
<bean id="teacherOne" class="com.atguigu.spring6.bean.Teacher">
<property name="teacherId" value="10010"></property>
<property name="teacherName" value="大宝"></property>
</bean>
<bean id="teacherTwo" class="com.spring6.bean.Teacher">
<property name="teacherId" value="10086"></property>
<property name="teacherName" value="二宝"></property>
</bean>
<bean id="studentFour" class="com.spring6.bean.Student">
<property name="teacherMap">
<map>
<entry>
<key>
<value>10010</value>
</key>
<ref bean="teacherOne"></ref>
</entry>
// 简洁写法
<entry key="10086" value-ref="teacherTwo"></entry>
</map>
</property>
</bean>
4.4 引用集合类型的Bean
<!--list集合类型的bean-->
<util:list id="students">
<ref bean="studentOne"></ref>
<ref bean="studentTwo"></ref>
</util:list>
<!--map集合类型的bean-->
<util:map id="teacherMap">
<entry key="10010" value-ref="teacherOne"></entry>
<entry key="10086" value-ref="teacherTwo"></entry>
</util:map>
<bean id="clazzTwo" class="com.atguigu.spring6.bean.Clazz">
<property name="students" ref="students"></property>
</bean>
<bean id="studentFour" class="com.atguigu.spring6.bean.Student">
<property name="teacherMap" ref="teacherMap"></property>
</bean>
注:使用util:list、util:map标签必须在配置文件中引入相应的命名空间
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
5. p命名空间
引入p命名空间:
<?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:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
引入p命名空间后可通过以下方式进行依赖注入:
<bean id="studentSix" class="com.spring6.bean.Student"
p:id="1006" p:name="小明" p:clazz-ref="clazzOne" p:teacherMap-ref="teacherMap">
</bean>
6.自动装配
所谓自动装配即不需要我们在xml中为bean对象的属性赋值(只适用于对象属性),而是由IOC容器在xml配置文件中找到对应bean对象并注入依赖
自动装配是通过bean标签的autowire属性设置,有byType和byName两种
6.1 byType
<bean id="userController" class="com.spring6.autowire.controller.UserController" autowire="byType"></bean>
<bean id="userService" class="com.spring6.autowire.service.impl.UserServiceImpl" autowire="byType"></bean>
<bean id="userDao" class="com.spring6.autowire.dao.impl.UserDaoImpl"></bean>
根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值
若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null
若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionException
6.2 byName
<bean id="userController" class="com.spring6.autowire.controller.UserController" autowire="byName"></bean>
<bean id="userService" class="com.spring6.autowire.service.impl.UserServiceImpl" autowire="byName"></bean>
<bean id="userServiceImpl" class="com.spring6.autowire.service.impl.UserServiceImpl" autowire="byName"></bean>
<bean id="userDao" class="com.spring6.autowire.dao.impl.UserDaoImpl"></bean>
<bean id="userDaoImpl" class="com.spring6.autowire.dao.impl.UserDaoImpl"></bean>
将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值
7.引入外部属性文件
外部jdbc.properties文件:
jdbc.user=root
jdbc.password=atguigu
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.driver=com.mysql.cj.jdbc.Driver
引入文件:
<?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">
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
三、Bean生命周期
1.作用域
在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:
如果是在WebApplicationContext环境下还会有request / session作用域,分别代表在一次请求 / 会话范围内有效
2.生命周期
- bean对象创建(无参构造)
- 依赖注入
- 调用后置处理器(初始化之前)
- bean对象初始化(需在配置bean时指定初始化方法)
- 调用后置处理器(初始化之后)
- bean对象就绪可以使用
- 销毁(需在配置bean时指定销毁方法)
- IOC容器关闭
Java类:
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
public User() {
System.out.println("生命周期:1、创建对象");
}
public User(Integer id, String username, String password, Integer age) {
this.id = id;
this.username = username;
this.password = password;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
System.out.println("生命周期:2、依赖注入");
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public void initMethod(){
System.out.println("生命周期:3、初始化");
}
public void destroyMethod(){
System.out.println("生命周期:5、销毁");
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
}
配置bean:
<!-- 使用init-method属性指定初始化方法 -->
<!-- 使用destroy-method属性指定销毁方法 -->
<bean class="com.atguigu.spring6.bean.User" scope="prototype" init-method="initMethod" destroy-method="destroyMethod">
<property name="id" value="1001"></property>
<property name="username" value="admin"></property>
<property name="password" value="123456"></property>
<property name="age" value="23"></property>
</bean>
后置处理器(对所有Bean有效):
public class MyBeanProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("☆☆☆" + beanName + " = " + bean);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("★★★" + beanName + " = " + bean);
return bean;
}
}
配置后置处理器:
<!-- bean的后置处理器要放入IOC容器才能生效 -->
<bean id="myBeanProcessor" class="com.atguigu.spring6.process.MyBeanProcessor"/>
四、基于注解管理Bean
Spring 从 2.5 版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化 Spring 的 XML 配置
Spring 通过注解实现自动装配的步骤如下:
- 引入依赖
- 开启组件扫描
- 使用注解定义 Bean
- 依赖注入
1.引入依赖
<dependencies>
<!--spring context依赖-->
<!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.3</version>
</dependency>
</dependencies>
2.组件扫描
Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中
2.1 基础扫描
<?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-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启组件扫描功能-->
<context:component-scan base-package="com.spring6"></context:component-scan>
</beans>
2.2 指定排除扫描的组件
<context:component-scan base-package="com.spring6">
<!-- context:exclude-filter标签:指定排除规则 -->
<!--
type:设置排除或包含的依据
type="annotation",根据注解排除,expression中设置要排除的注解的全类名
type="assignable",根据类型排除,expression中设置要排除的类型的全类名
-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!--<context:exclude-filter type="assignable" expression="com.spring6.controller.UserController"/>-->
</context:component-scan>
2.3 仅扫描指定组件
<context:component-scan base-package="com" use-default-filters="false">
<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<!-- 此时必须设置use-default-filters="false",因为默认规则即扫描指定包下所有类 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!--<context:include-filter type="assignable" expression="com.spring6.controller.UserController"/>-->
</context:component-scan>
3.使用注解定义Bean
Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean
@Component(value="user") // <bean id="user" class="User"></bean>
// value属性可以省略,默认值为类名小写
public class User{
}
4.依赖注入
4.1 @Autowired
源码:
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
源码中有两处需要注意:
1.该注解可以标注在:构造方法、方法、形参、属性、注解
2.该注解有一个required属性,默认值是true,表示在注入的时候要求被注入的Bean必须是存在的,如果不存在则报错
Autowired可以通过属性 / set方法 / 构造方法 / 形参注入
@Controller
public class UserController {
@Autowired // 属性注入,不需要构造方法和set方法(有默认无参构造)
private UserService userService;
public void out() {
userService.out();
System.out.println("Controller层执行结束。");
}
}
@Controller
public class UserController {
private UserService userService;
@Autowired // set注入
public void setUserService(UserService userService) {
this.userService = userService;
}
public void out() {
userService.out();
System.out.println("Controller层执行结束。");
}
}
@Controller
public class UserController {
private UserService userService;
@Autowired // 构造注入
public UserController(UserService userService) {
this.userService = userService;
}
public void out() {
userService.out();
System.out.println("Controller层执行结束。");
}
}
@Controller
public class UserController {
private UserService userService;
// 形参注入
public UserController(@Autowired UserService userService) {
this.userService = userService;
}
public void out() {
userService.out();
System.out.println("Controller层执行结束。");
}
}
@Service
public class UserServiceImpl implements UserService {
// 只有一个构造方法时注解可以省略
private UserDao userDao;
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void out() {
userDao.print();
System.out.println("Service层执行结束");
}
}
@Service
public class UserServiceImpl implements UserService {
@Autowired // 默认根据类型装配,如果一个类型有多个Bean实例则报错
@Qualifier("userDaoImpl") // 添加注解指定bean的名字,根据名字装配
private UserDao userDao;
@Override
public void out() {
userDao.print();
System.out.println("Service层执行结束");
}
}
总结
- @Autowired注解可以出现在:属性上、构造方法上、构造方法的参数上、setter方法上
- 当带参数的构造方法只有一个,@Autowired注解可以省略
- @Autowired注解默认根据类型注入。如果要根据名称注入的话,需要配合@Qualifier注解一起使用
4.2 @Resource
@Resource注解也可以完成属性注入。那它和@Autowired注解有什么区别?
- @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)
- @Autowired注解是Spring框架的
- @Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配
- @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。
- @Resource注解只能用在属性、set方法上
引入依赖:
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
根据name注入:
@Service
public class UserServiceImpl implements UserService {
@Resource(name = "myUserDao")
private UserDao myUserDao;
@Override
public void out() {
myUserDao.print();
System.out.println("Service层执行结束");
}
}
name未知时以属性名为name查找:
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserDao myUserDao;
@Override
public void out() {
myUserDao.print();
System.out.println("Service层执行结束");
}
}
总结:
@Resource注解:默认byName注入,没有指定name时把属性名当做name,根据name找不到时,才会byType注入。byType注入时,某种类型的Bean只能有一个
5.Spring全注解开发
全注解开发就是不再使用spring配置文件了,写一个配置类来代替配置文件
@Configuration
//@ComponentScan({"com.spring6.controller","com.spring6.service","com.spring6.dao"})
@ComponentScan("com.spring6") // 开启注解扫描
public class Spring6Config {
}
测试类:
@Test
public void testAllAnnotation(){
ApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class);
UserController userController = context.getBean("userController", UserController.class);
userController.out();
logger.info("执行成功");
}