一.什么是spring
1.spring的官网: https://spring.io
spring核心功能:当你的项目启动的时候,自动将当前的各种Bean都自动注册到Spring容器中(其中相当于map集合把bean对象存起来),然后在项目的其他地方,如果需要用到这些bean,直接去Spring容器中查找需要的对象即可。
2.Spring家族的产品:
2.1 Spring FrameWork :Spring框架的基础,我们一般所说的spring,SpringMVC其实都是Spring Framework
2.2 Spring Boot: 简化Spring 家族所有产品的配置,可以一键创建一个带有各种配置的Spring 环境。
2.3 Spring Data: 简化数据库配置/简化数据库访问。
2.4 Spring Cloud: 微服务
2.5 Spring Security : 安全管理框架
2.6 Spring Session : session共享
-
Spring Framework学习的四块:
一.ioc
二.aop
三.jdbctemplate
四.事务
二. Ioc
2.1 Ioc理解:Inversion of Control
百度百科:控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
理解:简单来说,就是对一个对象的控制权的转移。
public class Book {
private Integer id;
private String name;
private Double price;
//此处为了看的更清楚省略getter/setter和构造方法
}
public class User {
private Integer id;
private String name;
private Integer age;
public void doSth() {
Book book = new Book();
book.setId(1);
book.setName("故事新编");
book.setPrice((double) 20);
}
}
再理解,Spring 通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
没学习spring之前,我们通过一个User对象去操作Book对象没错吧,如果我们现在项目有需求,通过一千个不同的对象去花样调用一千次不同的Book对象,那我们岂不是的重新创建一千次Book对象,这样代码的耦合度太高。
现在我们学习了spring容器和ioc就可以轻松做到上面的需求。即我们可以吧对象的创建,初始化,销毁等操作,交给Spring容器来管理,所有的 Bean 都将自己注册到 Spring 容器中去(如果有必要的话),然后如果其他 Bean 需要使用到这个 Bean ,则不需要自己去 new,而是直接去 Spring 容器去要。
把控制权交给了spring容器就是我们所说的Ioc。
2.2 IOC的实现:怎么把控制权交给spring容器?
第一步:创建一个maven工程
第二步:加载spring的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
第三步:
resources目录下创建名为applicationContext.xml的配置文件(不加配置文件不出现模板)
<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">
第四步. 实现ioc,即将配置所有需要注册到Spring容器的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 class="com.huang.demo.p2.model.Book" id="book"/>
</beans>
1.class 属性表示需要注册的 bean 的全路径,id 则表示 bean 的唯一标记,也开可以 name 属性作为 bean 的标记,在超过 99% 的情况下,id 和 name 其实是一样的,特殊情况下不一样
第五步. 配置一个进行测试的类
public static void main(String[] args) {
//1.这里只需要写配置文件的文件名即可,系统会自动去 classpath 下查找配置文件
//2.这个就是加载 Spring 容器,只要 Spring 容器启动了,那么配置文件中的所有 Bean 就会完成初始化
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//1.根据名字去查找一个 user 对象,这个方法返回一个 Object,需要进行类型转换
//2.告诉 Spring 容器,查找一个名为 user 的 bean,并且类型是 User
//3.如果对象名重复就会进行报错
User u3 = ctx.getBean("user", User.class);
u3.sayHello();
}
1.执行 main 方法,配置文件就会被自动加载,进而在 Spring 中初始化
一个 Book 实例。此时,我们显式的指定 Book 类的无参构造方法,
并在无参构造方法中打印日志,可以看到无参构造方法执行了,进而证明对象
已经在 Spring 容器中初始化了。
2.加载方式,除了ClassPathXmlApplicationContext 之外(
去 classpath 下查找配置文件),另外也可以使用 FileSystemXmlApplicationContext
,FileSystemXmlApplicationContext 会从操作系统路径下去寻找配置文件。
public class Main {
public static void main(String[] args) {
FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext("F:\\workspace5\\workspace\\spring\\spring-ioc\\src\\main\\resources\\applicationContext.xml");
Book book = (Book) ctx.getBean("book");
System.out.println(book);
}
}
三.Spring容器获取Bean
3.1spring容器理解
:就把spring看出装咖啡豆的容器,里面装了一堆对象,bean就是那行对象
3.2正常情况
启动Spring容器,并且配置文件,当Spring容器启动之后,无论你是否跟Spring容器去User对象,此时User对象都是已经创建好的状态,并保存在Spring容器中。
public class MainDemo01 {
public static void main(String[] args) {
//这里只需要写配置文件的文件名即可,系统会自动去 classpath 下查找配置文件
//这个就是加载 Spring 容器,只要 Spring 容器启动了,那么配置文件中的所有 Bean 就会完成初始化
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//根据名字去查找一个 user 对象,这个方法返回一个 Object,需要进行类型转换
User u1 = (User) ctx.getBean("user");
u1.sayHello();
//去查找 User 类型的对象
//这种方式有一个缺陷:Spring 容器中如果存在多个 User 对象,那么这个方法执行就会报错
User u2 = ctx.getBean(User.class);
u2.sayHello();
//告诉 Spring 容器,查找一个名为 user 的 bean,并且类型是 User
User u3 = ctx.getBean("user", User.class);
u3.sayHello();
}
}
3.3异常情况
NoSuchBeanDefinitionException(没有找到需要的Bean):
1.先检查注册的时候,bean的名称是否正确
2.检查跟 Spring 容器要的时候,Bean 的名称是否正确。
3.检查一下启动 Spring 容器时,Spring 的配置文件名称是否正确
NoUnitBeanDefinitionException():这表示要查找的目标 Bean 有多个,查找异常。这种时候就不要按照类型去查找,而应该按照名字去查找。
四.Bean的获取(如何注入Spring中)
4.1.构造器注入
A:默认情况下,如果我们向spring容器注入一个Bean的时候,如果你的类里面没有无参构造方法,就会出错
B:例举
book实体类
public class Book {
private Integer id;
private String name;
private String author;
public Book(Integer id, String name, String author) {
this.id = id;
this.name = name;
this.author = author;
}
}
applicationConfig.xml
<bean class="com.huang.demo.p2.model.Book" id="book"/>
出错详解:由于这个Java类没有无参构造方法(默认使用无参构造方法),所有在注入Bean的时候,如果按照上面的方式注入,就会出错,解决办法(1.bean标签中使用指定的构造方法。)
C:如何使用指定的构造方法
applicationConfig.xml
<!--這裏沒有指定那个构造方法,所以这里默认无参构造,如果实体类中定义了有参构造后,没有定义无参构造就会报错-->
<bean class="com.huang.demo.model.Book" id="book"/><!--這裏沒有指定那个构造方法,所以这里默认无参构造,如果实体类中定义了有参构造后,没有定义无参构造就会报错-->
<!-- 也可以指定要用那个构造方法-->
<bean class="com.huang.demo.model.Book" id="book2">
<constructor-arg name="id" value="1"/>
<constructor-arg name="name" value="三国演义"/>
<constructor-arg name="author" value="罗贯中"/>
</bean>
可能出现的错误:
有几个属性构造方法就写几个对应属性的
负责一定会出错
4.2.set方法注入
applicationConfig.xml
<!--這裏沒有指定那个构造方法,所以这里默认无参构造,如果实体类中定义了有参构造后,没有定义无参构造就会报错-->
<!-- 也可以指定要用那个构造方法-->
<bean class="com.huang.demo.model.Book" id="book2">
<property name="id" value="2"/>
<property name="name" value="红楼梦"/>
<property name="author" value="曹雪芹"/>
</bean>
4.3.p 名称空间注入
A:p名称空间注入,本质上,其实就是set方法注入。
B:
applicationConfig.xml
<!--没有指定构造方法,依然是使用默认的构造方法-->
<bean class="com.huang.demo.model.Book" id="book5" p:name="黄黄五号"
p:id="5" p:author="鲁班大师">
</bean>
补充注意:
p:name的对应的属性,
想写几个写几个
4.4 复杂属性注入
复杂属性的注入指的是:List,数组,对象,Map,Set,Properties等类型的注入
4.4.1 首先定义User类
public class User {
private Integer id;
private String name;
private String address;
private Cat cat;
private List<String> favorites;
private List<Cat> cats;
private Book[] books;
private Map<String, Object> info;
private Properties school;
}
4.2.2List,数组,对象,Map,Set,Properties属性注入:
<?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 class="com.qfedu.demo.model.Cat" id="cat">
<property name="name" value="小白"/>
<property name="color" value="白色"/>
</bean>
<bean class="com.qfedu.demo.model.User" id="user">
<property name="id" value="1"/>
<property name="name" value="zhangsan"/>
<property name="address" value="广州"/>
<!-- ref 表示这里的值是引用了一个提前定义好的 cat 对象即上面名为小白的白色狗,同理如:
用 set 方法给 cat 赋值的时候,有两种方式:
第一种 Cat cat = new Cat();
setCat(cat);
第二种 setCat(new Cat()); -->
<property name="cat" ref="cat"/>
<property name="favorites">
<!--
list 标签表示数据类型是一个 List
如果 list 中存放的数据是字符串,那么这里就直接使用 value
如果 list 中存放的数据库是对象,那么可以使用 ref 去引用外部的 对象,也可以使用 bean 标签现场定义 Bean
-->
<list>
<value>足球</value>
<value>篮球</value>
</list>
</property>
<property name="cats">
<list>
<!--引用外部定义的 cat-->
<ref bean="cat"/>
<bean class="com.qfedu.demo.model.Cat">
<property name="name" value="小黑"/>
<property name="color" value="黑色"/>
</bean>
</list>
</property>
<property name="books">
<array>
<bean class="com.qfedu.demo.model.Book">
<property name="id" value="1"/>
<property name="name" value="三国演义"/>
<property name="author" value="罗贯中"/>
</bean>
<bean class="com.qfedu.demo.model.Book">
<property name="id" value="2"/>
<property name="name" value="红楼梦"/>
<property name="author" value="曹雪芹"/>
</bean>
</array>
</property>
<property name="info">
<!--
map 的定义
-->
<map>
<entry key="gender" value="男"/>
<entry key="age" value="99"/>
</map>
</property>
<property name="school">
<props>
<prop key="name">广州千锋</prop>
<prop key="address">广州市白云区</prop>
</props>
</property>
</bean>
</beans>
4.5 外部 Bean 的注入
有时候,我们使用一些外部 Bean,这些 Bean 可能没有构造方法,而是通过 Builder 来构造的,这个时候,就无法使用上面的方式来给它注入值了。
例如在 OkHttp 的网络请求中,原生的写法如下:
public class OkHttpMain {
public static void main(String[] args) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.build();
Request request = new Request.Builder()
.get()
.url("http://b.hiphotos.baidu.com/image/h%3D300/sign=ad628627aacc7cd9e52d32d909032104/32fa828ba61ea8d3fcd2e9ce9e0a304e241f5803.jpg")
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
System.out.println(e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
FileOutputStream out = new FileOutputStream(new File("E:\\123.jpg"));
int len;
byte[] buf = new byte[1024];
InputStream is = response.body().byteStream();
while ((len = is.read(buf)) != -1) {
out.write(buf, 0, len);
}
out.close();
is.close();
}
});
}
}
这个 Bean 有一个特点,OkHttpClient 和 Request 两个实例都不是直接 new 出来的,在调用 Builder 方法的过程中,都会给它配置一些默认的参数。这种情况,我们可以使用 静态工厂注入或者实例工厂注入来给 OkHttpClient 提供一个实例。
1.静态工厂注入
首先提供一个 OkHttpClient 的静态工厂:
public class OkHttpUtils {
private static OkHttpClient OkHttpClient;
public static OkHttpClient getInstance() {
if (OkHttpClient == null) {
OkHttpClient = new OkHttpClient.Builder().build();
}
return OkHttpClient;
}
}
在 xml 文件中,配置该静态工厂:
<bean class="org.javaboy.OkHttpUtils" factory-method="getInstance" id="okHttpClient"></bean>
这个配置表示 OkHttpUtils 类中的 getInstance 是我们需要的实例,实例的名字就叫 okHttpClient。然后,在 Java 代码中,获取到这个实例,就可以直接使用了。
public class OkHttpMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OkHttpClient okHttpClient = ctx.getBean("okHttpClient", OkHttpClient.class);
Request request = new Request.Builder()
.get()
.url("http://b.hiphotos.baidu.com/image/h%3D300/sign=ad628627aacc7cd9e52d32d909032104/32fa828ba61ea8d3fcd2e9ce9e0a304e241f5803.jpg")
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
System.out.println(e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
FileOutputStream out = new FileOutputStream(new File("E:\\123.jpg"));
int len;
byte[] buf = new byte[1024];
InputStream is = response.body().byteStream();
while ((len = is.read(buf)) != -1) {
out.write(buf, 0, len);
}
out.close();
is.close();
}
});
}
}
2.实例工厂注入
实例工厂就是工厂方法是一个实例方法,这样,工厂类必须实例化之后才可以调用工厂方法。
这次的工厂类如下:
public class OkHttpUtils {
private OkHttpClient OkHttpClient;
public OkHttpClient getInstance() {
if (OkHttpClient == null) {
OkHttpClient = new OkHttpClient.Builder().build();
}
return OkHttpClient;
}
}
此时,在 xml 文件中,需要首先提供工厂方法的实例,然后才可以调用工厂方法:
<bean class="org.javaboy.OkHttpUtils" id="okHttpUtils"/>
<bean class="okhttp3.OkHttpClient" factory-bean="okHttpUtils" factory-method="getInstance" id="okHttpClient"></bean>
自己写的 Bean 一般不会使用这两种方式注入,但是,如果需要引入外部 jar,外部 jar 的类的初始化,有可能需要使用这两种方式。
4.6 注意属性的名字
内省。
对于框架而言,并不是看对象定义的属性叫什么名字,而是根据对象的 get/set 方法来推断属性名称,无论是 MyBatis、Spring、SpringMVC,所有框架,只要用到反射,都是这样的。所以,定义 get/set 方法的时候,不要写错,另一方面,变量的命名要符合规范。
4.7 Java 代码配置 Spring
理解:用一个 Java 配置类,去代替 XML 配置即可:
/**
* 这个是 Java 配置类,它的作用类似于 applicationContext.xml
*
* @Configuration 表示这是一个配置类
*/
@Configuration
public class JavaConfig {
/**
* @Bean 就表示将当前方法的返回值注册到 Spring 容器中
*
* 默认情况下,方法名称就是 bean 的名字
*
* 如果需要自定义 bean 的名称,那么在注解中配置即可
*
* @return
*/
@Bean("u")
User user() {
User user = new User();
user.setAddress("广州");
user.setName("zhangsan");
return user;
}
}
启动Spring容器的时候,用此配置类
public class Demo01 {
public static void main(String[] args) {
//启动 Spring 容器,加载一个 Java 配置类,构造方法中指定配置类即可
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
User user = ctx.getBean("u", User.class);
System.out.println("user = " + user);
}
}
5. @Configuration注解和@Component注解注解的区别
5.1 @Configuration注解
@Configuration:这个注解表示当前类是一个配置类,那么当前类中,所有添加@Bean注解的方法都会被注册到Spring容器中,如果有其他方法调用到一个添加了@Bean注解的方法,那么不会立马执行对应的方法,而是先去Spring容器中查看是否有对应的对象,如果有,则直接从容器中获取即可,如果容器中没有的话,才回去执行对应的方法。
5.2 @Component注解
@Component
虽然@Component也可以加载到配置类上,但是,如果有其他的方法调用到一个添加了@Bean注解的方法,那么不会先去Spring容器中查看是否有对应的对象,而是执行对应的方法,所以一般在配置类中不使用@Component注解。
如果一定要使用@Component注解,可以通过依赖注入的方法。
如下图:
/**
* 向 Spring 容器注册一个 Author 对象
*
* @return
*/
@Bean
Author author() {
Author author = new Author();
author.setName("鲁迅");
author.setAge(55);
return author;
}
/**
* 向 Spring 容器中注册一个 Book 对象
*
* book 中有一个 author 对象,book 中的 author 和 spring 容器中的 author 是否是同一个对象?
* @return
*/
@Bean
Book book2(Author author) {
Book book = new Book();
book.setName("故事新编");
book.setAuthor(author);
book.setPrice(18.0);
return book;
}
在这里,所有的方法都是Spring容器调用,当Spring容器调用book2这个方法的时候,就会发现这个方法的执行需要一个Author类型的参数,am此时Spring容器就会去查找是覅有一个Auther,如果有,则直接作为参数传递进来,如果Spring容器中没有这个对象,那么就直接抛出异常。