原生web开发中存在的问题:
1、传统Web开发中代码程序过度耦合
2、部分JavaEE API较为复杂 使用率底
3、侵入性强,移植性差
Spring框架
介绍:1、一个项目管理框架
2、众多优秀设计模式的整合(工厂模式、单例模式、代理模式等等)
3、Spring并未代替原有的框架产品 而是对众多框架产品进行一个整合
Spring的访问与下载:
官方网站:https://spring.io/
下载地址:http://repo.spring.io/release/org/springframework/spring/
Spring架构的组成
● 核心技术:**依赖注入**,事件,资源,i18n,验证,数据绑定,类型转换,SpEL,**AOP**。
● 测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。
● 数据访问:事务,DAO支持,JDBC,ORM,封送XML。
● Spring MVC和 Spring WebFlux Web框架。
● 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
● 语言:Kotlin,Groovy,动态语言。
常用的几个依赖包的解释:
本次学习从四个方面学习Spring框架
Ioc(Inversion of Control)/ DI (控制反转) :将控制权由其他转到了Spring UserDao注入到Spring后 UserService通过反射拿到UserDao
原理:XML解析+利用反射创建一个Bean 将初始化以后得Bean存入到Spring中
具体用法:
1. 引入 Spring 依赖 spring-context。
2. 创建 Spring 配置文件,在配置文件中,向 Spring 容器去注册一个 Bean。
2.1 基本属性注入
2.2 复杂属性注入
2.3 静态工厂注入/实例工厂注入
2.4 FactoryBean 工厂注入
3. 默认清空下,当 Spring 容器启动的时候,所以注册到 Spring 容器中的 Bean 就会自动被初始化。
4. 接下来就可以从 Spring 容器中去获取一个 Bean 了。
AOP :
JdbcTemplate :
事务:
Spring_Ioc:
public class BeanContext {
private Map<String, Object> map = new HashMap<>();
public Map<String, Object> getMap() {
return map;
}
/**
* 参数是配置文件的名字
*/
public BeanContext(String beanFileName) {
try {
SpringBean springBean = new ObjectMapper().readValue(BeanContext.class.getResourceAsStream(beanFileName), SpringBean.class);
//读取 JSON 中的 clazz 属性,利用反射创建出来一个 Bean
Class<?> aClass = Class.forName(springBean.getClazz());
Object o = aClass.getConstructor().newInstance();
//将初始化之后的 Bean 存入到 Spring 容器中
map.put(springBean.getName(), o);
} catch (IOException | NoSuchMethodException | ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
Spring_IOC属性注入:
xml配置:UserDao配置后就会上传到Bean中
三种方式注入对象属性值:
1.通过有参和无参构造构造方法
2.通过提供的set方法
3.通过开辟P空间的方法 (底层逻辑也是set方法)
<!--向 Spring 容器注册一个 Bean-->
<bean name="userDao" class="com.qfedu.demo.dao.UserDao"/>
<bean name="userDao2" class="com.qfedu.demo.dao.UserDao"/>
<!--
由于 Spring 是通过反射去获取 User 的实例的,而通过反射获取一个类的实例也是需要通过构造方法的,所以我们需要确保这里的配置要有相匹配的构造方法
-->
<bean id="u1" class="com.qfedu.demo.model.User">
<!--Spring将来会调用 User 类中包含了三个参数的构造方法去初始化 User-->
<constructor-arg name="address" value="guangzhou"/>
<constructor-arg name="id" value="99"/>
<constructor-arg name="username" value="zhangsan"/>
</bean>
<bean id="u2" class="com.qfedu.demo.model.User">
<!--这个会自动跟只有两个参数的构造方法匹配-->
<constructor-arg name="id" value="100"/>
<constructor-arg name="username" value="lisi"/>
</bean>
<bean id="u3" class="com.qfedu.demo.model.User">
<!--通过 set 方法为属性注入值,下面这三个配置,分别会调用到各自的
set 方法,注意 name 中的值,是 根据 set 方法推断出来的值,不是属性的名字-->
<property name="id" value="101"/>
<property name="username" value="wangwu"/>
<property name="address" value="shenzhen"/>
</bean>
<!--p 名称空间注入,本质上依然是 set 方法注入-->
<bean id="u4" class="com.qfedu.demo.model.User" p:id="102" p:address="shanghai" p:username="zhaoliu"/>
<bean id="cat" class="com.qfedu.demo.model.Cat">
<property name="name" value="小白"/>
<property name="color" value="白色"/>
<property name="age" value="3"/>
</bean>
<bean id="d1" class="com.qfedu.demo.model.Dog">
<property name="name" value="小黑"/>
</bean>
实例化对象的代码块:
注意:这里的名称要和上面xml注入的保持一致
public class User {
private Integer id;
private String username;
private String address;
public User() {
}
public User(Integer id, String username) {
this.id = id;
this.username = username;
}
public User(Integer id, String username, String address) {
this.id = id;
this.username = username;
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", address='" + address + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
方法测试的核心代码块:
public class Demo01 {
public static void main(String[] args) {
//默认会去 classpath 下查找配置文件
//初始化 Spring 容器,默认情况下,当容器初始化的时候,UserDao 就已经被实例化了
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//跟 Spring 容器去要一个名为 userDao 的 Bean
UserDao userDao = (UserDao) ctx.getBean("userDao");
System.out.println("userDao.sayHello() = " + userDao.sayHello());
//注意 Bean 的名字不要写错,没有找到UserDao2 则报错,会抛出 NoSuchBeanDefinitionException 异常
UserDao userDao01 = (UserDao) ctx.getBean("userDao2");
//跟 Spring 容器要 Bean 的时候,同时告诉 Spring 容器,我们需要的 Bean 是一个 UserDao
UserDao ud2 = ctx.getBean("userDao", UserDao.class);
System.out.println("ud2.sayHello() = " + ud2.sayHello());
//直接根据类型去 Spring 容器中查找需要的 Bean
//如果使用类型去查找 Bean,需要确保 Spring 容器中只有这一个 Bean,否则会抛出异常:NoUniqueBeanDefinitionException
//以下语句运行后会报错 因为没有唯一的Bean
UserDao ud3 = ctx.getBean(UserDao.class);
System.out.println("ud3.sayHello() = " + ud3.sayHello());
User u1 = ctx.getBean("u1", User.class);
System.out.println("u1 = " + u1);
User u3 = ctx.getBean("u3", User.class);
System.out.println("u3 = " + u3);
User u4 = ctx.getBean("u4", User.class);
System.out.println("u4 = " + u4);
}
}
运行结果:
复杂属性注入Bean
属性类:
public class User2 {
private String name;
private Cat cat;
private List<String> favorites;
private Date birthday;
private Map<String,Object> info;
private Properties info2;
private Dog[] dogs;
public User2() {
}
public User2(String name, Cat cat, List<String> favorites, Date birthday, Map<String, Object> info, Properties info2, Dog[] dogs) {
this.name = name;
this.cat = cat;
this.favorites = favorites;
this.birthday = birthday;
this.info = info;
this.info2 = info2;
this.dogs = dogs;
}
@Override
public String toString() {
return "User2{" +
"name='" + name + '\'' +
", cat=" + cat +
", favorites=" + favorites +
", birthday=" + birthday +
", info=" + info +
", info2=" + info2 +
", dogs=" + Arrays.toString(dogs) +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public List<String> getFavorites() {
return favorites;
}
public void setFavorites(List<String> favorites) {
this.favorites = favorites;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Map<String, Object> getInfo() {
return info;
}
public void setInfo(Map<String, Object> info) {
this.info = info;
}
public Properties getInfo2() {
return info2;
}
public void setInfo2(Properties info2) {
this.info2 = info2;
}
public Dog[] getDogs() {
return dogs;
}
public void setDogs(Dog[] dogs) {
this.dogs = dogs;
}
}
对应的xml 注入方式
给猫这个对象设置属性值
<bean id="cat" class="com.qfedu.demo.model.Cat">
<property name="name" value="小白"/>
<property name="color" value="白色"/>
<property name="age" value="3"/>
</bean>
给狗这个对象设置属性值
<bean id="d1" class="com.qfedu.demo.model.Dog">
<property name="name" value="小黑"/>
</bean>
利用构造方法加上引用给dog对象赋值
<bean id="u6" class="com.qfedu.demo.model.User2">
<constructor-arg name="cat">
</constructor-arg>
</bean>
<bean id="u5" class="com.qfedu.demo.model.User2">
<property name="name" value="zhangsan"/>
<!--ref表示引用一个外部的变量 下面那一行就是引用上面小白那只猫-->
<property name="cat" ref="cat"/>
兴趣爱好定义为一个list集合 这是注入Bean的写法
<property name="favorites">
<list>
<value>足球</value>
<value>篮球</value>
</list>
</property>
map的注入写法
<property name="info">
<map>
<entry key="gender" value="男"/>
<entry key="cat" value-ref="cat"/>
</map>
</property>
<property name="dogs">
<array>
<!--引用一个提前定义好的对象-->
<ref bean="d1"/>
<!--现场定义一个 Dog 对象-->
<bean class="com.qfedu.demo.model.Dog">
<property name="name" value="小黄"/>
</bean>
</array>
</property>
properties文件设置值然后注入Bean
<property name="info2">
<props>
<prop key="age">99</prop>
<prop key="address">guangzhou</prop>
</props>
</property>
<property name="birthday" value="2022/09/20"/>
</bean>
第三方工具类注入Bean的方法:
以OkHttpClienFactory为例,该工具作用是获取到该网页的源代码并返回
没注入Spring前的第三方工具类写法:每次都需要new 对象出来调用方法
public class Demo03 {
public static void main(String[] args) {
//1. 构建 OkHttpClient 对象
OkHttpClient okHttpClient = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(5, TimeUnit.SECONDS).build();
//2. 构建你的请求
Request request = new Request.Builder()
//设置请求方法是一个 GET 请求
.get()
//设置请求的 URL 地址
.url("http://www.baidu.com")
.build();
//3. 获取一个请求调用对象
Call call = okHttpClient.newCall(request);
enqueue是一个异步请求 遇到方法先执行下去 有响应了再返回
call.enqueue(new Callback() {
/**
* 请求调用失败的回调
* @param call
* @param e
*/
@Override
public void onFailure(Call call, IOException e) {
}
/**
* 请求调用成功的回调
* @param call
* @param response
* @throws IOException
*/
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println("response.body().string() = " + response.body().string());
}
});
}
}
一、静态工厂注入:
1.添加OkHttpClienFactory的依赖:
dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>3.12.10</version> </dependency>
写一个OkHttpClienFactory 将其单独拿出来封装 以后可以方便调用:
import okhttp3.OkHttpClient;
import java.util.concurrent.TimeUnit;
public class OkHttpClientFactory {
connectTimeout(5, TimeUnit.SECONDS) 连接时间给5s 5s內没接上则是超时
readTimeout(5, TimeUnit.SECONDS)读取请求时间给5s 5s內没接上则是超时
private static OkHttpClient okHttpClient = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(5, TimeUnit.SECONDS).build();
public static OkHttpClient getInstance() {
return okHttpClient;
}
}
Xml配置:将上面的方法注入到Bean中
<!--静态工厂注入-->
<!--注意,factory-method 属性所指定的方法的返回值,最终会被注入到 SPring 容器中-->
<!--根据id="okHttpClient2"拿到的是"getInstance"方法的返回值-->
<bean class="com.qfedu.demo.factory.OkHttpClientFactory" factory-method="getInstance" id="okHttpClient"/>
Test 测试类:这是通过向Bean取要一个 okHttpClient对象来调取方法
public class Demo02 {
private ClassPathXmlApplicationContext ctx;
@Test
public void test02() {
OkHttpClient okHttpClient = ctx.getBean("okHttpClient", OkHttpClient.class);
//2. 构建你的请求
Request request = new Request.Builder()
//设置请求方法是一个 GET 请求
.get()
//设置请求的 URL 地址
.url("http://www.baidu.com")
.build();
//3. 获取一个请求调用对象
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
/**
* 请求调用失败的回调
* @param call
* @param e
*/
@Override
public void onFailure(Call call, IOException e) {
}
/**
* 请求调用成功的回调
* @param call
* @param response
* @throws IOException
*/
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println("response.body().string() = " + response.body().string());
}
});
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
实例工厂注入:
先写实例方法:
public class OkHttpClientFactory2 {
private OkHttpClient okHttpClient = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(5, TimeUnit.SECONDS).build();
public OkHttpClient getInstance() {
return okHttpClient;
}
}
xml注入Bean中:
实例工厂注入,注意,这里需要先创建一个工厂的实例 先创建id="clientFactory2"这个对象 再通过对象调用getInstance方法
<bean class="com.qfedu.demo.factory.OkHttpClientFactory2" id="clientFactory2"/>
<!-- okHttpClient2提供给测试的一个id-->
<bean class="okhttp3.OkHttpClient" factory-bean="clientFactory2" factory-method="getInstance" id="okHttpClient2"/>
Teat:通过向Bean取要一个 okHttpClient2对象来调取方法 代码和上面的一样。
FactoryBean注入:
第一步:引入官方推荐的FactoryBean框架:
package com.demo.factory;
import com.demo.dao.UserDao;
import org.springframework.beans.factory.FactoryBean;
/**
* 这个是官方推荐的工厂 Bean
* 在 Spring 容器中,很多需要工厂方法提供的 Bean,基本上都是通过实现 FactoryBean 来完成的
*/
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
/**
* 工厂返回的 Bean 是否是单例的
* false 表示返回的 Bean 不是单例的,意味着每次从 Spring 容器中获取到的 Bean 都是一个全新的 Bean
* @return
*/
@Override
public boolean isSingleton() {
return true;
}
/**
* 具体返回 Bean 的方法
* @return
* @throws Exception
*/
@Override
public UserDao getObject() throws Exception {
return UserDao.getInstance();
}
/**
* 返回的 Bean 类型
* @return
*/
@Override
public Class<?> getObjectType() {
return UserDao.class;
}
}
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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
向 Spring 容器中去注册 UserDao
注意,这里最终注册到 Spring 容器中的 Bean 有两个:
1. UserDao
2. UserDaoFactoryBean
-->
<bean class="com.qfedu.demo.factory.UserDaoFactoryBean" id="userDao"/>
</beans>
Test:
public class Demo01 {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//拿到UserDao的Bean对象
UserDao ud = ctx.getBean("userDao", UserDao.class);
UserDao ud2 = ctx.getBean("userDao", UserDao.class);
System.out.println("ud = " + ud);
System.out.println("ud==ud2 = " + (ud == ud2));
//在 bean 的 id 之前,加上一个 & 符号,就表示获取到工厂 Bean 的实例
UserDaoFactoryBean factoryBean = (UserDaoFactoryBean) ctx.getBean("&userDao");
System.out.println("factoryBean = " + factoryBean);
Bean对象调用的生命周期问题:
这里我们写一个UserDao,里边有构造方法和初始化方法以及销毁方法:
public class User {
private String username;
public User() {
System.out.println("构造方法执行");
}
//方法名随意取 只要和xml里面的对应起来就可以了
public void init() {
System.out.println("bean 的初始化方法");
}
public void destroy() {
System.out.println("bean 的销毁方法");
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
xml中进行配置 :将User注入Spring容器中
<!--
默认情况下,这个 Bean 是单例的,多次从 Spring 容器中获取,拿到的是同一个 Bean
通过 scope 属性可以修改这个行为:
singleton:表示这个 Bean 是单例的
prototype:表示这个 Bean 是多例的,每一次获取都拿到一个新的 Bean
request:在 Web 环境下,每一次请求中,多次拿到这个 Bean,都是同一个
session:在 Web 环境下,在同一个会话中,多次拿到这个 Bean,都是同一个
application:在 Web 环境下,在一个应用实例中,多次拿到这个 Bean,都是同一个
lazy-init="true" 表示当跟 Spring 容器要这个 Bean 的时候,该 Bean 才会初始化
scope="prototype" 添加了这个后 销毁的方法将不会被执行
-->
<bean class="com.qfedu.demo.model.User" id="user" init-method="init" destroy-method="destroy" lazy-init="true" scope="prototype"/>
Test:
public class Demo01 {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
User u1 = ctx.getBean(User.class);
User u2 = ctx.getBean(User.class);
//测试拿到的两个User对象是否是同一个
//同一个User初始化一次就行
System.out.println("u1==u2 = " + (u1 == u2));
//销毁容器
ctx.close();
}
}
运行结果: