Java框架-Spring IOC部分
1 Spring介绍
官网:https://spring.io/
Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。
一句话:Spring是一个开源,轻量化,具有IOC和AOP两大核心功能的容器型框架。
- IOC:控制反转,指将创建对象的权利交给Spring框架,让框架帮我们维护和管理所需对象,本文主要来讨论该特性。
- AOP:面向切面编程,他是作为面向对象编程的一种补充,已经成为一种比较成熟的编程方式。下节讨论。
2 Bean规范
在正式学习Spring前,我们先了解一个重要的概念:Bean规范
,所谓Bean规范,就是对创建Java类提出一些要求,满足这些要求的类就是一个满足Bean规范的类。
- 必须是一个公开且具体的类
- 建议属性私有化
- 必须有get和set方法
- 必须有无参构造
public class User { //一个满足Bean规范的类
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
前面咱们说到Spring可以帮我们创建和管理对象,那么类必须满足Bean规范,Spring才能进行管理。
3 IOC控制反转
咱们先来学习他的第一个特性IOC控制反转,让框架来帮我们创建对象。
3.1 环境搭建
1.创建普通maven项目,导入spring-context包
即可。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.22.RELEASE</version>
</dependency>
这里需要注意,虽然只导入了spring-context包,但是会关联导入其他相关的依赖包。
也就是说,如果要自己全部来导一遍的话,配置应该如下:
<!--spring上下文的包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.22.RELEASE</version>
</dependency>
<!--spring的core包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.22.RELEASE</version>
</dependency>
<!--spring的bean包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.22.RELEASE</version>
</dependency>
<!--spring表达式的包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.22.RELEASE</version>
</dependency>
<!--spring依赖的日志包(可选)-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
3.2 Spring的IOC初体验
1.准备一个满足Bean规范的类
2.在resource目录下,准备配置文件: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="..." class="..."> </bean>
id: bean的id,需要唯一
class:类的全限定名(包名.类名)
-->
<bean id="user" class="cn.spring.bean.User"> </bean>
</beans>
3.读取配置文件创建IOC容器,获取对象。这里注意,在spring中把对象/实例也叫做bean。
//1.加载配置文件,并创建IOC容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2.获取容器中的bean(对象)
//参数1:bean的id 参数2:对应的类型
User user = context.getBean("user", User.class);
System.out.println(user);
这里spring构建对象默认调用的是类的无参构造。
3.3 ApplicationContext 和 BeanFactory 的区别(面试题)
ApplicationContext容器 和 BeanFactory容器 都是IOC容器。也就是说使用BeanFactory也能进行对象的管理。
那么他俩有什么区别呢?
1.ApplicationContext 是 BeanFactory 的子接口。BeanFactory 是所有容器的顶级接口。
2.ApplicationContext是立即加载bean,容器启动就会把bean创建好。而BeanFactory是懒加载,容器启动不会加载bean,调用时才会加载bean。
3.4 SpringBean懒加载 - lazy
默认情况下bean是在容器启动时就创建好了,如何实现懒加载呢?即:调用的时候再创建,不调用就不创建。只需要为bean增加配置:lazy-init="true"
<bean id="user" class="cn.spring.bean.User" lazy-init="true"> </bean>
3.5 SpringBean的作用域 - scope
默认情况下bean是单例的,只会创建一个对象,就算获取多次,也返回的是同一个对象。
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user1 = context.getBean("user", User.class);
User user2 = context.getBean("user", User.class);
System.out.println(user1); //cn.spring.bean.User@57baeedf
System.out.println(user2); //cn.spring.bean.User@57baeedf
如何设置为多例呢?(这里一把不叫多例,而是:原型)即:每次获取都创建新的对象,需要增加配置:scope="prototype"
原型,默认是scope="singleton"
单例。
<bean id="user" class="包路径.User" scope="prototype"> </bean>
除了singleton单例和prototype原型这两个作用域范围,Spring还有提供有request(表示每个请求需要容器创建一个全新Bean)、session(表示每个会话需要容器创建一个全新Bean)、GlobalSession(似于标准的HTTP Session作用域)等3种作用域。
3.6 Spring创建bean的方式
- 通过反射调用构造方法创建bean对象
- 通过静态工厂方法创建bean对象
- 通过实例工厂方法创建bean对象
- 通过FactoryBean创建bean对象
1.使用无参构造(默认的),上边我们一直使用的就是该方式。
<bean id="xx" class="xxx">
2.静态工厂:构建工厂类,并提供静态方法来创建bean
public class MyBean {
}
//使用静态工厂的方式创建bean
public class MyBeanFactory {
//提供创建bean的静态方法
public static MyBean getBean(){
return new MyBean();
}
}
配置如下:
<!--
class: 配置静态工厂的全限定名
factory-method:配置静态工厂中创建实例的方法名
spring就会调用getBean()创建实例
-->
<bean class="包路径.MyBeanFactory" factory-method="getBean"></bean>
3.实例工厂:构建工厂类,并提供实例方法来创建bean
public class MyBean {
}
//使用实例工厂
public class MyBeanFactory {
//提供创建bean的实例方法
public MyBean getBean(){
return new MyBean();
}
}
4.SpringBean工厂:实现Spring提供的FactoryBean接口来创建bean
public class MyBean {
}
//实现spring提供的FactoryBean接口
public class MyBeanFactoryBean implements FactoryBean<MyBean> {
@Override //创建对象的方法,最终会调用该方法创建实例
public MyBean getObject() throws Exception {
return new MyBean();
}
@Override //获取bean的类型
public Class<?> getObjectType() {
return MyBean.class;
}
@Override //设置是否为单例
public boolean isSingleton() {
return true;
}
}
配置如下:
<bean id="myBean" class="包路径.MyBeanFactoryBean"></bean>
面试题:BeanFactory 和 FactoryBean 的区别?
- BeanFactory:他是Spring IOC 容器的最顶层接口,它的作用是管理Bean,即实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
- FactoryBean:就是一个工厂Bean,一个能生产对象的工厂Bean,通过其提供的getObject方法获取你定制化实现的bean,并可以通过isSingleton方法控制是否为单例。
4 DI - 依赖注入
4.1 什么是依赖注入呢?
假设我们有一个类,类中还有属性,按照目前的方式创建的对象,属性都只有默认值,看案例。
public class MyBean {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override //增加 toString() 方便打印看结果
public String toString() {
return "MyBean{" + "id=" + id + ", name='" + name +"'}";
}
}
2.在xml中进行bean的配置
<bean id="myBean" class="包路径.MyBean"></bean>
3.执行结果:
MyBean{id=0, name='null'} //都是默认值
所谓DI(Dependency Injection)依赖注入:就是在创建对象时为对象的属性进行赋值。
4.2 依赖注入为基本类型的属性赋值
<!--
在bean标签中使用 property属性 进行赋值
name:属性名
value:属性值
-->
<bean id="myBean" class="包路径.MyBean">
<property name="id" value="1"></property>
<property name="name" value="张三"></property>
</bean>
此时的执行结果为:MyBean{id=1, name='张三'}
为8种基本类型 和 String类型 的属性赋值,使用上述方式即可。
4.3 依赖注入为对象属性赋值
//MyBean类
public class MyBean {
private int id;
private String name;
private User user; //对象属性 User
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "MyBean{" +
"id=" + id +
", name='" + name + '\'' +
", user=" + user +
'}';
}
}
//User类
public class User {
private String username;
private String password;
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;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
使用 ref
为对象属性赋值:
<!--1.创建User的实例,bean的id为user -->
<bean id="user" class="cn.spring.bean.User">
<property name="username" value="admin"></property>
<property name="password" value="123456"></property>
</bean>
<!--2.创建MyBean的实例-->
<bean id="myBean" class="cn.spring.bean.MyBean">
<property name="id" value="1"></property>
<property name="name" value="张三"></property>
<!--使用ref关联已经创建好User的实例-->
<property name="user" ref="user"></property>
</bean>
4.4 依赖注入为集合,数组等属性赋值
public class OtherBean {
}
public class MyBean {
// 数组
private String[] arrays;
// 集合中存字符串
private List<String> list;
private Set<String> set;
// 集合中存对象
private List<OtherBean> otherBeanList;
private Set<OtherBean> otherBeanSet;
//get和set
public String[] getArrays() {
return arrays;
}
public void setArrays(String[] arrays) {
this.arrays = arrays;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public Set<String> getSet() {
return set;
}
public void setSet(Set<String> set) {
this.set = set;
}
public List<OtherBean> getOtherBeanList() {
return otherBeanList;
}
public void setOtherBeanList(List<OtherBean> otherBeanList) {
this.otherBeanList = otherBeanList;
}
public Set<OtherBean> getOtherBeanSet() {
return otherBeanSet;
}
public void setOtherBeanSet(Set<OtherBean> otherBeanSet) {
this.otherBeanSet = otherBeanSet;
}
@Override
public String toString() {
return "MyBean{" +
"arrays=" + Arrays.toString(arrays) +
", list=" + list +
", set=" + set +
", otherBeanList=" + otherBeanList +
", otherBeanSet=" + otherBeanSet +
'}';
}
}
注入配置:
<bean id="otherBean" class="包路径.OtherBean"></bean>
<bean id="myBean" class="包路径.MyBean">
<!--使用 array标签为数组赋值 -->
<property name="arrays">
<array>
<value>arrays1</value>
<value>arrays2</value>
<value>arrays3</value>
</array>
</property>
<!--使用 list标签为list赋值 -->
<property name="list">
<list>
<value>list1</value>
<value>list2</value>
<value>list3</value>
</list>
</property>
<!--使用 set标签为set赋值 -->
<property name="set">
<set>
<value>set1</value>
<value>set2</value>
<value>set3</value>
</set>
</property>
<!--使用list标签为list赋值,不同在于集合中为OtherBean-->
<property name="otherBeanList">
<list>
<!--直接bean标签赋值-->
<bean class="包路径.OtherBean"></bean>
<bean class="包路径.OtherBean"></bean>
<!--直接ref关联一个bean的id-->
<ref bean="otherBean"></ref>
<ref bean="otherBean"></ref>
</list>
</property>
<!--使用set标签为set赋值,不同在于集合中为OtherBean-->
<property name="otherBeanSet">
<set>
<!--直接bean标签赋值-->
<bean class="包路径.OtherBean"></bean>
<bean class="包路径.OtherBean"></bean>
<!--直接ref关联一个bean的id-->
<ref bean="otherBean"></ref>
<ref bean="otherBean"></ref>
</set>
</property>
</bean>
到这里咱们已经把Spring的IOC和DI有了一定的了解,会发现IOC和DI是相辅相成的两个技术,IOC构建出对象,DI辅助IOC为对象的属性赋值。
前面都是使用xml配置文件实现依赖注入,还是比较麻烦,Spring还提供了注解的方式实现依赖注入,该注解是:@Autowired
,也是后期常用的,在Spring的单元测试中就可以体会到。
5 依赖注入的2种方式
5.1 set方式注入
前面咱们在xml中的bean标签
中使用property 标签
为属性赋值,用到的就是set方式,也就是调用属性对应的set方法进行值的注入,这也是为什么我们一开始要求必须要有get和set方法的原因。
5.2 构造器注入
使用有参构造进行属性值的注入,案例如下:
public class MyBean {
private Long id; //基本属性
private String name; //基本属性
//提供全参构造
public MyBean(Long id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "MyBean{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
为基本属性赋值:
<!--使用构造器注入方式1: name的方式(参数名) -->
<bean id="myBean" class="包路径.MyBean">
<constructor-arg name="id" value="1"></constructor-arg>
<constructor-arg name="name" value="张三"></constructor-arg>
</bean>
<!--使用构造器注入方式2: index的方式(参数下标)-->
<bean id="myBean" class="包路径.MyBean">
<constructor-arg index="0" value="2"></constructor-arg>
<constructor-arg index="1" value="李四"></constructor-arg>
</bean>
<!--使用构造器注入方式3: type的方式(参数类型) -->
<bean id="myBean" class="包路径.MyBean">
<constructor-arg type="java.lang.Long" value="3"></constructor-arg>
<constructor-arg type="java.lang.String" value="王五"></constructor-arg>
</bean>
为对象属性赋值:
public class OtherBean {
}
public class MyBean { //为MyBean的属性赋值
private Long id; //基本属性
private String name; //基本属性
private OtherBean otherBean; //对象属性
//提供全参构造
public MyBean(Long id, String name,OtherBean otherBean) {
this.id = id;
this.name = name;
this.otherBean = otherBean;
}
@Override
public String toString() {
return "MyBean{" +
"id=" + id +
", name='" + name + '\'' +
", otherBean=" + otherBean +
'}';
}
}
<!--为对象属性赋值, 方式1 -->
<bean id="otherBean" class="包路径.OtherBean"></bean>
<bean id="myBean" class="包路径.MyBean">
<constructor-arg name="id" value="1"></constructor-arg>
<constructor-arg name="name" value="赵六"></constructor-arg>
<!--使用ref关联一个存在的bean的id -->
<constructor-arg name="otherBean" ref="otherBean"></constructor-arg>
</bean>
<!--为对象属性赋值, 方式2 -->
<bean id="myBean" class="包路径.MyBean">
<constructor-arg name="id" value="1"></constructor-arg>
<constructor-arg name="name" value="赵六"></constructor-arg>
<constructor-arg name="otherBean">
<!--在内部直接使用bean标签创建bean-->
<bean class="包路径.OtherBean"></bean>
</constructor-arg>
</bean>
6 Spring的单元测试和@Autowired
1.导包,在原有基础上加入 spring-test 和 junit的包
<!--Spring的测试包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.22.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
2.编写测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml") //加载配置文件并创建IOC容器
public class SpringTest {
//如果容器中有User的实例,就会根据类型注入
//如果容器中没有User的实例,就会注入失败
@Autowired
private User user; //将容器中的bean注入给属性user(自动装配)
//特殊情况下,如果User在容器中有两个实例,那么就会根据属性名user去容器中去匹配两个实例中哪个的id是user,
//如果找到了就注入,没找到就注入失败。
//所以,咱们说@Autowired优先按类型注入,其次按名字注入
@Test
public void test1(){
System.out.println(user);
}
}
3.用于注入(自动装配)的注解有@Autowired和@Resource (面试题)
@Autowired:该注解由Spring提供,优先按照类型注入,相同类型有多个时,再按照名字注入。
@Resource:该注解由Java提供(javax.annotation.Resource),也可以完成依赖注入,但是不同在于它优先按名字查询对应的bean。
7 SpringBean的生命周期
bean的生命周期,即:一个bean从生到死的过程。大致分为:创建 - 初始化 - 提供服务 - 销毁。
public class MyBean {
public MyBean() {
System.out.println("-----MyBean创建了------");
}
public void init() {
System.out.println("-----MyBean初始化了------");
}
public void destroy() {
System.out.println("-----MyBean销毁了------");
}
}
8 Spring提供了4个注解用于创建bean
Spring提供如下几个注解来标注Spring Bean:
- @Component: 标注一个普通的Spring Bean类
- @Controller: 标注一个控制器组件类
- @Service: 标注一个业务逻辑组件类
- @Repository: 标注一个DAO组件类
试想,如果有很多对象都需要交给Spring管理,那么在xml中就需要配置很多的标签,这个问题Spring也提供了对应的注解来简化配置。仅需进行包扫描,就会把包下被上述4种注解修饰的类创建好对应的实例。使用方式为:1.在xml配置文件中加上包扫描的配置 2.在类上使用注解。
<context:component-scan base-package="需要扫描的包"/>
整个案例试试:
1.编写类,并使用注解修饰
package cn.spring.bean;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
}
2.配置文件如下
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- cn.spring.bean 包下被注解修饰的类就会创建对应的实例 -->
<context:component-scan base-package="cn.spring.bean"/>
</beans>
3.测试,启动容器打印对象看看
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest {
@Autowired
private MyBean myBean;
@Test
public void test1(){
System.out.println(myBean); //cn.spring.bean.MyBean@3b69e7d1
}
}
9 纯注解版Spring
前面咱们使用了xml进行相关配置,其实Spring支持纯注解编程。咱们来感受下:
1.准备一个配置类 ,在全注解的模式下,会使用配置类来替代xml
@Configuration //标记该类是一个Spring的配置类
public class SpringConfig {
@Bean //用于修饰方法
//将该方法返回的User实例交给Spring管理,相当于 <bean id="xx" class="xx"></bean>
public User user(){
return new User();
}
}
2.进行测试
方式1:自己创建容器进行测试
//1.创建IOC容器
//使用AnnotationConfigApplicationContext来读取配置类,并创建对象
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
//2.获取容器中的实例
User user = context.getBean("user", User.class);
System.out.println(user);
方式2:使用Spring的单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class) //加载配置类
public class SpringTest {
@Autowired
private User user;
@Test
public void test1(){
System.out.println(user);
}
}
好,到这里关于Spring的IOC就差不多了,咱们接着去讨论下Spring的AOP。
我亦无他,惟手熟尔
学java找他CSDN:码赛客1024