Spring 概述
(我是先学了springboot,现在穿越回来啃啃spring老祖宗,看看有什么不一样的发现)
Spring 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用 Spring 框架来创建性能好、易于测试、可重用的代码。(以上是官方话)
简单来说:spring诞生的目的是为了解决企业应用开发的复杂性!
创始人:Rod Johnson ----一个音乐学博士 有点东西给是?
两个开发框架组合:
SSH:Struct2 + Spring + Hibernate
SSM: SpringMvc + Spring + Mybatis
Spring的优点:
1.Spring是一个开源的、免费的框架。
2.Spring是一个轻量级的、非入侵式的框架(非入侵就是在原有项目中导入spring项目,不会影响原项目。)
3.控制反转、面向切面编程(AOP)
4.支持事物处理,对框架整合的的支持。
IoC本质.
IoC在SpringBoot笔记中已经说明了,这里不做过多的解释了。
控制反转loC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法。
没有IoC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序
中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方。
创建第一个Spring
需要先给出官方文档地址:
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html
先生成一个实体类
package com.heyexi.pojo;
public class Hello
{
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
@Override
public String toString()
{
return "Hello{" +
"name='" + name + '\'' +
'}';
}
}
然后需要创建一个配置文件,以下是标准格式
<?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>
然后就可以在里面配置我们的Javabean了。
<?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">
<bean id="hi" class="com.heyexi.pojo.Hello">
<property name="name" value="何夜息"></property>
</bean>
</beans>
我们要自动装配一个bean,那么可以通过bean标签来设置,配置一个bean就相当于实例化了一个对象,把这个对象交给spring去管理,需要用到时候我们只需要通过spring上下文就可以获取了。
其中:id是相对于对象的名字,例如: Dog hsq = new Dog();那么id就是hsq,然后后面的class是告诉spring这个实体类的具体位置。
那么如何给这个对象设置属性呢?
可以通过下面的property 标签来设置对象的属性,name就是属性名,value就是值,也很简单,很好理解。
既然我们已经配置好了,那么如果获取呢?通过:实例化容器(ApplicationContext )来获取:!
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Mytest
{
public static void main(String[] args)
{
//获取Spring上下文对象,这句话是固定写法
//ClassPathXmlApplicationContext里面可以写多个xml配置文件的名字
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
System.out.println(applicationContext.getBean("hi"));
}
}
可以看到,我们在测试类中先生成一个ApplicationContext 对象,这个是用来获取bean对象的。
然后通过getBean("bean的ID")就可以获取到我们配置的对象了,如输出结果如下:可以看到成功获取了对象,而不必须我们去自己new一个对象,这就会控制反转。
7月 10, 2020 4:27:03 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [beans.xml]
Hello{name='何夜息'}
注意,如果我们自定义了有参构造方法,那么配置文件中的bean就会报错,因为IOC创建不了无参对象,所有才会有Javabean的规范,就是要有无参构造方法。
那要如何配置有构造参数的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">
<bean id="hi" class="com.heyexi.pojo.Hello">
<constructor-arg index="0" value="何夜息"></constructor-arg>
</bean>
</beans>
可以看到是使用了<constructor-arg>这个标签来说创建的。
还有一种最常用的方式,就是通过直接构造方法中的参数赋值就行。
<bean id="hi" class="com.heyexi.pojo.Hello">
<constructor-arg name="name" value="何夜息"></constructor-arg>
</bean>
name的值跟的就是构造方法的参数名!!例如假设构造参数多了一个age,那么我们可以:就很方便!!!
<bean id="hi" class="com.heyexi.pojo.Hello">
<constructor-arg name="name" value="何夜息"></constructor-arg>
<constructor-arg name="age" value="20"></constructor-arg>
</bean>
特别重要的一个知识点:不管bean是否被调用,只要在配置文件中配置了,那么IOC就已经把对象给创建好了!
比如:我新建了一个实体类--User,然后在无参构造方法中输出一段话,也就是只要这个类被实例化就输出这句话!
package com.heyexi.pojo;
public class User
{
private String name;
private Integer id;
public User()
{
System.out.println("用户被创建!");
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Integer getId()
{
return id;
}
public void setId(Integer id)
{
this.id = id;
}
}
然后我把它装配到配置文件中:
<?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">
<!-- <bean id="hi" class="com.heyexi.pojo.Hello">-->
<!-- <constructor-arg index="0" value="何夜息"></constructor-arg>-->
<!-- </bean>-->
<bean id="hi" class="com.heyexi.pojo.Hello">
<constructor-arg name="name" value="何夜息"></constructor-arg>
<constructor-arg name="age" value="20"></constructor-arg>
</bean>
<bean id="user" class="com.heyexi.pojo.User"></bean>
</beans>
然后在测试类中只调用第一个对象!看看运行结果!
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Mytest
{
public static void main(String[] args)
{
//获取Spring上下文对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
System.out.println(applicationContext.getBean("hi"));
}
}
结果显示User也被创建了!!!说明只要是弄到配置文件中,Spring都会把所有的Bean都生成出来,不管你是否调用!
Spring配置的其他配置:
别名:别名相当于换了个id名,很简单。没啥用,因为bean的name可以用空格取多个名字。
<alias name="user" alias="user2"></alias>
import:导入其他的bean配置文件。
<import resource="beans2.xml"></import>
依赖注入(DI)
什么是依赖注入?我的理解就是:
依赖:就是我们的应用程序依赖于容器,比如在某个业务中,我们需要一个连接数据库的Connection对象,但我们可以通过依赖容器,让容器给我们这个对象。
注入:就是在配置文件中,把对象的各种属性注入。
如何注入多种类型的属性?
比如,我们创建一个Javabean
package com.heyexi.pojo;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class Teacher
{
private String name;
private Hello hello;
private String[] course;
private List<String> hobby;
private Map<String,String> id;
}
然后把这个注入到到配置文件中,请看配置信息:
<bean id="teacher" class="com.heyexi.pojo.Teacher">
<!--普通类型通过value注入-->
<property name="name" value="何夜息"></property>
<!--引用对象类型通过ref注入-->
<property name="hello" ref="hi"></property>
<!--数组也是属于引用对象类型-->
<property name="course">
<array>
<value>数据结构与算法</value>
<value>数据库导论</value>
<value>离散数学</value>
</array>
</property>
<!--List注入-->
<property name="hobby">
<list>
<value>写代码</value>
<value>听音乐</value>
<value>跑步</value>
<value>弹吉他</value>
</list>
</property>
<!--map注入-->
<property name="id">
<map>
<entry key="教师编号" value="18744"></entry>
<entry key="住址" value="云南"></entry>
</map>
</property>
</bean>
需要注意的也就是map的注入方式,是通过实体标签,通过给实体赋值键值对进行注入的。
然后我们在测试类中引入这个对象。
public class Mytest
{
public static void main(String[] args)
{
//获取Spring上下文对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
System.out.println(applicationContext.getBean("teacher"));
}
}
输出结果如下:
信息: Loading XML bean definitions from class path resource [beans.xml]
Teacher(name=何夜息, hello=Hello{name='何夜息'},
course=[数据结构与算法, 数据库导论, 离散数学],
hobby=[写代码, 听音乐, 跑步, 弹吉他], id={教师编号=18744, 住址=云南})
bean的作用域(Bean Scopes)
以上就是官网的六种作用域,默认是单例模式,如下是中文版!把最后两个合并为了一个。
作用域 | 描述 |
singleton | 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值 |
prototype | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean() |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境 |
global-session | 一般用于Portlet应用环境,该运用域仅适用于WebApplicationContext环境 |
- 单例模式
就是全局共享一个对象,不管在哪调用都是同一个对象。
<bean id="user" class="com.heyexi.pojo.User" scope="singleton"></bean>
- 原型模式
这个相当于每次从容器中拿的时候都是新new出来的对象。
<bean id="user" class="com.heyexi.pojo.User" scope="prototype"></bean>
Spring Beans 自动装配
除了使用可以使用配置文件中<property>来我们自己指定id获取bean外,我们也可以通过属性autowire来让spring自己去找需要的bean。
这个在SpringBoot中不是随处可见么?也没啥可以解释的了吧,一下是获取方式。
no | 这是默认的设置,它意味着没有自动装配,你应该使用显式的bean引用来连线。你不用为了连线做特殊的事。在依赖注入章节你已经看到这个了。 |
由属性名自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byName。然后尝试匹配,并且将它的属性与在配置文件中被定义为相同名称的 beans 的属性进行连接。 | |
由属性数据类型自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byType。然后如果它的类型匹配配置文件中的一个确切的 bean 名称,它将尝试匹配和连接属性的类型。如果存在不止一个这样的 bean,则一个致命的异常将会被抛出。 | |
类似于 byType,但该类型适用于构造函数参数类型。如果在容器中没有一个构造函数参数类型的 bean,则一个致命错误将会发生。 | |
autodetect | Spring首先尝试通过 constructor 使用自动装配来连接,如果它不执行,Spring 尝试通过 byType 来自动装配。 |
自动装配的局限性
当自动装配始终在同一个项目中使用时,它的效果最好。如果通常不使用自动装配,它可能会使开发人员混淆的使用它来连接只有一个或两个 bean 定义。不过,自动装配可以显著减少需要指定的属性或构造器参数,但你应该在使用它们之前考虑到自动装配的局限性和缺点。
只要注意byName是根据Javabean的set方法来获取的,就只需要保证xml中bean的id唯一。
byType则是根据xml中class的类型来匹配的,就不能有两个相同 class的配置文件,就会出错,需要保证bean的class唯一。
Spring的注解
首先,要使用注解需要在xml中提供注解约束
需要加入如下来句话:
xmlns:context="http://www.springframework.org/schema/context"
<context:annotation-config/>
@Component注解:
这个注解的作用就是可以不再xml中配置bean了,比如我们新建一个实体类,然后在类上使用该注解。
package com.heyexi.pojo;
import org.springframework.stereotype.Component;
@Component //相当于:<bean id="dog" class="com.heyexi.pojo.User" />
public class Dog
{
private String name;
@Override
public String toString()
{
return "Dog{" +
"name='" + name + '\'' +
'}';
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
然后就可以在测试类中使用它了!
import com.heyexi.pojo.Dog;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Mytest
{
public static void main(String[] args)
{
//获取Spring上下文对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Dog dog = (Dog)applicationContext.getBean("dog");
System.out.println(dog);
}
}
//输出
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7637f22: startup date [Sun Jul 12 00:22:04 CST 2020]; root of context hierarchy
7月 12, 2020 12:22:04 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [beans.xml]
Dog{name='null'}
注意:调用时这个组件的名字就是类名的小写。
但是要使用这个组件注解,我们需要在配置文件中指定组件所在的包!就是下面这一句!
<context:component-scan base-package="com.heyexi.pojo" />
@Component有几个衍生注解,功能都一样,并没有其他特殊的功能,都是把类注入到spring容器中,只是为了区分类的作用,取了不同的名字。
- 在dao层我们使用 @Repository注解
- 在service层我们使用 @Service
- 在controller层我们使用 @Controller
@Value注解:
这个注解相当于是xml配置文件中的property,就是给成员变量赋值,可以配合Comment一起使用的话,完全就可以脱离配置文件了。
@Component
public class Dog
{
@Value("哈士奇") //直接就把值给注入了
private String name;
输出:
信息: Loading XML bean definitions from class path resource [beans.xml]
Dog{name='哈士奇'}
要多用注解,因为在SpringBoot中基本只使用注解,代替了配置。
但是注解和配置都有自己特点:
使用配置文件的好处就是维护方便,所有的bean都统一管理,而注解维护不方便,要修改就需要去单独找。
所以,最佳的实践就是:
Xml中负责配置bean
注解(@value)负责在类中注入属性,而不再xml文件中使用属性,看着方便管理。
使用Configuration完全代替xml配置:
我们可以使用@Configuration注解,来代替xml配置文件,这个在SpringBoot中就是这么做的,我都不清楚这两个有什么区别了。
这个特别简单,就是直接生成一个类,给这个类配置@Configuration,然后在里面通过@bean注解配置bean,就会由spring来接管,如下代码:
package com.heyexi.config;
import com.heyexi.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig
{
@Bean
public User getUser()
{
return new User();
}
}
我弄了一个Bean,是用来获取User对象的,然后在测试类试着拿出来。
import com.heyexi.config.MyConfig;
import com.heyexi.pojo.Dog;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Mytest
{
public static void main(String[] args)
{
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
System.out.println(applicationContext.getBean("getUser"));
}
}
输出结果:
WARNING: All illegal access operations will be denied in a future release
用户被创建!
User{name='null', id=null}
可以看到,现在上下文是通过new AnnotationConfigApplicationContext("配置类的字节码")来获取的,不是通过xml,结果都是一样的。
只需要注意getBean默认是通过配置类的方法名获取的,当然也可以取别名,这个在SpringBoot笔记中写得很清楚了。
作用域可以使用@Scope注解来使用!这个还是很重要的,有时候某些实体我们需要使用单例模式,比如dao类,有时候有些实体又需要使用原型模式,这样才能拿到不同的对象。
比如我们定义为原型:
@Configuration
public class MyConfig
{
@Bean
@Scope("prototype")
public User getUser()
{
return new User();
}
}
然后在测试类中:
public class Mytest
{
public static void main(String[] args)
{
// //获取Spring上下文对象
// ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//
// Dog dog = (Dog)applicationContext.getBean("dog");
// System.out.println(dog);
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
User user1 = (User) applicationContext.getBean("getUser");
User user2 = (User) applicationContext.getBean("getUser");
System.out.println(user1.hashCode());
System.out.println(user2.hashCode());
}
输出结果:
WARNING: All illegal access operations will be denied in a future release
用户被创建!
用户被创建!
1948471365
1636506029
说明拿到的是不同的对象。
如果我们改为单例模式:输出结果为:
WARNING: All illegal access operations will be denied in a future release
用户被创建!
60292059
60292059
拿到的是同一个对象,同时可以看到构造方法都只执行了一个,说明都是同一个对象,因为是单例模式。