Spring5 总结
1. IOC
1.1 概念
- 控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理
- 好处:降低耦合度
1.2 IOC 底层原理
XML解析、工厂模式、反射
1.3 IOC(接口)
- IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
- Spring 提供IOC容器两种实现方式:(两个接口)
- BeanFactory: IOC容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用
加载配置文件时不会创建对象,在获取对象(使用)才去创建对象 - ApplicationContext: BeanFactory接口的子接口,提供更多强大的功能,一般由开发人员进行使用
加载配置文件时就会把在配置文件对象进行创建 - ApplicationContext接口实现类
FileSystemXMLApplicationContext:电脑盘符路径
ClassPathXMLApplicationContext:src下类路径
- BeanFactory: IOC容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用
1.4 IOC 操作 Bean 管理
- Bean 管理指的是两个操作:
- 创建对象
- 注入属性
- Bean 管理操作有两种方式:
- 基于 XML 配置文件方式实现
- 基于注解方式实现
1.4.1 基于 XML 方式
1.4.1.1 基于 XML 方式创建对象
<bean id="user" class="fan.com.domain.User"></bean>
- 在 Spring 配置文件里,使用 bean 标签,标签里面添加对应属性,就可以实现对象创建
- 在 bean 标签有很多属性:
- id 属性:唯一标识,可以不使用 id,只留全类名,此时类名作为唯一标识
- class 属性:类全路径(包类路径)
- 创建对象时,默认是执行无参构造方法完成对象创建
1.4.1.2 基于 xml 方式注入属性
DI:依赖注入,就是注入属性
-
第一种注入方式:使用 set 方法注入
public class User { private Integer id; private String name; public void setId(Integer id) { this.id = id; } public void setName(String name) { this.name = name; } }
<bean id="user" class="fan.com.domain.User"> <property name="id" value="1"></property> <property name="name" value="张三"></property> </bean>
-
第二种注入方式:使用有参构造注入
public class User { private Integer id; private String name; public void printValue(){ System.out.println(id + ":" + name); } public User(Integer id, String name) { this.id = id; this.name = name; } }
<bean id="user" class="fan.com.domain.User"> <constructor-arg name="id" value="1"></constructor-arg> <constructor-arg name="name" value="张三"></constructor-arg> </bean>
-
p名称空间注入(需要依赖于 set 方法)
<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="user" class="fan.com.domain.User" p:id="1" p:name="张三"></bean> </beans>
1.4.1.3 基于 XML 方式注入属性(其他类型属性)
-
字面量(前面注入的属性都是字面量)\
- null 值
<bean id="user" class="fan.com.domain.User"> <property name="name"> <null></null> </property> </bean>
- 特殊符号 <>
<bean id="user" class="fan.com.domain.User"> <property name="id" value="1"></property> <property name="name"> <value> <![CDATA[<<南京>>]]> </value> <!-- <<南京>>,也可使用转义字符 > --> </property> </bean>
- null 值
-
注入属性-外部 bean(ref)
创建两个类 service 和 dao,在 service 里调用 dao 的方法public class UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void printValue(){ System.out.println("service add..."); userDao.update(); } } public class UserDao { public void update() { System.out.println("userDao update..."); } }
<bean id="userService" class="fan.com.service.UserService"> <!-- 使用ref --> <property name="userDao" ref="userDao"></property> </bean> <bean id="userDao" class="fan.com.dao.UserDao"></bean>
-
注入属性-内部 bean
public class Dept { private String dname; public void setDname(String dname) { this.dname = dname; } } public class Emp { private Integer id; private String name; private Dept dept; public void setId(Integer id) { this.id = id; } public void setName(String name) { this.name = name; } public void setDept(Dept dept) { this.dept = dept; } public void printValue(){ System.out.println(id + ":" + name + ":" + dept); } }
<bean id="emp" class="fan.com.domain.Emp"> <property name="id" value="1" /> <property name="name" value="张三" /> <property name="dept"> <bean id="dept" class="fan.com.domain.Dept"> <property name="dname" value="安保部" /> </bean> </property> </bean>
-
注入属性-级联赋值
<!-- 第一种方法 --> <bean id="emp" class="fan.com.domain.Emp"> <property name="id" value="1" /> <property name="name" value="张三" /> <property name="dept" ref="dept" /> </bean> <bean id="dept" class="fan.com.domain.Dept"> <property name="dname" value="安保部" /> </bean> <!-- 第二种方法 --> <bean id="emp" class="fan.com.domain.Emp"> <property name="id" value="1" /> <property name="name" value="张三" /> <property name="dept" ref="dept" /> <property name="dept.dname" value="财务部" /> // 需要 get 方法,获取到dept对象 </bean> <bean id="dept" class="fan.com.domain.Dept" />
-
注入属性-数组、集合、map、set
public class User { private int[] arrays; private List<String> lists; private Map<Integer,String> maps; private Set<String> sets; public void setArrays(int[] arrays) { this.arrays = arrays; } public void setLists(List<String> lists) { this.lists = lists; } public void setMaps(Map<Integer, String> maps) { this.maps = maps; } public void setSets(Set<String> sets) { this.sets = sets; } public void printValue(){ System.out.println(Arrays.toString(arrays)); System.out.println(lists); System.out.println(maps); System.out.println(sets); } }
<bean id="user" class="fan.com.domain.User"> <property name="arrays"> <array> <value>1</value> <value>2</value> </array> </property> <property name="lists"> <list> <value>张三</value> <value>小张</value> </list> </property> <property name="maps"> <map> <entry key="1" value="Map1" /> <entry key="2" value="Map2" /> </map> </property> <property name="sets"> <set> <value>MySQL</value> <value>Redis</value> </set> </property> </bean>
-
注入多个对象属性
public class User { private List<Course> courseList; public void setCourseList(List<Course> courseList) { this.courseList = courseList; } public void printValue(){ System.out.println(courseList.toString()); } }
<bean> <property name="courseList"> <list> <ref bean="course1" /> <ref bean="course2" /> </list> </property> </bean> <bean id="course1" class="fan.com.domain.Course"> <property name="cname" value="Spring" /> </bean> <bean id="course2" class="fan.com.domain.Course"> <property name="cname" value="Spring5" /> </bean>
-
提取集合公共部分
<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" // 定义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"> <util:list id="nameList"> <value>张三</value> <value>李四</value> <value>王五</value> </util:list> <bean id="user" class="fan.com.domain.User"> <property name="lists" ref="nameList"></property> </bean> </beans>
1.4.1.4 工厂 Bean(FactoryBean)
- Spring 有两种类型 bean,一种普通 bean,另外一种工厂 bean (FactoryBean)
- 普通 bean:在配置文件中定义 bean 类型就是返回类型
- 工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样
- 第一步创建类,让这个类作为工厂 bean,实现接口 FactoryBean
- 第二步实现接口里面的方法,在实现的方法中定义返回的 bean 类型
<!-- 创建 bean -->
<bean id="user" class="fan.com.domain.User" /> // 创建 bean(user)
public class User implements FactoryBean<Course> {
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("abc");
return course;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
public class Test1 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Course course = context.getBean("user", Course.class); // 改成返回的 bean 类型(Course)
System.out.println(course); // Course{cname='abc'}
}
}
1.4.2 bean 作用域
-
在 Spring 里,默认情况下, bean 是单实例对象。安全的
public class Test1 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); Course course = context.getBean("course", Course.class); Course course1 = context.getBean("course", Course.class); System.out.println(course); System.out.println(course1); } }
-
设置为单实例还是多实例(scope)
- 在 spring 配置文件 bean 标签里面有属性(scope),用于设置单实例还是多实例
- scope 属性值(singleton、prototype、request、session、websocket、application)
- 第一个值默认值,singleton,表示是单实例对象,加载 spring 配置文件时就会创建单实例对象
- 第二个值 prototype,表示是多实例对象,不是在加载 spring 配置文件时创建对象,而是在 getBean 方法时创建多实例对象
1.4.3 bean 生命周期
- 通过构造器创建 bean 实例(无参数构造)
- 为 bean 的属性设置值和对其他bean引用(调用set方法)
- 把 bean 实例传递给 bean 后置处理器的方法(postProcessBeforeInitialization)
- 调用 bean 的初始化的方法(需要进行配置初始化的方法)
- 把 bean 实例传递给 bean 后置处理器的方法(postProcessAfterInitialization)
- bean 可以使用了(对象获取到了)
- 当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
public class Course {
private String cname;
public Course() {
System.out.println("第一步:执行构造器方法");
}
public void setCname(String cname) {
this.cname = cname;
System.out.println("第二步:调用set方法");
}
// @PostConstruct
public void initMethod(){
System.out.println("第三步:执行初始化方法");
}
// @PreDestory
public void destroyMethod(){
System.out.println("第五步:执行销毁方法");
}
}
public class Test1 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Course course = context.getBean("course", Course.class);
System.out.println("第四步:获取创建 bean 实例对象");
System.out.println(course);
((ClassPathXmlApplicationContext)context).close(); // 调用销毁方法
}
}
<!-- 配置初始化方法和销毁方法 -->
<bean id="course" class="fan.com.domain.Course" init-method="initMethod" destroy-method="destroyMethod">
<property name="cname" value="张三" />
</bean>
1.4.4 后置处理器 BeanPostProcessor
- postProcessBeforeInitialization 在 Bean 实例化、依赖注入后,初始化前调用
- postProcessAfterInitialization 在 Bean 实例化、依赖注入、初始化都完成后调用
当需要添加多个后置处理器实现类时,默认情况下 Spring 容器会根据后置处理器的定义顺序来依次调用。也可以通过实现 Ordered 接口的 getOrder 方法指定后置处理器的执行顺序。该方法返回值为整数,默认值为 0,值越大优先级越低
<!-- 创建 bean -->
<bean id="coursePost" class="fan.com.domain.CoursePost" />
实现 BeanPostProcessor 接口
public class CoursePost implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之前执行的方法");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之后执行的方法");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
@Override
public int getOrder() {
return 5;
}
}
1.4.5 XML 自动装配
根据指定装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入。autorire 属性常用两个值:
- byName,根据属性名称注入,注入值 bean 的id值和类属性名称一样
- byType,根据属性类型注入,假如有两个同样的 bean 报错
<bean id="user" class="fan.com.domain.User" autowire="byName" />
<bean id="course" class="fan.com.domain.Course">
<property name="cname" value="张三" />
</bean>
1.4.6 XML(引入外部属性文件)
<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:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driverClass}" />
<property name="url" value="${url}" />
<property name="username" value="${userName}" />
<property name="password" value="${password}" />
</bean>
</beans>
1.5 基于注解方式
- 注解是代码特殊标记,格式: @注解名称(属性名称=属性值,属性名称=属性..
- 使用注解,注解作用在类上面,方法上面,属性上面
- 使用注解目的:简化 XML 配置
1.5.1 Spring 针对 Bean 管理中创建对象提供注解
@Component
,可以使用此注解描述 Spring 中的 Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次。 使用时只需将该注解标注在相应类上即可。@Service
,通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。@Controller
,通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。@Repository
,用于将数据访问层(DAO层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同
上面四个注解功能是一样的,都可以用来创建 bean 实例
@Bean
,通常是我们在标有该注解的方法中定义产生这个 bean 的逻辑,告诉 Spring 这个方法将会返回一个对象,这个对象要注册为 Spring 应用上下文中的 bean。@Bean 注解比 @Component 注解的自定义性更强
1.5.2 基于注解方式创建对象
-
开启组件扫描,扫描多个包
<!-- 扫描上层目录 --> <context:component-scan base-package="fan.com" /> <!-- 逗号隔开 --> <context:component-scan base-package="fan.com.dao, fan.com.service" />
-
创建类,在类上面添加创建对象注解
// value 可以省略不写,默认值是类名称,首字母小写 @Service(value = "userService") public class UserService { }
-
组件扫描细节配置
use-default-filters="false"
表示不适用默认 filter,自己配 filtercontext:include-filter
设置需要扫描哪些内容context:exclude-filter
设置哪些内容不需要扫描
<context:component-scan base-package="fan.com" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/> </context:component-scan>
1.5.3 基于注解方式实现属性注入
- @Autowired:根据属性类型进行自动装配。Spring 推荐使用
@Service(value = "userService") public class UserService { @Autowired private UserDao userDao; public void add(){ System.out.println("service add.."); userDao.update(); } }
- @Qualifier:根据属性名称进行注入,配合 @Autowired 进行使用,当有多个实现类时,需要根据名称注入
@Autowired @Qualifier(value = "userDaoImpl1") private UserDao userDao;
- @Resource:可以根据类型注入、可以根据名称注入,位于 javax.annotation 包下。Spring 不推荐使用,JavaEE 推荐使用
@Resource // 根据类型注入 // @Resource(name = "userDaoImpl1") 根据名称注入 private UserDao userDao;
- @Value:注入普通类型属性
@Value("张三") private String name;
1.5.4 完全注解开发(配置类)
@Configuration
@ComponentScan(basePackages = {"fan.com"})
public class SpringConfig {
}
public class Test1 {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
2. AOP
2.1 概念
- 面向切面(方面)编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
- 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
2.2 AOP 底层原理
2.2.1 AOP 底层使用动态代理
有两种情况动态代理:
- 第一种:有接口情况,使用 JDK 动态代理(创建接口实现类代理对象,增强类的方法)
- 第二种:没有接口情况,使用 CGLIB 动态代理(创建当前类子类代理对象,增强类的方法)
2.2.2 AOP(JDK 动态代理)
使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象, 调用 newProxyInstance 方法,三个参数
- 第一参数,类加载器
- 第二参数,增强方法所在的类,这个类实现的接口,支持多个接口
- 第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的方法
public interface UserDao {
public int add(int a, int b);
public String update(String id);
}
public class UserDaoImpl implements UserDao {
@Override
public String update(String id) {
return id;
}
@Override
public int add(int a, int b) {
return a + b;
}
}
public class JDKProxy {
public static void main(String[] args) {
Class[] interfaces = {UserDao.class}; // 类实现接口
UserDaoImpl userDaoImpl = new UserDaoImpl();
UserDao userDao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDaoImpl));
int result = userDao.add(1,2);
// 代理对象UserDaoProxy返回的结果,add方法,返回为3,假如为update方法,返回null
System.out.println(result);
}
}
class UserDaoProxy implements InvocationHandler{
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("add".equals(method.getName())){ // 判断是哪个方法才执行
System.out.println("方法之前执行..." + method.getName() + "传递的参数:" + Arrays.toString(args));
Object result = method.invoke(obj,args); // 方法执行,传入对象和参数
System.out.println("方法之后执行" + obj);
return result;
}else {
return null;
}
}
}
2.3 AOP 术语
- Joinpoint 连接点:那些被拦截到的点,类里面哪些方法可以被拦截到被增强,这些方法称为连接点
- Pointcut 切入点:指要对哪些 Joinpoint 进行拦截,即被拦截的连接点,实际被真正增强的方法,称为切入点
- Advice 通知(增强):实际增强的逻辑部分称为通知(增强)
- 前置通知
@Before
:方法执行之前执行 - 后置/返回通知
@AfterReturning
:方法返回结果之后执行 - 环绕通知
@Around
:方法之前和之后都执行 - 异常通知
@AfterThrowing
:异常时执行 - 最终通知:
@After(finally)
:方法执行之后执行
- 前置通知
- Aspect 切面:一个动作,把通知应用到切入点的过程
- Target 目标:指代理的目标对象
- Weaving 织入:指把增强代码应用到目标上,生成代理对象的过程
- Proxy 代理:指生成的代理对象
2.4 AOP 操作
- Spring 框架一般都是基于 AspectJ 实现 AOP 操作
AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spring 框架一起使用, 进行 AOP 操作 - 基于 AspectJ 实现 AOP 操作
- 基于 XML 配置文件实现
- 基于注解方式实现(实用)
- 切入点表达式
- 作用:知道对哪个类里面的哪个方法进行增强
- 语法结构:
execution([权限修饰符] [返回类型(可省略)] [类全路径] [方法名称] ([参数列表]))
- 举例 1:对 fan.com.dao.BookDao 类里面的 add 进行增强
execution(* fan.com.dao.BookDao.add(..)) // 修饰符 包名.类名.方法名(参数..)
- 举例 2:对 fan.com.dao.BookDao 类里面的所有的方法进行增强
execution(* fan.com.dao.BookDao.* (..))
- 举例 3:对 fan.com.dao 包里面所有类,类里面所有方法进行增强
execution(* fan.com.dao.*.* (..))
- 举例 1:对 fan.com.dao.BookDao 类里面的 add 进行增强
2.4.1 基于注解方式(抽取公共切入点)
-
开启注解扫描和生成代理对象:
<?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="fan.com" /> <aop:aspectj-autoproxy /> </beans>
使用完全注解开发
@Configuration @ComponentScan(basePackages = {"fan.com"}) @EnableAspectJAutoProxy(proxyTargetClass = true) // 开启代理对象 public class SpringConfig { }
-
类和增强类:
@Component public class User { public void add(){ System.out.println("user add..."); } } @Component @Aspect public class UserProxy { @Pointcut(value = "execution(* fan.com.domain.User.add(..))") // 抽取公共切入点 public void pointDemo(){ } @Before(value = "pointDemo()") public void before() { System.out.println("前置通知before........."); } @After(value = "pointDemo())") public void after() { System.out.println("最终通知after........."); } @AfterThrowing(value = "pointDemo()") public void afterThrowing() { System.out.println("异常通知afterThrowing........."); } @AfterReturning(value = "pointDemo()") public void afterReturning() { System.out.println("后置通知afterReturning........."); } @Around(value = "pointDemo()") public void around(ProceedingJoinPoint joinPoint) throws Throwable{ try { System.out.println("环绕前置通知"); //前置通知@Before joinPoint.proceed(); //目标方法执行 System.out.println("环绕返回通知"); //环绕返回通知@AfterReturning } catch (Throwable throwable) { System.out.println("环绕异常通知"); //环绕异常通知@AfterThrowing throw new RuntimeException(throwable); } finally { System.out.println("环绕最终通知"); //最终通知@After } } }
执行顺序:* 正常情况:环绕前置 == @Before == 目标方法执行 ==@AfterReturning == @After == 环绕返回 ==环绕最终
- 异常情况:环绕前置== @Before == 目标方法执行 == @AfterThrowing == @After == 环绕异常 == 环绕最终
- 异常情况:环绕前置== @Before == 目标方法执行 == @AfterThrowing == @After == 环绕异常 == 环绕最终
-
多个增强类对同一个方法进行增强,设置增强类优先级
在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高@Component @Aspect @Order(1) // 设置优先级 public class PersonProxy { @Pointcut(value = "execution(* fan.com.domain.User.add(..))") public void pointDemo(){ } @Before(value = "pointDemo()") public void before() { System.out.println("person before........."); } }
-
测试类
public class Test1 { public static void main(String[] args) { // ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); User user = ac.getBean("user", User.class); user.add(); } }
2.4.2 AspectJ 配置文件
<bean id="book" class="fan.com.Book"></bean>
<bean id="bookProxy" class="fan.com.BookProxy"></bean>
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* fan.com.Book.buy(..))"/>
<!--配置切面 aop:aspect 大多用于面向切面编程,aop:advisor 大多用于事务管理-->
<aop:aspect ref="bookProxy">
<!--增强作用在具体的方法上-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
-
定义切面
<aop:aspect>
- 该元素可以将定义好的 Bean 转换为切面 Bean,所以使用
<aop:aspect>
之前需要先定义一个普通的 Spring Bean - id 用来定义该切面的唯一表示名称,ref 用于引用普通的 Spring Bean
<aop:config> <aop:aspect id="myAspect" ref="aBean"> ... </aop:aspect> </aop:config>
- 该元素可以将定义好的 Bean 转换为切面 Bean,所以使用
-
定义切入点
<aop:pointcut>
- 当
<aop:pointcut>
元素作为<aop:config>
元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享 - 当
<aop:pointcut>
元素作为<aop:aspect>
元素的子元素时,表示该切入点只对当前切面有效 - id 用于指定切入点的唯一标识名称,execution 用于指定切入点关联的切入点表达式
<aop:config> <aop:pointcut id="myPointCut" expression="execution(* net.biancheng.service.*.*(..))"/> </aop:config>
- 当
-
定义通知
<aop:aspect id="myAspect" ref="aBean"> <!-- 前置通知 --> <aop:before pointcut-ref="myPointCut" method="..."/> <!-- 后置通知 --> <aop:after-returning pointcut-ref="myPointCut" method="..."/> <!-- 环绕通知 --> <aop:around pointcut-ref="myPointCut" method="..."/> <!-- 异常通知 --> <aop:after-throwing pointcut-ref="myPointCut" method="..."/> <!-- 最终通知 --> <aop:after pointcut-ref="myPointCut" method="..."/> .... </aop:aspect>
3. JdbcTemplate
3.1 配置连接属性
<?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="fan.com" />
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置数据库连接池 Druid -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="${driverClassName}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!-- 配置 JdbcTemplate 对象,注入 DataSource -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
3.2 Dao层
public interface UserDao {
User selectUser(int id); // 按id 查询
List<User> selectAll(); // 查询所有
int selectTotalCount(); // 查询总记录条数
int updateUser(User user); // 按id 修改
int insertUser(User user); // 插入
int deleteUser(int id); // 按id 删除
int[] batchAdd(List<Object[]> users); // 批量插入
}
@Component
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override // 按id 查询
public User selectUser(int id) {
String sql = "select * from user where id = ? ";
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), id);
return user;
}
@Override // 查询所有
public List<User> selectAll() {
String sql = "select * from user";
List<User> maps = jdbcTemplate.query(sql,new BeanPropertyRowMapper<User>(User.class));
return maps;
}
@Override // 查询总记录条数
public int selectTotalCount() {
String sql = "select count(*) from user";
int i = jdbcTemplate.queryForObject(sql, Integer.class);
return i;
}
@Override // 按id 修改
public int updateUser(User user) {
String sql = "update user set name = ?, age = ? where id = ?";
Object[] args = {user.getName(),user.getAge(),user.getId()};
int update = jdbcTemplate.update(sql, args);
return update;
}
@Override // 插入
public int insertUser(User user) {
Object[] args = {user.getId(),user.getName(),user.getAge()};
String sql = "insert into user value(?,?,?)";
int update = jdbcTemplate.update(sql, args);
return update;
}
@Override // 批量插入
public int[] batchAdd(List<Object[]> users) {
String sql = "insert into user value(?,?,?)";
int[] ints = jdbcTemplate.batchUpdate(sql, users);
return ints;
}
@Override // 按id 删除
public int deleteUser(int id) {
String sql = "delete from user where id = ?";
int update = jdbcTemplate.update(sql, id);
return update;
}
}
3.3 Service 层
@Service(value = "userService")
public class UserService {
@Autowired
private UserDao userDao;
public User selectUser(int id){
User user = userDao.selectUser(id);
return user;
}
public List<User> selectAll() {
List<User> users = userDao.selectAll();
return users;
}
public int selectTotalCount() {
int i = userDao.selectTotalCount();
return i;
}
public int updateUser(User user){
int i = userDao.updateUser(user);
return i;
}
public int insertUser(User user){
int i = userDao.insertUser(user);
return i;
}
public int[] batchAdd(List<Object[]> users) {
int[] ints = userDao.batchAdd(users);
return ints;
}
public int deleteUser(int id){
int i = userDao.deleteUser(id);
return i;
}
}
3.4 测试类
public class Test1 {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
UserService userService = ac.getBean("userService", UserService.class);
User user = new User(2,"ff",18);
int i = userService.updateUser(user);
System.out.println(i); // 1
List<User> users = userService.selectAll();
System.out.println(users);
List<Object[]> users = new ArrayList<>();
Object[] user1 = {6, "6", 6};
Object[] user2 = {7,"7",8};
Object[] user3 = {8, "7", 8};
users.add(user1);
users.add(user2);
users.add(user3);
int[] ints = userService.batchAdd(users);
}
}
4. 事务操作
4.1 概念
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败。典型场景:银行转账
- lucy 转账 100 元 给 mary
- lucy 少 100,mary 多 100
<?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:tx="http://www.springframework.org/schema/tx"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="fan.com" />
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="${driverClassName}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
Dao 层
public interface PriceDao {
public void reduce();
public void add();
}
@Component
public class PriceDaoImpl implements PriceDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void reduce() {
String sql = "update price set price = price - ? where name = ?";
jdbcTemplate.update(sql,100,"张三");
}
@Override
public void add() {
String sql = "update price set price = price + ? where name = ?";
jdbcTemplate.update(sql,100,"李四");
}
}
Service 层
@Service
public class PriceService {
@Autowired
private PriceDao priceDao;
public void account(){
priceDao.reduce();
int a = 10 / 0;
priceDao.add();
}
}
4.2 接口
PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 是事务的 3 个核心接口
4.2.1 PlatformTransactionManager 接口
PlatformTransactionManager 接口用于管理事务,接口定义如下:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
4.2.2 TransactionDefinition 接口
TransactionDefinition 接口提供了获取事务相关信息的方法,接口定义如下
public interface TransactionDefinition {
int getPropagationBehavior();
int getIsolationLevel();
String getName();
int getTimeout();
boolean isReadOnly();
}
4.2.3 TransactionStatus 接口
TransactionStatus 接口提供了一些简单的方法来控制事务的执行和查询事务的状态,接口定义如下:
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
boolean isCompleted();
}
4.3 Spring 事务管理介绍
- 事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)
- 在 Spring 进行事务管理操作有两种方式:编程式事务管理和声明式事务管理(实用)
- 声明式事务管理
- 基于注解方式(实用)
- 基于 xml 配置文件方式
- 在 Spring 进行声明式事务管理,底层使用 AOP 原理
- Spring 事务管理 API
提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
4.4 注解声明式事务管理
- 配置事务管理器
<!-- 创建事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> // 注入数据源 </bean>
- 开启事务注解
<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:tx="http://www.springframework.org/schema/tx" // 引入名称空间 tx 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd "> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 开启事务注解 --> <tx:annotation-driven transaction-manager="transactionManager" /> </beans>
- 在 service 类上面(或者 service 类里面方法上面)添加事务注解
- @Transactional,这个注解添加到类上面,也可以添加方法上面
- 如果把这个注解添加类上面,这个类里面所有的方法都添加事务
- 如果把这个注解添加方法上面,为这个方法添加事务
4.5 声明式事务管理参数配置
- propagation:事务传播行为:多事务方法直接进行调用,这个过程中事务 是如何进行管理
- ioslation:事务隔离级别
- timeout:超时时间
- 事务需要在一定时间内进行提交,如果不提交进行回滚
- 默认值是 -1 ,设置时间以秒单位进行计算
- readOnly:是否只读
- 读:查询操作,写:添加修改删除操作
- readOnly 默认值 false,表示可以查询,可以添加修改删除操作
- 设置 readOnly 值是 true,设置成 true 之后,只能查询
- rollbackFor:回滚
设置出现哪些异常进行事务回滚 - noRollbackFor:不回滚
设置出现哪些异常不进行事务回滚
4.6 XML 声明式事务管理
<!--1 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2 配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务-->
<tx:method name="account" propagation="REQUIRED"/>
<!--<tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>
<!--3 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* fan.com.service.PriceService.*(..))"/>
<!--配置切面 aop:advisor 大多用于事务管理,aop:aspect 大多用于面向切面编程-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
4.7 完全注解开发声明式事务管理
@Configuration //配置类
@ComponentScan(basePackages = "com.atguigu") //组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
//创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///user_db");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
//创建 JdbcTemplate 对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
//到 ioc 容器中根据类型找到 dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入 dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
4.8 事务失效的原因(重要)
4.8.1 数据库引擎不支持
以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。 从 MySQL 5.5.5 开始的默认存储引擎是 InnoDB,之前默认的都是 MyISAM。所以这点需要注意,底层引擎不支持事务,再怎么操作事务都无效。
4.8.2 没有被 Spring 管理
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
// update order
}
}
如果此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。
4.8.3 方法不是 public 的
@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。
4.8.4 自身调用问题
@Service
public class OrderServiceImpl implements OrderService {
public void update(Order order) {
updateOrder(order);
}
@Transactional
public void updateOrder(Order order) {
// update order
}
}
上面的 update 方法上面没有加 @Transactional 注解,调用有 @Transactional 注解的 updateOrder 方法,updateOrder 方法上的事务不管用。
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
updateOrder(order);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateOrder(Order order) {
// update order
}
}
上面这次在 update 方法上加了 @Transactional,而 updateOrder 加了 REQUIRES_NEW 新开启一个事务,那么新开的事务不管用。
因为它们发生了自身调用,调该类自己的方法,而没有经过 Spring 的代理类,默认只有在外部调用事务才会生效。
解决方案:
<!-- 在Spring配置中添加标签 -->
<aop:aspectj-autoproxy expose-proxy="true"/>
<!-- <aop:config expose-proxy="true"> -->
// 在代码的调用中要求使用代理对象去调用即可:
((ServiceA ) AopContext.currentProxy()).insert();
4.8.5 数据源没有配置事务管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
4.8.6 不支持事务
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
updateOrder(order);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateOrder(Order order) {
// update order
}
}
Propagation.NOT_SUPPORTED: 表示不以事务运行,当前若存在事务则挂起
4.8.7 异常被 catch
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
// 异常被 catch,但又不抛出来
}
}
}
4.8.8 异常类型错误
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
throw new Exception("更新错误");
}
}
}
这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:
@Transactional(rollbackFor = Exception.class)
这个配置仅限于 Throwable 异常类及其子类
5. Spring5 新特性
整个 Spring5 框架的代码基于 Java8,运行时兼容 JDK9,许多不建议使用的和方法在代码库中删除
5.1 自带的通用日志封装 log4j2
- Spring5 已经移除 Log4jConfigListener,官方建议使用 Log4j2
- Spring5 框架整合 Log4j2
5.1.1 引入依赖
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.13.3</version>
</dependency>
<!-- 使用slf4j日志门面 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.13.3</version>
</dependency>
5.1.2 配置文件 log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="INFO">
<!--定义所有的appender -->
<appenders>
<!--这个输出控制台的配置 -->
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式 -->
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</console>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息 -->
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.mybatis" level="INFO"></logger>
<root level="info">
<appender-ref ref="Console" />
</root>
</loggers>
</configuration>
5.1.3 手动打印日志
public class UserLog {
public static final Logger log = LoggerFactory.getLogger(UserLog.class);
public static void main(String[] args) {
log.info("hello log4j2");
log.warn("hello log4j2");
}
}
5.2 Spring5 核心容器支持 @Nullable 注解
@Nullable 注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空
- 注解用在方法上面,方法返回值可以为空
- 注解使用在方法参数里面,方法参数可以为空
- 注解使用在属性上面,属性值可以为空
5.3 Spring5 核心容器支持函数式风格 GenericApplicationContext
//函数式风格创建对象,交给 spring 进行管理
public void testGenericApplicationContext() {
//1 创建 GenericApplicationContext 对象
GenericApplicationContext context = new GenericApplicationContext();
//2 调用 context 的方法对象注册
context.refresh();
context.registerBean("user1",User.class,() -> new User());
//3 获取在 spring 注册的对象
// User user = (User)context.getBean("com.atguigu.spring5.test.User");
User user = (User)context.getBean("user1");
System.out.println(user);
}
5.4 Spring5 支持整合 JUnit5
5.4.1 整合 JUnit4
@RunWith(SpringJUnit4ClassRunner.class) // 单元测试框架
@ContextConfiguration("classpath:bean.xml") // 加载配置文件
public class JTest4 {
@Autowired
private UserService userService;
@Test
public void test1() {
userService.accountMoney();
}
}
5.4.2 Spring5 整合 JUnit5
// @ExtendWith(SpringExtension.class)
// @ContextConfiguration("classpath:bean1.xml")
@SpringJUnitConfig(locations = "classpath:bean1.xml")
public class JTest5 {
@Autowired
private UserService userService;
@Test
public void test1() {
userService.accountMoney();
}
}
6. Webflux
Spring5 新添加的模块,用于 Web 开发。功能和 SpringMVC 类似,Webflux 使用响应式编程出现的框架。传统 Web 框架,比如 SpringMVC,这些基于 Servlet 容器,Webflux 是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet3.1 以后才支持,核心是基于 Reactor 的相关 API 实现的。
6.1 异步非阻塞(NIO)
异步与非阻塞都是针对对象不一样
- 异步和同步针对调用者
调用者发送请求,如果等着对方回应之后才去做其他事情就是同步;如果发送请求之后不等着对方回应就去做其他事情就是异步 - 阻塞和非阻塞针对被调用者
被调用者受到请求之后,做完请求任务之后才给出反馈就是阻塞;受到请求之后马上给出反馈然后再去做事情就是非阻塞
6.2 SpringMVC 与 Webflux 对比
Webflux
- 非阻塞式: 在有限资源下,提高系统吞吐量和伸缩性,以 Reactor 为基础实现响应式编程
- 函数式编程: Spring5 框架基于 Java8,Webflux 使用 Java8 函数式编程方式实现路由请求
- 两个框架都可以使用注解方式,都运行在 Tomcat 等容器中
- SpringMVC 采用命令式编程,Webflux 采用异步响应式编程
6.3 响应式编程(Java 实现)
6.3.1 概念
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
例: 电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似 "=B1+C1"
的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。
6.3.2 Java8 及其之前版本
提供的观察者模式两个类 Observer 和 Observable
public class ObserverDemo extends Observable {
public static void main(String[] args) {
ObserverDemo observerDemo = new ObserverDemo();
observerDemo.addObserver((o, arg) -> {
System.out.println("发生变化");
});
observerDemo.addObserver((o, arg) -> {
System.out.println("手动被观察者通知,准备改变");
});
observerDemo.setChanged();
observerDemo.notifyObservers();
}
}
6.3.3 Java9 及其之后版本
使用 Flow 这个 API,Flow 是 JDK 对 Reactive Stream (响应式流/反应流) 的实现,Reactive Stream 是一套基于发布/订阅模式的数据处理规范。
响应式流从 2013 年开始,作为提供非阻塞背压的异步流处理标准的倡议。 它旨在解决处理元素流的问题——如何将元素流从发布者传递到订阅者,而不需要发布者阻塞,或订阅者需要有无限制的缓冲区或丢弃。
更确切地说,Reactive 流目的是“找到最小的一组接口,方法和协议,用来描述必要的操作和实体以实现这样的目标:以非阻塞背压方式实现数据的异步流”。
响应式流 (Reactive Stream) 规范诞生,定义了如下四个接口:
- Subscription:接口定义了连接发布者和订阅者的方法
- Publisher:接口定义了发布者的方法
- Subscriber:接口定义了订阅者的方法
- Processor<T,R>:接口定义了处理器
6.4 响应式编程(Reactor 实现)
- Reactor 是满足 Reactive 规范的框架
- Reactor 有两个核心类,Mono 和 Flux,这两个类实现接口 Publisher,提供丰富操作符
Flux 对象实现发布者,返回 N 个元素;Mono 实现发布者,返回 0 或者 1 个元素 - Flux 和 Mono 都是数据流的发布者,使用 Flux 和 Mono 都可以发出三种数据信号:元素值,错误信号,完成信号
错误信号和完成信号都代表终止信号,用于告诉订阅者数据流结束了;错误信号终止数据流同时把错误信息传递给订阅者
6.4.1 引入 POM 依赖
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.4.15</version>
</dependency>
6.4.2 实现
public class ReactorDemo {
public static void main(String[] args) {
// just 方法直接声明
Flux<Integer> flux = Flux.just(1, 2, 3, 4);
flux.subscribe(System.out::println);
Mono<Integer> mono = Mono.just(1);
mono.subscribe(System.out::print);
// 其他方法
Integer[] integers = {1, 2, 3, 4};
Flux.fromArray(integers);
List<Integer> list = Arrays.asList(integers);
Flux.fromIterable(list);
Stream<Integer> stream = list.stream();
Flux.fromStream(stream);
}
}
- 错误信号和完成信号都是终止信号,不能共存
- 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流
- 如果没有错误信号,没有完成信号,表示是无限数据流
调用 just 或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生
6.4.3 操作符
对数据流进行一道道操作,称为操作符
- map:元素映射为新元素
- flatMap:元素映射为流。把每个元素转换成流,再把转换后的多个流合并成大的流
6.4.4 Spring Webflux 执行流程和核心 API
Spring Webflux 基于 Reactor,默认使用容器是 Netty,Netty 是高性能的 NIO (异步非阻塞)的框架
- BIO
- NIO。通过 Channel(通道),在 Selector(选择器)里进行注册。实现多路复用,对每个通道选择不同的状态
- Spring Webflux 执行过程和 SpringMVC 相似
Spring Webflux 核心控制器 DispatchHandler,实现接口 WebHandler,其中有一个 handle 方法public Mono<Void> handle(ServerWebExchange exchange) { // http 请求响应信息 if (this.handlerMappings == null) { return createNotFoundError(); } if (CorsUtils.isPreFlightRequest(exchange.getRequest())) { return handlePreFlight(exchange); } return Flux.fromIterable(this.handlerMappings) .concatMap(mapping -> mapping.getHandler(exchange)) .next() .switchIfEmpty(createNotFoundError()) .flatMap(handler -> invokeHandler(exchange, handler)) .flatMap(result -> handleResult(exchange, result)); }
- Spring Webflux 里面 DispatcherHandler,负责请求的处理
- HandlerMapping:请求查询到处理的方法
- HandlerAdapter:真正负责请求处理
- HandlerResultHandler:响应结果处理
- Spring Webflux 实现函数式编程,两个接口:RouterFunction(路由处理)和 HandlerFunction(处理函数)
6.5 Spring Webflux(基于注解编程模型)
SpringWebflux 实现方式有两种:注解编程模型和函数式编程模型。使用注解编程模型方式,和 SpringMVC 使用相似,只需要把相关依赖配置到项目中,SpringBoot 自动配置相关运行容器,默认情况下使用 Netty 服务器
6.5.1 引入 POM 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>2.6.4</version>
</dependency>
6.5.2 Controller
@RestController
public class UserController {
@Resource
private UserService userService;
@GetMapping("/user/{id}")
public Mono<User> getUserById(@PathVariable int id){
return userService.getUserById(id);
}
@GetMapping("/user")
public Flux<User> getAllUser(){
return userService.getAllUser();
}
@PostMapping("/addUser")
public Mono<Void> addUser(@RequestBody User user){
return userService.addUser(Mono.just(user));
}
}
6.5.3 Entity 和 Service
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String id;
private String name;
private String gender;
}
public interface UserService {
Mono<User> getUserById(int id);
Flux<User> getAllUser();
Mono<Void> addUser(Mono<User> userMono);
}
@Service
public class UserServiceImpl implements UserService {
public static final Map<Integer, User> MAP = new HashMap<>();
public UserServiceImpl() {
MAP.put(1, new User("user1", "张三", "男"));
MAP.put(2, new User("user2", "李四", "女"));
MAP.put(3, new User("user3", "王五", "男"));
}
@Override
public Mono<User> getUserById(int id) {
return Mono.justOrEmpty(MAP.get(id));
}
@Override
public Flux<User> getAllUser() {
return Flux.fromIterable(MAP.values());
}
@Override
public Mono<Void> addUser(Mono<User> userMono) {
return userMono.doOnNext(user -> {
MAP.put(MAP.size() + 1, user);
}).thenEmpty(Mono.empty());
}
}
- SpringMVC 方式实现,同步阻塞的方式,基于 SpringMVC + Servlet + Tomcat
- Spring Webflux 方式实现,异步非阻塞方式,基于 Spring Webflux + Reactor + Netty
6.6 Spring Webflux(基于函数式编程模型)
- 在使用函数式编程模型操作时候,需要自己初始化服务器
- 基于函数式编程模型时候,有两个核心接口:RouterFunction(实现路由功能,请求转发给对应的 handler)和 HandlerFunction(处理请求生成响应的函数)
核心任务定义两个函数式接口的实现并且启动需要的服务器 - Spring Webflux 请求和响应不再是 ServletRequest 和 ServletResponse ,而是 ServerRequest 和 ServerResponse
6.6.1 Entity 和 Service
与 6.5.3 同
6.6.2 handler
创建 Handler(具体实现方法)
public class UserHandler {
private final UserService userService;
public UserHandler(UserService userService) {
this.userService = userService;
}
public Mono<ServerResponse> getUserById(ServerRequest serverRequest) {
// 获取 Id 值
Integer id = Integer.valueOf(serverRequest.pathVariable("id"));
// 空值处理
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
// 调用 service 方法得到数据
Mono<User> userMono = userService.getUserById(id);
// 把 userMono 进行转换返回
return userMono.flatMap(user -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(user))).switchIfEmpty(notFound);
}
public Mono<ServerResponse> getAllUser(ServerRequest serverRequest) {
Flux<User> allUser = userService.getAllUser();
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.body(userMono, User.class);
// return userMono.flatMap(user -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
// .body(BodyInserters.fromObject(user))).switchIfEmpty(notFound);
}
public Mono<ServerResponse> addUser(ServerRequest serverRequest){
// 得到 user 对象
Mono<User> userMono = serverRequest.bodyToMono(User.class);
return ServerResponse.ok().build(userService.addUser(userMono));
}
}
6.6.3 Server
先创建 Router 路由,再创建服务器完成适配,最后进行调用。
public class Server {
// 3. 调用
public static void main(String[] args) throws IOException {
Server server = new Server();
server.createReactorServer();
System.in.read();
}
// 1. 创建 Router 路由
public RouterFunction<ServerResponse> routerFunction() {
UserServiceImpl userService = new UserServiceImpl();
UserHandler userHandler = new UserHandler(userService);
return RouterFunctions.route(RequestPredicates.GET("/users/{id}")
.and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), userHandler::getUserById)
.andRoute(RequestPredicates.GET("/users")
.and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), userHandler::getAllUser);
}
// 2. 创建服务器完成适配
public void createReactorServer(){
// 路由和 handler 适配
RouterFunction<ServerResponse> routerFunction = routerFunction();
HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);
ReactorHttpHandlerAdapter handlerAdapter = new ReactorHttpHandlerAdapter(httpHandler);
// 创建服务器
HttpServer httpServer = HttpServer.create();
httpServer.handle(handlerAdapter).bindNow();
}
}
6.6.4 Client
使用 WebClient 调用
public class Client {
public static void main(String[] args) {
// 调用服务器地址
WebClient webClient = WebClient.create("http://localhost:1289");
// 根据 Id 查询
String id = "1";
User block = webClient.get().uri("/users/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToMono(User.class).doOnNext(System.out::println).block();
System.out.println(block);
// 查询所有
webClient.get().uri("/users").accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToFlux(User.class).doOnNext(System.out::println).blockLast();
}
}