首先:先上思维导图
一、IOC 操作 Bean 管理
- 创建一个Maven项目
- 导入依赖
- 代码编写
1、创建一个Maven项目
2、导入依赖
- 需要以下的jar包:
- aop
- beans
- context
- core
- expression
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
3、代码编写
1.1、基于XML文件的方式
1.1.1、创建对象
实现用Spring来帮我们创建对象
-
创建普通类
package com.hgw.spring5.entity; public class User { public void add() { System.out.println("add..."); } }
-
创建Spring配置文件,在配置文件配置创建的对象
<?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"> <!--配置User对象创建--> <bean id="user" class="com.hgw.spring5.entity.User"></bean> </beans>
-
加载配置文件,获取配置创建的对象
@Test public void test1() { // 1、加载 Spring 的配置文件 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); // 2、获取配置创建的对象 User user = context.getBean("user", User.class); System.out.println(user); user.add(); }
1.1.2、注入属性方式
- DI:DI是IOC中的一种具体实现,表示 依赖注入,就是注入属性。
第一种注入方式:使用 set 方法进行注入
1、创建类,定义属性和对应的set方法
package com.hgw.spring5.entity;
public class Book {
// 1、创建属性
private String bname;
private String bauther;
private String address;
// 2、创建属性对应的set方法
public void setBname(String bname) {
this.bname = bname;
}
public void setBauther(String bauther) {
this.bauther = bauther;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Book{" +
"bname='" + bname + '\'' +
", bauther='" + bauther + '\'' +
", address='" + address + '\'' +
'}';
}
}
2、在 spring 配置文件配置对象创建,配置属性注入
- 普通:
<property name="bname" value="剑指Offer"></property>
- null值:
<property name="address"> <null/> </property>
- 特殊符号:
<![CDATA[原样输入]]>
<?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="book" class="com.hgw.spring5.entity.Book">
<!--
使用 property 完成属性注入
name:类里面属性名称
value:向属性注入的值
-->
<property name="bname" value="剑指Offer"></property>
<property name="bauther" value="何海淘"></property>
<!--null值-->
<!-- <property name="address">
<null/>
</property>-->
<!--特殊符号-->
<property name="address">
<value><![CDATA[<<浙江省杭州市>>]]></value>
</property>
</bean>
</beans>
3、加载配置文件,获取配置创建的对象
@Test
public void test2() {
// 1、加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
// 2、获取配置创建的对象
Book book = context.getBean("book", Book.class);
System.out.println(book);
}
输出:Book{bname='剑指Offer', bauther='何海淘'}
第二种注入方式:使用有参数构造器进行注入
1、创建类,定义属性,创建属性对应的有参数构造方法
package com.hgw.spring5.entity;
/**
* Data time:2022/4/24 21:04
* StudentID:2019112118
* Author:hgw
* Description: 使用有参数构造注入
*/
public class Orders {
// 1、创建属性
private String orderId;
private String address;
// 2、创建有参数构造
public Orders(String orderId, String address) {
this.orderId = orderId;
this.address = address;
}
}
2、在 spring 配置文件配置对象创建,配置属性注入
<?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="orders" class="com.hgw.spring5.entity.Orders">
<constructor-arg name="orderId" value="20001901"></constructor-arg>
<!--<constructor-arg name="address" value="浙江省杭州市市民中心"></constructor-arg>-->
<constructor-arg index="1" value="浙江省杭州市市民中心"></constructor-arg>
</bean>
</beans>
3、加载配置文件,获取配置创建的对象
@Test
public void test3() {
// 1、加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
// 2、获取配置创建的对象
Orders orders = context.getBean("orders", Orders.class);
System.out.println(orders);
}
Orders{orderId='20001901', address='浙江省杭州市市民中心'}
第三种注入方式:p名称空间注入
使用 p 名称空间注入,可以简化基于 xml 配置文件
1、添加 p名称空间在配置文件中
2、进行属性注入,在 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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="book" class="com.hgw.spring5.entity.Book" p:bname="阿甘正传" p:bauther="鲁迅"></bean>
</beans>
1.1.3、注入属性-特殊
1.1.3.1、注入属性外部Bean
- 创建两个类 service类 和 dao类
- 在 service 调用 dao 里面的方法
- 在 spring 配置文件中进行配置
1、创建两个类 service类 和 dao类
package com.hgw.spring5.service;
import com.hgw.spring5.dao.UserDao;
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void update() {
System.out.println("service update......");
userDao.update();
}
}
package com.hgw.spring5.dao;
public interface UserDao {
void update();
}
package com.hgw.spring5.dao.impl;
import com.hgw.spring5.dao.UserDao;
public class UserDaoImpl implements UserDao {
@Override
public void update() {
System.out.println("dao update ........");
}
}
2、在 spring 配置文件中进行配置
<?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="userService" class="com.hgw.spring5.service.UserService">
<!--注入外部bean对象
name :类里面属性名称
ref :外部 bean 标签的id
-->
<property name="userDao" ref="userDaoImpl"></property>
</bean>
<bean id="userDaoImpl" class="com.hgw.spring5.dao.impl.UserDaoImpl"></bean>
</beans>
3、加载配置文件,获取配置创建的对象
@Test
public void test4() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.update();
}
测试结果:
service update......
dao update ........
1.1.3.2、注入属性内部Bean
- 一对多关系:部门和员工
一个部门有多个员工,一个员工属于一个部门,一对多关系,部门为一,员工是多 - 在实体类之间表示一对多关系,员工表示所属部门,使用对象类型属性进行表示
1、创建两个类 Dept类 和 Emp类
package com.hgw.spring5.bean;
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}
public String getDname() {
return dname;
}
}
package com.hgw.spring5.bean;
public class Emp {
private String ename;
private String gender;
// 员工属于某一个部分,使用对象形式表示
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
public void setEname(String ename) {
this.ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}
public void test() {
System.out.println("名称:"+ename+";性别:"+gender+";部门:"+dept.getDname());
}
}
2、在 spring 配置文件中进行配置
<?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-->
<bean name="emp" class="com.hgw.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--设置对象类型属性-->
<property name="dept">
<bean id="dept" class="com.hgw.spring5.bean.Dept">
<property name="dname" value="安保部"></property>
</bean>
</property>
</bean>
</beans>
3、加载配置文件,获取配置创建的对象
@Test
public void test6() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml");
Emp emp = context.getBean("emp", Emp.class);
emp.test();
}
测试结果:
名称:lucy;性别:女;部门:安保部
1.1.3.3、注入属性-级联复制
1、第一种写法
<!--级联赋值-->
<bean name="emp" class="com.hgw.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--设置对象类型属性-->
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.hgw.spring5.bean.Dept">
<property name="dname" value="运维部"></property>
</bean>
2、第二种写法
-
生成内部类的 get 方法
-
编写配置文件
<!--级联赋值--> <bean name="emp" class="com.hgw.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="lucy"></property> <property name="gender" value="女"></property> <!--设置对象类型属性--> <property name="dept" ref="dept"></property> <property name="dept.dname" value="研发部"></property> </bean> <bean id="dept" class="com.hgw.spring5.bean.Dept"> <property name="dname" value="运维部"></property> </bean>
-
加载配置文件,获取配置创建的对象
@Test public void test8() { ApplicationContext context = new ClassPathXmlApplicationContext("bean8.xml"); Emp emp = context.getBean("emp", Emp.class); emp.test(); }
测试结果:
名称:lucy;性别:女;部门:研发部
1.1.4、注入属性-集合
1、注入属性集合演示
-
注入数组类型属性
<array> <value>值1</value> ...... <value>值n</value> </array>
-
注入 List 集合类型属性
<list> <value>值1</value> ...... <value>值n</value> </list>
-
注入 Set 集合类型属性
<set> <value>值1</value> ...... <value>值n</value> </set>
-
注入 Map 集合类型属性
<map> <entry key="键" value="值"> <entry key="键" value="值"> </map>
-
集合类型里面设置对象类型值
<ref bean="bean唯一id"></ref>
1、创建类,定义数组、list、map、set类型属性,生成对应 set 方法
package com.hgw.spring5.entity;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Data time:2022/4/25 19:05
* StudentID:2019112118
* Author:hgw
* Description:
*/
public class Stu {
// 1、数组类型属性
private String[] courses;
// 2、list 集合类型属性
private List<String> list;
// 3、set 集合类型属性
private Set<String> set;
// 4、map 集合类型属性
private Map<String,String> maps;
// 5、list 集合类型里面设置对象类型值
private List<Course> courseList;
// 所有属性的get/set方法
}
package com.hgw.spring5.entity;
public class Course {
private String cname;
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
}
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="stu" class="com.hgw.spring5.entity.Stu">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>Java 课程</value>
<value>数据库 课程</value>
</array>
</property>
<!--List 类型属性注入-->
<property name="list">
<list>
<value>张三</value>
<value>小三</value>
</list>
</property>
<!--set 类型属性注入-->
<property name="set">
<set>
<value>Java</value>
<value>MySQL</value>
</set>
</property>
<!--map 类型属性注入-->
<property name="maps">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="MSYQL" value="java"></entry>
</map>
</property>
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
</bean>
<bean id="course1" class="com.hgw.spring5.entity.Course">
<property name="cname" value="高等数学"></property>
</bean>
<bean id="course2" class="com.hgw.spring5.entity.Course">
<property name="cname" value="线性代数"></property>
</bean>
</beans>
3、加载配置文件,获取配置创建的对象
/**
* xml注入属性
*/
@Test
public void test9() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean9.xml");
Stu stu = context.getBean("stu", Stu.class);
System.out.println(Arrays.toString(stu.getCourses()));
System.out.println(stu.getList());
System.out.println(stu.getSet());
System.out.println(stu.getMaps());
stu.getCourseList().forEach(System.out::println);
}
测试结果:
[Java 课程, 数据库 课程]
[张三, 小三]
[Java, MySQL]
{JAVA=java, MSYQL=java}
Course{cname='高等数学'}
Course{cname='线性代数'}
2、把集合注入部分提取出来
1、在spring配置文件中引入名称空间 util
<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
</beans>
2、使用 util 标签完成list集合注入提取
<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!--1、提取list集合类型属性注入-->
<util:list id="bookList">
<value>操作系统</value>
<value>组成原理</value>
<value>计算机网络</value>
<value>数据结构与算法</value>
</util:list>
<!--2、提取list集合类型属性注入使用-->
<bean id="books" class="com.hgw.spring5.entity.Books">
<property name="bookNames" ref="bookList"></property>
</bean>
</beans>
1.1.5、FactoryBean
Bean
把Bean理解为类的代理或代言人(实际上确实是通过反射、代理来实现的),这样它就能代表类拥有该拥有的东西了
- 普通Bean:在配置文件中定义 bean 类型就是返回类型
- 工厂Bean(FactoryBean):在配置文件中定义 bean类型可以和返回类型不一样
第一步、创建类,让这个类作为工厂bean,实现接口 FactoryBean
第一步、实现接口里面的方法,在实现的方法中定义返回 bean 类型
- 创建类,实现接口 FactoryBean,实现接口里面的方法,在实现的方法中定义返回 bean 类型
package com.hgw.spring5.factoryBeanDemo;
public class MyBean implements FactoryBean<Course> {
// 定义返回 bean
@Override
public Course getObject() throws Exception {
Class<Course> course = (Class<Course>) Class.forName("com.hgw.spring5.entity.Course");
Course course1 = course.newInstance();
course1.setCname("离散数学");
return course1;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return false;
}
}
- 编写配置文件
<?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="myBean" class="com.hgw.spring5.factoryBeanDemo.MyBean"></bean>
</beans>
- 进行测试
@Test
public void test11() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean11.xml");
Course myBean = context.getBean("myBean", Course.class);
System.out.println(myBean);
}
测试结果:
Course{cname='离散数学'}
1.1.6、xml 自动装配
之前用的都是手动装配,根据指定装配规则,Spring自动将匹配的属性值进行注入:
- bean 标签属性 autowire,配置自动装配
- autowire 属性常用两个值:
- byName 根据属性名称自动注入,注入值 bean的id 和 类的属性名称一样
- byType 根据属性类型注入
- autowire 属性常用两个值:
演示:
<?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="emp" class="com.hgw.spring5.autowire.Emp" autowire="byName"></bean>
<bean id="dept" class="com.hgw.spring5.autowire.Dept"></bean>
</beans>
<?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="emp" class="com.hgw.spring5.autowire.Emp" autowire="byType"></bean>
<bean id="dept" class="com.hgw.spring5.autowire.Dept"></bean>
</beans>
1.1.7、注入 外部属性文件
举一个案例:通过配置连接池完成数据库配置
第一种、直接配置数据库信息
<?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="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/userDb"></property>
<property name="username" value="root"></property>
<property name="password" value="hgw6721224"></property>
</bean>
</beans>
第二种、引入外部属性文件配置数据库连接池
- 引入context名称空间
- 在spring配置文件使用
1、创建外部属性文件,properties 格式文件,写数据库信息
prop.driverClassName=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/userDb
prop.username=root
prop.password=hgw6721224
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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--1、引入外部属性文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--2、配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClassName}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.username}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
</beans>
1.2、基于注解方式
1、什么是注解
- 注解是代码特殊标记,格式:
@注解名称(属性名称=属性值,属性名称=属性值.....)
- 使用注解,注解作用可以在类上面、方法上面、属性上面
- 使用注解目的:简化 xml 配置
2、创建对象的注解
- @Component 普通组件
- @Service 业务逻辑层,Service层
- @Controller 控制层,Controller层
- @Repository 持久层,dao层
注解里面的value属性值可以省略不写,value相当于<bean id=""
。默认值是类名称,首字母小写
1.2.1、创建对象
创建对象的注解
- @Component 普通组件
- @Service 业务逻辑层,Service层
- @Controller 控制层,Controller层
- @Repository 持久层,dao层
注解里面的value属性值可以省略不写,value相当于<bean id=""
。默认值是类名称,首字母小写
开启组件扫描的细节:
-
扫描多个包,多个包使用逗号隔开,或者直接扫描包上层的目录
<context:component-scan base-package="扫描的包路径"></context:component-scan>
-
设置某个包下面哪些内容进行扫描
use-default-filters="false"
: 表示现在不使用默认 filter,自己配置 filtercontext:include-filter
: 设置扫描哪些内容
示例:设置只扫描com.hgw包下面的 Service注解
<context:component-scan base-package="com.hgw" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Service"/>
</context:component-scan>
- 设置某个包下面哪些内容不进行扫描
context:exclude-filter
:设置哪些内容不进行扫描
<context:component-scan base-package="com.hgw">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Service"/>
</context:component-scan>
第一步、引入依赖
第二步、开启组件扫描
- 配置context名称空间
- 通过 context:component-scan 开启组件扫描,并制定包下内容 base-package
<?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">
<!--开启组件扫描
component-scan:组件扫描
base-package:指定哪个包中内容
1、如果扫描多个包,多个包使用逗号隔开
2、或者扫描包上层目录
-->
<context:component-scan base-package="com.hgw.spring5"></context:component-scan>
</beans>
第三步、创建类,在类上面添加创建对象注解
package com.hgw.spring5.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void add() {
System.out.println("Service add.....");
}
}
读取配置文件,创建bean对象,进行测试:
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
1.2.2、属性注入
- @Autowired 根据属性类型进行自动装配
- @Qualifier 根据属性名称进行自动注入
- @Resource
- 可以根据属性类型进行自动装配
- 也可以根据属性名称进行自动注入
- @Value(value = “属性值”) 注入普通类型属性
测试案例:
1、把 service 和 doa 对象创建,在service 和 dao 类添加创建对象注解
2、把 service 注入 doa 对象创建,在service 类添加 dao 类型属性,在属性上面使用注解
package com.hgw.spring5.dao;
import org.springframework.stereotype.Service;
@Service(value = "userDaoImpl")
public class UserDao {
public void add(){
System.out.println("dao add....");
}
}
package com.hgw.spring5.service;
@Service
public class UserService {
@Value(value = "hgw")
private String name;
@Autowired // 根据类型进行注解
@Qualifier(value = "userDaoImpl") // 根据名称进行注入
private UserDao userDao;
public void add() {
System.out.println(name+" Service add.....");
userDao.add();
}
}
3、配置文件,开启组件扫描
<?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:component-scan base-package="com.hgw.spring5"></context:component-scan>
</beans>
4、读取配置文件,进行测试
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
测试结果:
hgw Service add.....
dao add....
1.3、完全注解开发
1、创建配置类,替代 xml 配置文件
- @Configuration 作为配置类
- @ComponentScan(basePackages = {“com.hgw”}) 开启组件扫描,指定扫描包路径
package com.hgw.spring5.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @Configuration 作为配置类
* @ComponentScan(basePackages = {"com.hgw"}) 开启组件扫描,指定扫描包路径
*/
@Configuration
@ComponentScan(basePackages = {"com.hgw"})
public class SpringConfig {
}
2、测试,读取配置文件创建Bean
/**
* 基于全注解开发 使用注解方式实现IOC
*/
@Test
public void test02() {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
二、AOP
AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,
通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D6F8rlhu-1651053695237)(Spring.assets/image-20220426130817525.png)]
2.1、AOP 底层原理
AOP底层使用动态代理,有两种情况动态代理
- 第一种,有接口情况,使用 JDK 动态代理
- 创建接口实现类代理对象,增强类的方法
- 第二种,没有接口情况,使用 CGLIB 动态代理
- 创建子类的代理对象,增强类的方法
编写一个 JDK 动态代理:
使用 java.lang.reflect.Proxy 类里面的方法创建代理对象
1、创建接口,定义方法
package com.hgw.spring5.dao;
public interface UserDao {
int add(int a, int b);
String update(String id);
}
2、创建接口实现类,实现方法
package com.hgw.spring5.dao.impl;
import com.hgw.spring5.dao.UserDao;
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {
System.out.println("执行了 UserDaoImpl 的 add方法");
return a+b;
}
@Override
public String update(String id) {
System.out.println("执行了 UserDaoImpl 的 update方法");
return id+" Sucess!";
}
}
3、创建代理对象代码
package com.hgw.spring5.config;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
public class UserDaoProxy implements InvocationHandler {
// 1、把创建的是谁的代理对象,把谁传递过来
// 通过有参数构造器传递
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
/**
* 增强的逻辑
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法之前
System.out.println("方法之前执行..." + method.getName() +":传递的参数..." + Arrays.toString(args));
// 被增强的方法执行
Object res = method.invoke(obj, args);
// 方法追后
System.out.println("方法之后执行..." + obj);
return res;
}
}
4、使用 Proxy 类创建接口代理对象
package com.hgw.spring5.config;
import com.hgw.spring5.dao.UserDao;
import com.hgw.spring5.dao.impl.UserDaoImpl;
import java.lang.reflect.Proxy;
public class JDKProxy {
public static void main(String[] args) {
UserDaoImpl userDao = new UserDaoImpl();
UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), new Class[]{UserDao.class}, new UserDaoProxy(userDao));
int result = dao.add(1,2);
System.out.println("result:" + result);
}
}
2.2、AOP 操作
Spring 一般都是基于 AspectJ 实现 AOP 操作。
-
什么是 AspectJ
- AspectJ 不是 Spring 组成部分,独立于 AOP框架,一般叭 AspectJ 和 Spring 框架一起使用,进行 AOP 操作。
-
操作步骤:
-
引入AOP相关依赖
<!--Aop--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
-
引入切入点表达式(功能:知道对哪个类里面的哪个方法进行增强)
-
格式:
execution(
[权限修饰符]
[返回类型]
[类全路径]
[方法名称]([参数列表])
)
-
举例1:对 com.hgw.dao.BookDao 类里面的 add 方法进行增强
execution(* com.hgw.dao.BookDao.add(...))
-
举例2:对 com.hgw.dao.BookDao 类里面的所有方法进行增强
execution(* com.hgw.dao.BookDao.*(...))
-
举例2:对 com.hgw.dao 包里面的所有类,类里面的所有方法进行增强
execution(* com.hgw.dao.*.*(...))
-
-
2.2.1、基于 注解 方式
第一步、创建被增强类,在类里面定义方法
package com.hgw.spring5.aopanno;
/**
* Description: 被增强类
*/
public class User {
public void add() {
System.out.println("add.....");
}
}
第二步、创建增强类(编写增强逻辑)
package com.hgw.spring5.aopanno;
/**
* Description: 增强类
*/
public class UserProxy {
// 前置通知
public void before() {
System.out.println("before....");
}
}
第三步、进行通知的配置
-
在 Spring 配置文件中,开启注解扫描
<context:component-scan base-package="扫描包路径" />
-
使用注解创建 被增强类 和 增强类
-
在 增强类上面添加注解
@Aspect
-
在 Spring 配置文件中,开启生成代理对象
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
1、在 Spring 配置文件中,开启注解扫描
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.hgw.spring5.aopanno" />
</beans>
2、使用注解创建 被增强类 和 增强类
@Component
public class User {
public void add() {
System.out.println("add.....");
}
}
@Component
public class UserProxy {
// 前置通知
public void before() {
System.out.println("before....");
}
}
3、在 增强类上面添加注解 @Aspect
package com.hgw.spring5.aopanno;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* Description: 被增强类
*/
@Aspect
@Component // 生成代理对象
public class User {
public void add() {
System.out.println("add.....");
}
}
4、在配置文件中,开启Aspect生成代理对象
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启注解扫描-->
<context:component-scan base-package="com.hgw.spring5.aopanno" />
<!--开启Aspect生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
第四步、配置不同类型的通知
在增强类的里面,作为通知方法上面添加通知类型注解,使用切入点表达式配置
package com.hgw.spring5.aopanno;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* Data time:2022/4/26 11:52
* StudentID:2019112118
* Author:hgw
* Description: 增强类
*/
@Aspect //生成代理对象
@Component
public class UserProxy {
// 相同切入点抽取
@Pointcut(value = "execution(* com.hgw.spring5.aopanno.User.add(..))")
public void pointdemo() {
}
// 前置通知,目标方法被调用之前调用通知功能
@Before(value = "pointdemo()")
public void before() {
System.out.println("before....");
}
// 后置通知,目标方法完成之后调用
@AfterReturning(value = "pointdemo()")
public void afterReturning() {
System.out.println("afterReturning....");
}
// 返回通知(最终通知),目标方法成功执行之后调用通知
@After(value = "pointdemo()")
public void after() {
System.out.println("after....");
}
// 异常通知,目标方法抛出异常之后调用通知
@AfterThrowing(value = "pointdemo()")
public void afterThrowing() {
System.out.println("afterThrowing....");
}
// 环绕通知,在被通知的方法调用之前和调用之后执行自定义的行为
@Around(value = "pointdemo()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前....");
// 被增强的方法执行
proceedingJoinPoint.proceed();
System.out.println("环绕之后....");
}
}
测试:
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
User user = context.getBean("user", User.class);
user.add();
}
测试结果:
环绕之前....
before....
add.....
环绕之后....
after....
afterReturning....
2.2.2、基于 Xml 配置文件方式
1、创建被增强类,在类里面定义方法
package com.hgw.spring5.aopxml;
/**
* Description: 被增强类
*/
public class Book {
public void buy() {
System.out.println("被增强类 buy....");
}
}
2、创建增强类(编写增强逻辑)
package com.hgw.spring5.aopxml;
/**
* Description: 增强类
*/
public class BookProxy {
public void before() {
System.out.println("增强类 before.....");
}
}
3、在 Spring配置文件中创建两个类对象
<bean id="book" class="com.hgw.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.hgw.spring5.aopxml.BookProxy"></bean>
4、在 Spring配置文件中配置切入点
测试:
@Test
public void test03() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Book book = context.getBean("book", Book.class);
book.buy();
}
测试结果:
增强类 before.....
被增强类 buy....
2.2.3、完全使用注解开发
创建配置类,不需要创建 xml 配置文件
@Configuration
作为配置类@ComponentScan(basePackages = {"包路径"})
扫描注解@EnableAspectJAutoProxy(proxyTargetClass = true)
开启组件扫描
package com.hgw.spring5.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = {"com.hgw"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}
三、JdbcTemplate
JdbcTemplate 就是 Spring框架对 JDBC 进行封装。
第一步、导入相关依赖
<!--spring-jdbc依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<!--druid数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>0.2.15</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
第二步、在 Spring 配置文件配置数据库连接池
- Jdbc.properties文件
prop.driverClassName=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/userDb
prop.username=root
prop.password=hgw6721224
- Bean.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"
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">
<!--1、引入外部属性文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--2、配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClassName}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.username}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
</beans>
第三步、配置 JdbcTemplate,注入DataSource
<!--JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入 dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
第四步、开启组件扫描
<!--开启组件扫描-->
<context:component-scan base-package="com.hgw"></context:component-scan>
第五步、创建 Service 类,创建 dao 类,在 dao 注入 JdbcTemplate 对象
1、Dao 层
package com.atguigu.spring5.dao;
import com.atguigu.spring5.entity.Book;
import java.util.List;
public interface BookDao {
//添加的方法
void add(Book book);
//修改的方法
void updateBook(Book book);
//删除的方法
void deleteBook(String id);
//查询表记录数
int selectCount();
//查询返回对象
Book selectBookInfo(String id);
//查询返回集合
List<Book> findAllBook();
//查询满足条件返回集合
List<Book> findAllBookForUst(String ustatus);
//批量添加
void batchAddBook(List<Object[]> batchArgs);
//批量修改
void batchUpdateBook(List<Object[]> batchArgs);
//批量删除
void batchDeleteBook(List<Object[]> batchArgs);
}
package com.atguigu.spring5.dao;
import com.atguigu.spring5.entity.Book;
import com.sun.org.apache.xpath.internal.operations.Bool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.Arrays;
import java.util.List;
@Repository
public class BookDaoImpl implements BookDao{
//注入jdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void add(Book book) {
// 1 创建sql语句
String sql = "insert into t_book values(?,?,?)";
// 2 调用方法实现
Object[] args = {book.getBookId(), book.getBookname(), book.getUststus()};
int update = jdbcTemplate.update(sql, args);
System.out.println(update);
}
@Override
public void updateBook(Book book) {
// 1 创建sql语句
String sql = "update t_book set bookname = ?,ustatus = ? where book_id = ?";
// 2 调用方法实现
Object[] args = {book.getBookname(), book.getUststus(),book.getBookId()};
int update = jdbcTemplate.update(sql, args);
System.out.println(update);
}
@Override
public void deleteBook(String id) {
// 1 创建sql语句
String sql = "delete from t_book where book_id = ?";
// 2 调用方法实现
int update = jdbcTemplate.update(sql, id);
System.out.println(update);
}
@Override
public int selectCount() {
// 1 创建sql语句
String sql = "select count(*) '记录总数' from t_book";
// 2 调用方法实现
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
@Override
public Book selectBookInfo(String id) {
// 1 创建sql语句
String sql = "select book_id, bookname, ustatus from t_book where book_id = ?";
// 2 调用方法实现
Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class),id);
return book;
}
@Override
public List<Book> findAllBook() {
// 1 创建sql语句
String sql = "select book_id, bookname, ustatus from t_book";
// 2 调用方法实现
List<Book> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
return query;
}
@Override
public List<Book> findAllBookForUst(String ustatus) {
// 1 创建sql语句
String sql = "select book_id, bookname, ustatus from t_book where ustatus = ?";
// 2 调用方法实现
List<Book> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class), ustatus);
return query;
}
@Override
public void batchAddBook(List<Object[]> batchArgs) {
// 1 创建sql语句
String sql = "insert into t_book values(?,?,?)";
// 2 调用方法实现
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
@Override
public void batchUpdateBook(List<Object[]> batchArgs) {
// 1 创建sql语句
String sql = "update t_book set bookname = ?,ustatus = ? where book_id = ?";
// 2 调用方法实现
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
@Override
public void batchDeleteBook(List<Object[]> batchArgs) {
// 1 创建sql语句
String sql = "delete from t_book where book_id = ?";
// 2 调用方法实现
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(ints);
}
}
2、Service层调用 BookDao
package com.atguigu.spring5.service;
import com.atguigu.spring5.dao.BookDao;
import com.atguigu.spring5.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookService {
//注入dao
@Autowired
private BookDao bookDao;
//添加的方法
public void addBook(Book book){
bookDao.add(book);
}
//修改的方法
public void updateBook(Book book){
bookDao.updateBook(book);
}
//删除的方法
public void deleteBook(String id){
bookDao.deleteBook(id);
}
//查询表中有多少条记录
public int findCount() { return bookDao.selectCount();};
//查询返回对象
public Book findOne(String id) {
return bookDao.selectBookInfo(id);
}
//查询返回对象
public List<Book> findAll() {
return bookDao.findAllBook();
}
//查询满足条件返回对象
public List<Book> findAllForUst(String ustatus) {
return bookDao.findAllBookForUst(ustatus);
}
//批量添加对象
public void batchAdd(List<Object[]> batchArgs) {
bookDao.batchAddBook(batchArgs);
}
//批量修改对象
public void batchUpdate(List<Object[]> batchArgs) {
bookDao.batchUpdateBook(batchArgs);
}
//批量修改对象
public void batchDelete(List<Object[]> batchArgs) {
bookDao.batchDeleteBook(batchArgs);
}
}
四、事务管理
五、Webflux(响应式编程)
5.1、SpringWebflux 介绍
5.1、SpringWebflux概述
SpirngWebflux 是 响应式web框架,是完全异步且非阻塞的,核心是基于 Reactor 相关API实现的
(1)Spring WebFlux 是 Spring Framework 5.0中引入的新的响应式web框架。与Spring MVC不同,它不需要Servlet API,是完全异步且非阻塞的,并且通过Reactor项目实现了Reactive Streams规范。
(2)Spring WebFlux 用于创建基于事件循环执行模型的完全异步且非阻塞的应用程序。
(PS:所谓异步非阻塞是针对服务端而言的,是说服务端可以充分利用CPU资源去做更多事情,这与客户端无关,客户端该怎么请求还是怎么请求。)
- 解释什么是 异步非阻塞
- 异步和同步 针对调用者,
- 调用方发送请求之后,如果等着对方回应之后才去做其他事情就是 同步
- 调用方发送请求之后,如果不等着对方回应之后才去做其他事情就是 异步
- 阻塞和非阻塞针对被调用者,
- 被调用者收到请求之后,昨晚请求任务之后才给出反馈就是 阻塞
- 收到请求之后马上给出反馈然后再去做事情就是 非阻塞
- 异步和同步 针对调用者,
Reactive Streams是一套用于构建高吞吐量、低延迟应用的规范。而Reactor项目是基于这套规范的实现,它是一个完全非阻塞的基础,且支持背压。Spring WebFlux基于Reactor实现了完全异步非阻塞的一套web框架,是一套响应式堆栈。
-
【spring-webmvc + Servlet + Tomcat】命令式的、同步阻塞的
-
【spring-webflux + Reactor + Netty】响应式的、异步非阻塞的
5.2、Webflux 特点
Webflux 特点:
- 非阻塞式:在有限的资源下,提高系统吞吐量和伸缩性,以 Reactor 为基础实现响应式编程
- 函数式编程:Spring5 框架基于 java8,Webflux 使用 Java8 函数式编程方式实现路由请求
5.3、比较 SpringMVC
它们都可以用注解式编程模型,都可以运行在tomcat,jetty,undertow等servlet容器当中。但是SpringMVC采用==命令式编程==方式,代码一句一句的执行,这样更有利于理解与调试,而WebFlux则是基于异步**响应式编程**,对于初次接触的码农们来说会不习惯。对于这两种框架官方给出的建议是:
1)如果原先使用用SpringMVC好好的话,则没必要迁移。因为命令式编程是编写、理解和调试代码的最简单方法。因为老项目的类库与代码都是基于阻塞式的。
2)如果你的团队打算使用非阻塞式web框架,WebFlux确实是一个可考虑的技术路线,而且它支持类似于SpringMvc的Annotation的方式实现编程模式,也可以在微服务架构中让WebMvc与WebFlux共用Controller,切换使用的成本相当小
3)在SpringMVC项目里如果需要调用远程服务的话,你不妨考虑一下使用WebClient,而且方法的返回值可以考虑使用Reactive Type类型的,当每个调用的延迟时间越长,或者调用之间的相互依赖程度越高,其好处就越大。比如在网关的时候,适用异步响应式。
我个人意见是:官网明确指出,SpringWebFlux并不是让你的程序运行的更快(相对于SpringMVC来说),而是在有限的资源下提高系统的伸缩性,因此当你对响应式编程非常熟练的情况下并将其应用于新的系统中,还是值得考虑的,否则还是老老实实的使用WebMVC吧
5.2、响应式编程(Java实现)
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
- 例如,电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1"的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。
Java8 j及其之前版本
提供的 观察者模式 两个类 Observer 和 Observable
- 观察者模式:定义对象之间的一种一对多的依赖关系,使得每当一个对象状态发送改变时其相关依赖对象皆得到通知并自动更新。
- Subject(目标)
- ConcreteSubject(具体目标)
- Observer(观察者)
- ConcreteObserver(具体观察者)
示例:
package com.hgw.spring5.reactor8;
import java.util.Observable;
/**
* Description: 演示观察者模式
*/
public class ObserverDemo extends Observable {
public static void main(String[] args) {
ObserverDemo observer = new ObserverDemo();
// 添加观察者
observer.addObserver(((o, arg) -> {
System.out.println("发生变化");
}));
observer.addObserver(((o, arg) -> {
System.out.println("手动被观察者通知,准备改变");
}));
observer.setChanged(); // 数据变化
observer.notifyObservers(); // 通知
}
}
5.3、响应式编程(Reactor)
-
响应式编程操作中,Reactor 是满足 Reactive 规范框架
-
Reactor 有两个核心类,Mono 和 Flux,这两个类实现结构 Publisher,提供丰富操作符。
- Flux 对象实现发布者,返回 N 个元素;
- Mono 实现发布者,返回 0 或者 1 个元素
-
Flux 和 Mono 都是数据流的发布者,使用 Flux 和 Mono 都可以发出三种数据信号:
- 元素值
- 错误信号
- 完成信号
三种信号特点:
- 错误信号和完成信号都是终止信号,不能共存。终止信号用于告诉订阅者数据流结束了。错误信号终止数据流同时把错误信息传递给订阅者。
- 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示空数据流
- 如果没有错误信号,没有完成信号,表示无限数据流
- 操作符
对数据流进行一道道操作,称为 操作符,比如工厂流水线-
Map 元素映射为 新元素
-
flatMap 元素映射为流
把每个元素转换流,把转换之后多个流合并大的流
-
第一步、引入依赖
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.1.15.RELEASE</version>
</dependency>
第二步、编程代码
@Test
void contextLoads() {
// just 方法直接声明
Flux.just(1,2,3,4);
Mono.just(1);
// 其他方法
Integer[] array = new Integer[]{1,2,3,4};
Flux.fromArray(array);
List<Integer> list = Arrays.asList(array);
Flux.fromIterable(list);
Stream<Integer> stream = list.stream();
Flux.fromStream(stream);
}
调用 just()
或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生的
// just 方法直接声明
Flux.just(1,2,3,4).subscribe(System.out::print);
Mono.just(1).subscribe(System.out::print);
5.4、SpringWebFlux 执行流程 和 核心API
SpringWebFlux 基于 Reactor,默认使用容器是 Netty,Netty 是高性能的NIO 框架,异步非阻塞的框架。
-
Netty ----> NIO
-
SpringWebFlux 执行过程和 SpirngMVC 相似的
-
SpringWebFlux 核心控制器 DispatchHandler,实现接口 WebHandler
-
接口 WebHandler 有一个方法
public interface WebHandler { Mono<Void> handle(ServerWebExchange exchange); }
-
- SpringWebflux 里面 DispatchHandler,负责请求的处理
- HandlerMapping:请求查询到处理的方法
- HandlerAdapter:真正负责请求处理
- HandlerResultHandler:响应结果处理
- SpringWebflux 实现函数式编程,两个接口:
- RouterFunction(路由处理)
- HandlerFunction(处理函数)
5.5、SpringWebFlux(基于注解编程模型)
SpringWebFlux 实现方式有两种:注解编程模型 和 函数式编程模型
- 使用注解编程模型方式,和之前 SpringMVC 使用相似的,只需要把相关依赖配置到项目中,SpringBoot 自动配置相关运行容器,默认情况下使用 Netty 服务器
第一步、创建 SpringBoot 工程,引入 Webflux 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
第二步、配置启动端口号
server.port=8081
第三步、创建包和相关类
- 实体类
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private String name;
private String gender;
private Integer age;
}
- 创建Service层接口以及实现类
package com.hgw.demowebflux1.service.impl;
public class UserServiceImpl implements UserService {
// 创建 map 集合存储数据
private final Map<Integer,User> users = new HashMap<>();
public UserServiceImpl() {
this.users.put(1,new User("lucy","nan",20));
this.users.put(2,new User("mary","nv",30));
this.users.put(3,new User("jack","nan",18));
}
// 根据 id 查询一个用户
@Override
public Mono<User> getUserById(int id) {
return Mono.just(this.users.get(id));
}
// 查询多个用户
@Override
public Flux<User> getAllUsers() {
return Flux.fromIterable(this.users.values());
}
// 添加用户
@Override
public Mono<Void> saveUserInfo(Mono<User> userMono) {
return userMono.doOnNext(person -> {
// 向 map 集合里面放值
int id = users.size()+1;
users.put(id, person);
}).thenEmpty(Mono.empty());
}
}
- 编写Controller层
package com.hgw.demowebflux1.controller;
@RestController
public class UserController {
// 注入 service
private UserService userService;
// id查询
@GetMapping("/user/{id}")
public Mono<User> getUserId(@PathVariable int id) {
return userService.getUserById(id);
}
// 查询所有
@GetMapping("/user")
public Flux<User> getUsers() {
return userService.getAllUsers();
}
// 添加
@PostMapping("/saveuser")
public Mono<Void> saveUser(@RequestBody User user) {
Mono<User> userMono = Mono.just(user);
return userService.saveUserInfo(userMono);
}
}
-
【spring-webmvc + Servlet + Tomcat】命令式的、同步阻塞的
-
【spring-webflux + Reactor + Netty】响应式的、异步非阻塞的
5.6、SpringWebflux(基于函数式编程模型)
-
在使用函数式编程模型操作的时候,需要自己初始化服务器
-
基于函数式编程模型的时候,有两个核心接口:
- RouterFunction:实现路由功能,请求转发给对应的 handler
- HandlerFunction:处理请求生成响应的函数
核心任务定义两个函数式接口的实现并且启动需要的服务器
-
SpringWebflux 请求和响应不再是 ServletRequest 和 ServletResponse ,而是 ServerRequest 和 ServerResponse
第一步、创建 SpringBoot 工程,引入 Webflux 依赖,并复制5.5中的 entity 和 service 内容
第二步、创建 Handler (具体实现方法)
package com.hgw.demowebflux2.handler;
import com.hgw.demowebflux2.entity.User;
import com.hgw.demowebflux2.service.UserService;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.imageio.spi.ServiceRegistry;
import static org.springframework.web.reactive.function.server.EntityResponse.fromObject;
/**
* Data time:2022/4/27 15:55
* StudentID:2019112118
* Author:hgw
* Description:
*/
public class UserHandler {
private final UserService userService;
public UserHandler(UserService userService) {
this.userService = userService;
}
// 根据 id 查询
public Mono<ServerResponse> getUserById(ServerRequest request) {
// 1、获取id
int userId = Integer.valueOf(request.pathVariable("id"));
// 2、空值处理
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
// 3、调用 service 方法得到数据
Mono<User> userMono = this.userService.getUserById(userId);
// 4、把 userMono 进行转换返回
// 使用 Reactor 操作副 flatMap
return userMono.flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person));
}
// 查询所有
public Mono<ServerResponse> getAllUsers(ServerRequest request) {
Flux<User> users = this.userService.getAllUsers();
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(users);
}
// 添加
public Mono<ServerResponse> saveUser(ServerRequest request) {
Mono<User> userMono = request.bodyToMono(User.class);
return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
}
}
第三步、初始化服务器,编写 Router
- 创建 Router 路由
- 创建服务器完成适配
package com.hgw.demowebflux2;
import com.hgw.demowebflux2.handler.UserHandler;
import com.hgw.demowebflux2.service.impl.UserServiceImpl;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.netty.http.server.HttpServer;
import java.io.IOException;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
/**
* Data time:2022/4/27 16:29
* StudentID:2019112118
* Author:hgw
* Description:
*/
public class Server {
public static void main(String[] args) throws IOException {
Server server = new Server();
server.createReactorServer();;
System.out.println("enter to exit");
System.in.read();
}
// 1.创建 Router 路由
public RouterFunction<ServerResponse> routerFunction() {
// 1、创建 handler 对象
UserServiceImpl userService = new UserServiceImpl();
UserHandler userHandler = new UserHandler(userService);
// 2、设置路由
return RouterFunctions.route(
GET("/user/{id}").and(accept(MediaType.APPLICATION_JSON)),userHandler::getUserById)
.andRoute(GET("/users").and(accept(MediaType.APPLICATION_JSON)),userHandler::getAllUsers);
}
// 2. 创建服务器完成适配
public void createReactorServer() {
// 路由和handler适配
RouterFunction<ServerResponse> route = routerFunction();
HttpHandler httpHandler = toHttpHandler(route);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
// 创建服务器
HttpServer httpServer = HttpServer.create();
httpServer.handle(adapter).bindNow();
}
}