推荐先学
概述
介绍
- 是轻量级开源的javaEE框架。
- 可以解决企业开发应用的复杂性。
- 核心:
- IOC:控制反转,将创建对象过程交给Spring
- AOP:面向切面,不修改源代码进行功能增强
特点
- 少jar包,轻量级。
- 方便复杂开发。
- 方便与其他框架进行结合开发。
- 方便操作数据库,尤其是事务管理。
- 方便程序测试。
- 方便API的开发。
入门
-
导入jar包,网上下载,入门前导入五个jar包。
-
首先是非Spring 官网的jar包:commons-logging-1.2.jar
-
然后是四个Spring官方的jar包:
- spring-beans-5.2.9.RELEASE.jar
spring-context-5.2.9.RELEASE.jar
spring-core-5.2.9.RELEASE.jar
spring-expression-5.2.9.RELEASE.jar
- spring-beans-5.2.9.RELEASE.jar
-
首先在src目录下编写一个xml文件,Spring config
<?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="user" class="www.hyb.spring5.User"> </bean> </beans>
bean 标签表示创建一个对象,id为user,来自哪个包下的类。
-
然后编写测试类,测试是否成功。
package com.www.hyb.spring5.Test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.www.hyb.spring5.User; public class SpringTestFirst { @Test public void FirstSpring(){ // 加载src下的配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("firstSpringTes.xml"); // 获取配置创建的对象 User user = (User) context.getBean("user",User.class); System.out.println(user); user.out(); } }
IOC容器
- 为了降低两类之间的耦合度。
- 加入有两个类,分别有两个方法,若是其中一个类想调用另一个类的方法,就要在该类中new另一个类的对象来调用其方法。
- 这样可行,但是耦合度太高,若是另一个类的方法名发生改变,这边这个类的方法名也要发生改变。
- 为了降低耦合度,提出了工厂模式,我们可以让new 另一个类的对象放在一个公共类里,这样,哪个类想调用这个类的方法直接调用工程类就可以了。
- 但紧紧是工厂类还不行,IOC 可以将耦合度降得更低。
- 主要是:xml解析+工厂模式+反射。
底层
-
IOC思想是底层是基于IOC容器完成的,其底层就是工厂模式。
-
xml文件:
<bean id="user" class="www.hyb.com.User"></bean>
-
工厂类:
class UserFactory{ public static User getUser(){ //xml解析 String S=xml文件中calss的值; Class class=Class.forName(s); return (User)class.newInstance(); } }
-
上面可以进一步减低耦合度,若是User类的路径改变,我们只要改变xml中的路径就可以了。
-
在IOC中,主要有两个重要的接口:
BeanFactory:Spring框架内部接口,不建议使用,在解析的xml文件时不会立马创建一个对象。
ApplicationContext:BeanFactory子接口,建议使用,在解析xml文件时会立马创建一个对象,这样的好处时让服务器事先处理好。在此接口中,有两个重要的实现类:FileSystemXmlApplicationContext 该实现类解析xml文件夹时要将xml文件的全路径写入。ClassPathXmlApplicationContext 该实现类在解析xml文件时候直接将文件的名字放进去就可以了,但这个文件要放在src目录下。
Bean管理
xml管理
属性注入
-
对象创建
<bean id="user" class="com.hyb.www.spring5.User"> </bean>
-
属性注入:
<property name="name" value="hyb"></property>
-
创建User类,记住,要想设置哪个属性,就一定得创建其set方法
package com.hyb.www.spring5; public class User { private String name; @Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; } public void setName(String name) { this.name=name; } }
-
编写测试类:
@Test public void FirstSpring(){ // 加载src下的配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("firstSpringTes.xml"); // 获取配置创建的对象 User user = (User) context.getBean("user",User.class); System.out.println(user.toString()); }
-
前面说过类里一定要有set方法,其实不然,还有另一种注入方法,在类里写一个有参构造。
private int id; public User(int id) { this.id = id; }
其toString我们得重写一下。
-
然后在xml文件里:
<bean id="user" class="com.hyb.www.spring5.User"> <property name="name" value="hyb"></property> <constructor-arg name="id" value="1"></constructor-arg> </bean>
-
其测试类一样。
-
所以,我们总结,xml管理的属性注入有两种方式分别对应java属性赋值的两种方式,set方法,有参构造方法,分别用两个标签对象两个方法赋值。
-
下面了解一下其他注入的方式,命名空间注入:
在xml文件头标签里我们可以看到有这么一个属性:
xmlns="http://www.springframework.org/schema/beans
我们可以起另一个属性与其类似,也放在xml文件夹头标签的属性里。
xmlns:p="http://www.springframework.org/schema/p"
-
然后我们注入属性的时候可以直接代替其set方法对应的注入方法,在bean标签的属性里注入:
<bean id="user" class="com.hyb.www.spring5.User" p:name="hyb"> <!-- <property name="name" value="hyb"></property>--> <constructor-arg name="id" value="1"></constructor-arg> </bean>
-
当然这种方式只能代替set方法,不能代替有参构造,可读性不好,了解一下即可。
特殊注入
-
空值注入:
<bean id="user" class="com.hyb.www.spring5.User" > <property name="name" value=""> <null/> </property> <constructor-arg name="id" value="1"></constructor-arg> </bean>
-
特殊符号注入:
<bean id="user" class="com.hyb.www.spring5.User" > <property name="name" value=""> <null/> </property> <constructor-arg name="id"> <value> <![CDATE[<<1>>]]> </value> </constructor-arg> </bean>
也可以将<>进行转义:<,>
外部Bean注入
-
加入有两个类,一个类需要调用另一个类的方法,那么利用注入的方法改怎么做?
-
首先我们创建两个类,一个类里有一种属性是另一个类。
package com.hyb.www.spring5.Dao.Impl;import com.hyb.www.spring5.Dao.UserDao;public class UserDaoImpl implements UserDao { @Override public void update() { System.out.println("UserDaoImplement被调用了!"); }}
package com.hyb.www.spring5.Service.Impl;import com.hyb.www.spring5.Dao.Impl.UserDaoImpl;import com.hyb.www.spring5.Dao.UserDao;public class ServiceImpl implements Service { private UserDao userDao; public void setUserDao(UserDaoImpl userDao) { this.userDao=userDao; } @Override public void out() { userDao.update(); }}
我们可以看到,ServiceImpl类里要调用DaoImpl类,就要将其当成自己的属性,然后给予set方法。
-
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"> <bean id="userDao" class="com.hyb.www.spring5.Dao.Impl.UserDaoImpl"> </bean> <bean id="service" class="com.hyb.www.spring5.Service.Impl.ServiceImpl"> <property name="userDao" ref="userDao" ></property> </bean></beans>
-
ref属性代表要引入外部哪一个bean标签。
内部bean
-
和前面的内部bean返回来,将外部bean套进去就可以了。
<bean id="service" class="com.hyb.www.spring5.Service.Impl.ServiceImpl"> <property name="userDao"> <bean class="com.hyb.www.spring5.Dao.Impl.UserDaoImpl"> </bean> </property></bean>
级联bean
-
像外部bean和内部bean注入,都是级联bean,还有一种级联bean注入方法,是修改一个类里的属性。
-
这里我们在UserDaoImpl类里加入一个属性name,然后写其set方法,因为我们在ServiceImpl里调用其接口,所以我们要在UserDao中写name的set方法,就相当于其实现方法重写这个setName。
-
下面xml:
<bean id="service" class="com.hyb.www.spring5.Service.Impl.ServiceImpl"> <property name="userDao" ref="userDao"> </property> <property name="userDao.name" value="hyb"></property> </bean> <bean id="userDao" class="com.hyb.www.spring5.Dao.Impl.UserDaoImpl"> </bean>
我们可以看到,userDao是ServiceImpl里的属性,而userDao又是一个类,所以可以改变其name属性。
但其底层是用get方法实现的,所以要在ServiceImpl里实现getUserDao。
也就是说,当类a里有属性类b时,你要改变类b里的属性c,就要先创建b的对象,这里我们用了外部bean进行创建了,创建成功后,在类a里,产生getB方法,才能改变类b里的属性c。(记住,改变属性c,也是要在setC的基础上的。)
注入数组,集合,Map
-
首先创建一个类。
package com.hyb.www.spring5;import java.util.Arrays;import java.util.List;import java.util.Map;public class Student { private String[] course; private List<String> list; private Map<String,Object> map; public void setCourse(String[] course) { this.course = course; } public void setList(List<String> list) { this.list = list; } public void setMap(Map<String, Object> map) { this.map = map; } @Override public String toString() { return "Student{" + "course=" + Arrays.toString(course) + ", list=" + list + ", map=" + map + '}'; }}
-
然后编写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"> <bean id="student" class="com.hyb.www.spring5.Student"><!-- 数组--> <property name="course" > <array> <value>java</value> <value>php</value> </array> </property> <property name="list"> <list> <value>张三</value> <value>李四</value> </list> </property> <property name="map"> <map> <entry key="key1" value="value1"></entry> <entry key="key2" value="value2"></entry> </map> </property> </bean></beans>
-
然后编写测试类
@Testpublic void insertMap(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Collection.xml"); Student student = (Student) context.getBean("student"); System.out.println(student.toString());}
集合中放对象
-
先创建一个类
package com.hyb.www.spring5;public class Course { private String name; public void setName(String name) { this.name = name; } @Override public String toString() { return "Course{" + "name='" + name + '\'' + '}'; }}
-
然后编写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"> <bean id="student" class="com.hyb.www.spring5.Student"><!-- 数组--> <property name="course" > <array> <value>java</value> <value>php</value> </array> </property> <property name="list"> <list> <value>张三</value> <value>李四</value> </list> </property> <property name="map"> <map> <entry key="key1" value="value1"></entry> <entry key="key2" value="value2"></entry> </map> </property> <property name="courseList"> <list> <ref bean="course1"></ref> <ref bean="course2"></ref> </list> </property> </bean> <bean id="course1" class="com.hyb.www.spring5.Course"> <property name="name" value="java"></property> </bean> <bean id="course2" class="com.hyb.www.spring5.Course"> <property name="name" value="python"></property> </bean></beans>
可见,我们存放集合的时候发生了一些不同,必须得先创建出我们要存放进去的对象,然后用外联bean的方式存放到list集合当中。
抽取公共部分
-
以集合为例,该集合只能用于本bean下使用,若是能抽取成公共的,就可减少了很多代码。
-
首先我们还是新建一个类
package com.hyb.www.spring5;import java.util.List;public class Book { private List<String > name; public void setName(List<String> name) { this.name = name; } @Override public String toString() { return "Book{" + "name=" + name + '}'; }}
-
然后是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: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"> <util:list id="bookList"> <value>java</value> <value>php</value> <value>python</value> </util:list> <bean id="book" class="com.hyb.www.spring5.Book"> <property name="name"> <ref bean="bookList"></ref> </property> </bean></beans>
注意,xml这里的头标签bean 里的属性是发生增加了的。
-
然后是测试类
@Testpublic void ListTest(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("List.xml"); Book book= (Book) context.getBean("book"); System.out.println(book.toString());}
工厂bean
-
从前面各类bean中可以看到,是受本身类型限制的,这便是工厂bean的由来。
-
首先我们还是创建一个类,让其实现接口FactoryBean。
package com.hyb.www.spring5.FactoryBean; import com.hyb.www.spring5.bean.Course; import org.springframework.beans.factory.FactoryBean; public class factoryBean implements FactoryBean<Course> { /* * 该方法决定了bean的返回类型 * */ @Override public Course getObject() throws Exception { Course course = new Course(); course.setName("java"); return course; } @Override public Class<?> getObjectType() { return null; } @Override public boolean isSingleton() { return false; } }
该类可以设置泛型,且里面有一个方法决定返回值类型。
-
在xml文件了,我们直接new 这个bean对象就可以了。
<bean id="factoryBean" class="com.hyb.www.spring5.FactoryBean.factoryBean"> </bean>
-
然后测试类我们便根据具体类型具体测试就可以了
@Test public void testFactory(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("FactoryBean.xml"); Course course = context.getBean("factoryBean", Course.class); System.out.println(course); }
bean的作用域
- 作用在一个单实例或多实例中。
单实例
- 前面的都是单实例,想验证是否是单实例,可以在测试类中,进行两次getBean,会发现这些对象的地址都是一样的。这些便是单实例,这是系统默认的,用singleton表示。
多实例
- 我们可以自定义多实例,在xml文件中,创建bean对象的时候,加入scope属性,prototype表示多实例,singleton表示单实例对象,而session和request表示会话和请求。
区别
- 单实例是在配置文件加载的时候就帮我们创建好了对象,只有一个,所以在测试的时候获取到的对象都是相同的。
- 而多实例是在每次getBean的时候才会创建一个对象,所以多实例获取到的对象是不同的。
bean生命周期
生命历程
- 首先是通过无参构造创建bean实例。
- 调用set方法设置bean的属性值。
- 把bean实例传给bean的后置处理器。
- 调用bean初始化方法。
- 后置处理器将bean传出来。
- 获得一个可以使用的bean
- 当容器关闭的时候,调用bean的销毁方法。
- 在上面的生命历程中,要演示出后置处理器和初始化还有销毁方法,必须得自己设置内置属性,而且这个后置处理器可有可无。
举例
-
首先,我们创建一个类
package com.hyb.www.spring5.BeanLife;public class BeanLife { private String name; public BeanLife() { System.out.println("第一步,调用了无参构造"); } public void setName(String name) { this.name = name; System.out.println("第二步,调用了set方法"); } public void init(){ System.out.println("调用了初始化方法"); } public void destroy(){ System.out.println("调用了销毁方法!"); } @Override public String toString() { return "BeanLife{" + "name='" + name + '\'' + '}'; }}
在这个类里,我们写好该有的步骤,都输出一个语句,看看待会执行的步骤。
-
然后编写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"> <bean id="beanLife" class="com.hyb.www.spring5.BeanLife.BeanLife" init-method="init" destroy-method="destroy"> <property name="name" value="生命周期演示"> </property> </bean> <bean id="beanLifePost" class="com.hyb.www.spring5.BeanLife.BeanLifePost"></bean></beans>
在xml中,id=beanLifePost 只是一个普通的bean new一个对象的标签,但这里其所属类继承了后置处理器,所以变成了后置处理标签。
package com.hyb.www.spring5.BeanLife;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanPostProcessor;import org.springframework.lang.Nullable;public class BeanLifePost implements BeanPostProcessor { /* * 为了演示,我们让他实现后置处理器 * 但是这个后置处理器里的两个方法是不能重写的 * 所以我们下面进行手动模拟一下重写 * */ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("进入了后置处理器1"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("进入了后置处理2"); return bean; }}
-
最后,我们编写测试类来测试
@Testpublic void test(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("BeanLife.xml"); BeanLife beanLife = context.getBean("beanLife", BeanLife.class); /* * 下面输出便表示bean已经可以使用了 * */ System.out.println(beanLife.toString()); /* * 该方法表示销毁,若关闭context,将会调用我们自定义的销毁方法 * */ context.close();}
自动装配
-
前面的给属性赋值的过程中,都叫手动装配,还有一种装配,根据名字和类型进行装配。
-
首先我们创建两个类:
package com.hyb.www.spring5.autowire;public class B {}
package com.hyb.www.spring5.autowire;public class A { private B b; public void setB(B b) { this.b = b; } @Override public String toString() { return "A{" + "b=" + b + '}'; }}
-
然后编写xml文件
<bean id="a" class="com.hyb.www.spring5.autowire.A" autowire="byName"><!-- <property name="b" ref="b"></property>--> </bean> <bean id="b" class="com.hyb.www.spring5.autowire.B"> </bean>
我们可以看见,自动装配时,要用autowrie 标签,并且byName表示按照名字装配,这个名字必须规定 要装配的类的id值要和被装配的类里的属性名一样,例如 这里的另一个bean标签中id名字要和类A里的属性B的名字一样。
如果用byType属性,则一定得是一个类只能有一个bean,也就是说,如果用byType 自动装配,只能出现一个关于B的bean标签。
-
注意,在开发中,一般很少用到自动装配,一般都是通过注解反射的方式进行自动装配。
引入外部属性文件
- 这里我们以数据库连接池为例子,之前我们学到过,在进行数据库连接池连接的时候,要将数据库的一些信息都放在一个properties文件包里。
- 那么在这里也是,只不过我们强调用xml文件来解析这个properties文件。
举例
-
这里我们还是以durid来距离,我们先导入druid包。然后写一个properties的配置文件
prop.driverClass=com.mysql.jdbc.Driverprop.url=jdbc:mysql://localhost:3306/hybprop.userName=rootprop.password=15717747056HYB
-
之后我们用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"> <context:property-placeholder location="druidSql.properties"></context:property-placeholder> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"></property> <property name="url" value="${prop.url}"></property> <property name="username" value="${prop.userName}"></property> <property name="password" value="${prop.password}"></property> </bean></beans>
注意其头标签的属性是发生改变了的。
注解管理
- 上面我们学过xml管理来创建对象,接下来学习注解管理创建对象。
- 在Spring中,提供了四大注解来创建对象。
- @Component:用在任何地方
- @Service:建议用在Service层
- @Controller:建议用在web层
- @Respository:建议用在Dao层。
- 以上四个注解任何地方都可使用,只是为了方便,建议用在该用的地方。
创建对象
-
在下载好Spring 框架包中找到jar包spring-aop-5.2.9.RELEASE.jar引入
-
开启组件扫描,让Spring这个容器扫描出哪个类有注解,要进行创建对象。
- 首先要如引入外部属性文件的举例子那般,加入context命名空间
- 然后才可以写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"> <!-- base-package:定义包的路径,确定哪些文件需要被扫描。 具体路径具体被扫描,也可以写上级目录,表示该目录下都被扫描 --> <context:component-scan base-package="com.hyb.www.spring5"></context:component-scan></beans>
-
开启组件扫描后,编写含有注解的类
package com.hyb.www.spring5.Annotation.Service;import org.springframework.stereotype.Component;/** 该value可以不写,默认是类名首字母小写* 而且,这个注解不是唯一的,只要是四大注解里的都行* */@Component(value = "userService")public class UserService { public void out(){ System.out.println("UserService有了Component注解!"); }}
-
然后编写测试类测试,测试类都一样
@Testpublic void test1(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("AnnotationXml.xml"); UserService bean = context.getBean("userService", UserService.class); bean.out();}
组件扫描细化
- 在xml配置组件扫描的时候,可以更加细化,可以精确到有哪个注解的类进行扫描:
<context:component-scan base-package="com.hyb.www.spring5" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/></context:component-scan>
use-default-filter=false表示不进行默认全部扫描。context:include代表 只要有Component类型的注解就进行扫描。
- 还可以精确到哪个注解不进行扫描:
<context:component-scan base-package="com.hyb.www.spring5"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/></context:component-scan>
注解属性注入
- 基于三大注解的属性注入:
- @AutoWired 根据属性类型注入
- @Qualifier 根据属性名称注入
- @Resource 两者兼顾 不是Spring的包下,而是javax包下
- @Value 普通类型注入
- 注意,举例之前首先要开启组件扫描。
Dao
package com.hyb.www.spring5.Annotation.Dao;public interface UserDao { public void add();}
package com.hyb.www.spring5.Annotation.Dao;public interface OrderDao { public void order();}
DaoImpl
package com.hyb.www.spring5.Annotation.Dao.UserDaoImpl;import com.hyb.www.spring5.Annotation.Dao.OrderDao;import org.springframework.stereotype.Repository;@Repository(value = "orderDaoImpl")public class OrderImpl implements OrderDao{ @Override public void order() { System.out.println("OrderDao实现类!"); }}
package com.hyb.www.spring5.Annotation.Dao.UserDaoImpl;
import com.hyb.www.spring5.Annotation.Dao.UserDao;
import org.springframework.stereotype.Repository;
@Repository(value = "userDaoImpl")
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("UserDao实现类!");
}
}
Service
package com.hyb.www.spring5.Annotation.Service;
import com.hyb.www.spring5.Annotation.Dao.OrderDao;
import com.hyb.www.spring5.Annotation.Dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/*
* 该value可以不写,默认是类名首字母小写
* 而且,这个注解不是唯一的,只要是四大注解里的都行
* */
@Service(value = "userService")
public class UserService {
/*
* 自动类型注入,不用set方法,内部给我们封装好了。
* */
@Autowired
/*
* 但是上面的注解有个弊端,若是我们UserDao层下有多个实现类
* 根据类型注入就显得有心无力了(不知道要注入到哪个类型的类),所以需要第二个搭配一起使用
* 那就是名称注入!
* */
@Qualifier(value = "userDaoImpl")
private UserDao userDao;
/*
* 上面两个注解要做到全面得两个结合使用
* 但还有更加好用的,只不过不是Spring官方的
* 而是java本身的javax包下的注解
* 它在隐匿了name属性的时候是类型注入
* 也可以用name属性实现名称注入
* */
@Resource(name="orderDaoImpl")
private OrderDao orderDao;
/*
* Value注解的方便在于省去xml的方式给属性赋值,
* 当然我们也可以按照传统的方法赋值,但是不符合Spring的思想
* */
@Value(value="hyb")
private String name;
public void out(){
System.out.println("UserService有了Component注解!"+name);
userDao.add();
orderDao.order();
}
}
test
@Testpublic void test1(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("AnnotationXml.xml"); UserService bean = context.getBean("userService", UserService.class); bean.out();}
完全注解开发
-
前面的注解属性注入中,尽管xml文件很简洁了,但是还是会有组件扫描开启的标签,完全注解开发便可以省去这个标签。甚至省去xml配置文件。
-
它的思想便是将xml文件的配置信息放在一个类。
package com.hyb.www.spring5.Annotation.Config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; /* * 第一个注解表示完全注解开发 * 第二个注解表示开启组件扫描 * */ @Configuration @ComponentScan(basePackages = {"com.hyb.www.spring5"}) public class AnnotationConfig { }
-
然后我们删除xml文件,进行测试
@Test public void test1(){ ApplicationContext context = new AnnotationConfigApplicationContext(AnnotationConfig.class); UserService bean = context.getBean("userService", UserService.class); bean.out(); }
-
完全注解开发到这里了解一下即可,因为要用相关知识开发的话,会用另一个框架Springboot。
AOP
- 面向切面编程。
- 这是oop的创新,降低了软件各部分功能的耦合度,能更有利于分布式开发。
- 通俗来讲就是让一个软件分不同部分开发维护,降低后期升级的难度。
底层
- AOP的底层是动态代理:
- 第一种是有接口的情况,假如有一个接口,有一个方法login,那么其实现类要实现这个方法。而AOP则是通过代理模式创建一个代理对象来完成和login一样的功能。该名为jdk动态代理。
- 若是没有接口,AOP会创建其子类为代理对象,完成一样的功能。
package com.hyb.www.spring5.Proxy;public interface UserDao { public int add(int a,int b); public String update(String id);}
package com.hyb.www.spring5.Proxy;public class UserDaoImpl implements UserDao{ @Override public int add(int a,int b) { return a+b; } @Override public String update(String id) { return id; }}
package com.hyb.www.spring5.Proxy;import com.hyb.www.spring5.bean.User;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class JDKProxy { public static void main(String[] args) { Class[] interfaces={UserDao.class}; UserDao userDao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserProxy(new UserDaoImpl())); int add = userDao.add(1, 2); System.out.println(add); String update = userDao.update("1"); System.out.println(update); }}class UserProxy implements InvocationHandler{ Object object; public UserProxy(Object o) { this.object=o; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object o = method.invoke(object, args); return o; }}
术语
- 连接点:假设一个类里有多个方法需要被增强,这些需要被增强的方法就被称为连接点。
- 切入点:确实被增强了的方法伪切入点。
- 通知(增强),已经被增强了的方法的增强部分。
- 前置通知
- 后置通知。
- 环绕通知。
- 异常通知。
- 最终通知。
- 切面:把通知应用到切入点的动作叫做切面
准备工作
- 引入一个Spring框架依赖,三个外部依赖。
- 准备切入点表达式,方便知晓对哪个方法进行增强。
- execution([],[],[]) execution(* com.dao.add) 表示所有权限,对com下的dao的add进行增强。若是表示对该包下所有方法和类增强,那么也可以用*代替。
AspectJ 注解
-
创建一个最原始的类,在里面随便写一个需要增强的方法。
package com.hyb.www.spring5.AOP.Annotation;public class User { public void add(){ System.out.println("这个方法将要被增强!"); }}
-
然后写一个增强类,设想这个类里的方法比add方法执行。
package com.hyb.www.spring5.AOP.Annotation;public class UserProxy { public void before(){ 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" 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.hyb.www.spring5.AOP"> </context:component-scan></beans>
注意,这里多了一个名称空间发生改变
-
使用注解创建两个类的对象
在两个类上加上@Component注解。
-
在增强类上加入@Aspect注解
-
在Spring文件中开启生成代理对象。
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
还是在原来的xml文件中、
-
配置不同类型的通知。
在增强类里,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
/** @before注解表示前置通知* */@Before(value = "execution(* com.hyb.www.spring5.AOP.Annotation.User.add())")public void before(){ System.out.println("该方法在add方法之前执行。");}
-
测试类测试
@Testpublic void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("Aop.xml"); User user = context.getBean("user", User.class); user.add();}
-
类似的,还有其他通知,和before类似。
package com.hyb.www.spring5.AOP.Annotation;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Component@Aspectpublic class UserProxy { /* * @before注解表示前置通知 * */ @Before(value = "execution(* com.hyb.www.spring5.AOP.Annotation.User.add())") public void before(){ System.out.println("该方法在add方法之前执行。"); } /* * 后置通知 * */ @After(value ="execution(* com.hyb.www.spring5.AOP.Annotation.User.add())") public void after(){ System.out.println("该方法在add方法之后执行。"); } /* * 异常通知 * 当方法出现异常时候的通知 * */ @AfterThrowing(value = "execution(* com.hyb.www.spring5.AOP.Annotation.User.add())") public void afterThrowing(){ System.out.println("异常通知。"); } /* * 返回通知 * 和后置通知一样,都是在方法之后执行 * 但是返回通知是在方法执行出返回结果之后才通知 * */ @AfterReturning(value = "execution(* com.hyb.www.spring5.AOP.Annotation.User.add())") public void afterReturning(){ System.out.println("这是返回通知!"); } /* * 环绕通知 * 在方法之前之后都会执行 * */ @Around(value = "execution(* com.hyb.www.spring5.AOP.Annotation.User.add())") public void Around(){ System.out.println("这是环绕通知!"); }}
切入点抽取
- 我们可以看到,在上面的AspectJ中,很多注解上都有相同的value属性,也就是切入点,所以这里可进行切入点抽取。
- 我们可以定义一个空方法,在上面放一个名为@pointcut的注解。
/** 切入点抽取* */@Pointcut(value ="execution(* com.hyb.www.spring5.AOP.Annotation.User.add())")public void pointCut(){}
- 在其他注解方法里,我们就可以直接调用此方法就可以了
@Around(value = "pointCut()")
优先级
- 若是有多个注解类,可以在类上加入@Order() 注解,括号里填入数字,数值越小,优先级越高,更先执行。
完全注解开发
- 和上面的完全注解开发都一样,一样用注解来代替xml文件里的不同标签。
@Configuration@ComponentScan(basePackages = {"com.hyb.www.spring5"})@EnableAspectJAutoProxy(proxyTargetClass = true)public class ConfigAnnotation {}
JdbcTemplate
概念
- 是Spring对JDBC的封装,用来更方便的操作数据库。
准备工作
-
在对其操作前,需要引入几个相关的依赖
spring-jdbc-5.2.9.RELEASE.jar
spring-orm-5.2.9.RELEASE.jar
spring-tx-5.2.9.RELEASE.jardruid-1.2.6.jar
mysql-connector-java-8.0.26.jar -
在Spring配置文件中配置连接池
prop.driverClass=com.mysql.cj.jdbc.Driverprop.url=jdbc:mysql://localhost:3306/hybprop.userName=rootprop.password=15717747056HYB
<?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:property-placeholder location="druidSql.properties"></context:property-placeholder> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"></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
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"> </property></bean>
-
创建Service类和Dao类,Service注入Dao,Dao中注入JDBCTemplate对象
<!-- 开启组件扫描--> <context:component-scan base-package="com.hyb.www.spring5.JDBCT"></context:component-scan>
public interface Dao {}
@Repositorypublic class DaoImpl implements Dao{// 注入JDBCT @Autowired private JdbcTemplate jdbcTemplate;}
@org.springframework.stereotype.Servicepublic class Service {// 注入Dao @Autowired private Dao dao;}
添加
-
首先,在上面准备工作的所配置的数据库中随便创建一个表。
-
第二步,根据这个表的属性创建一个类
package com.hyb.www.spring5.JDBCADD;public class UserJ { private String Jid; private String Jname; private String Jstatus; public String getJid() { return Jid; } public void setJid(String jid) { Jid = jid; } public String getJname() { return Jname; } public void setJname(String jname) { Jname = jname; } public String getJstatus() { return Jstatus; } public void setJstatus(String jstatus) { Jstatus = jstatus; } @Override public String toString() { return "UserJ{" + "Jid='" + Jid + '\'' + ", Jname='" + Jname + '\'' + ", Jstatus='" + Jstatus + '\'' + '}'; }}
-
然后在准备工作的Service中创建一个add方法
package com.hyb.www.spring5.JDBCT.Service;import com.hyb.www.spring5.JDBCADD.UserJ;import com.hyb.www.spring5.JDBCT.DAO.Dao;import org.springframework.beans.factory.annotation.Autowired;@org.springframework.stereotype.Servicepublic class Service {// 注入Dao @Autowired private Dao dao; public void add(UserJ userJ){ dao.add(userJ); }}
-
并且在对应Dao层实现操作数据库
public interface Dao { void add(UserJ userJ);}
@Repositorypublic class DaoImpl implements Dao{// 注入JDBCT @Autowired private JdbcTemplate jdbcTemplate; @Override public void add(UserJ userJ) { String sql="insert into userJ(Jid,Jname,Jstatus) values(?,?,?)"; jdbcTemplate.update(sql,userJ.getJid(),userJ.getJname(),userJ.getJstatus()); } }
-
类似的,更新,删除都是用jdbcTemplate 里的update方法。
-
而查询返回某个值的方法,名为queryForObject(sql,Class class) 第二参数为返回值类型,若是要求返回String 就写String.clss 若是Integer类型就写Integer.class
-
查询返回对象:即数据表的一行,queryForObject(sql,RowMapper rowMapper,Object …args) 第二个参数是Spring提供的一个封装返回类型的一个接口,咱们可以用其new BeanPropertyRowMapper<类名>(类名.class) 代替,中间是其实现类,后面是要封装在哪个类中。
-
查询返回集合:query方法,返回List集合,参数和queryForObject一样,但是没有第三个参数
-
批量添加:batchUpdate(sql,List<Object[] o>) 第二个参数代表将多行数据放在List集合中,而Object[] o代表存放一行数据的数组。这个数据的顺序要根据占位符来。
-
批量修改,批量删除:和批量添加一样。就是存放数据的集合一定要按照占位符来存放。
事务
概念
- 是数据库操作的基本单元,是一组操作,并且要求所有操作都得成功,整个事务才会成功。
- 最常见的实例就是转账问题,有转入和转出两种操作,但要求使用事务来实现,不然若是中间出现了问题,会造成不必要的损失。
- 事务有ACID特性,原子性(不可分割,各种操作互相联系),一致性(操作前后数据量不变),持久性(事务提交后数据是持久存在的),和隔离性(多事务操作不会产生影响)。
准备工作
搭建环境
-
第一步:创建一个数据库表,分别有id,name,money属性,并且为其添加两条记录,都有1000块钱。
-
第二步:和JdbcTemplate的准备工作一样
-
第三步:在Dao层 创建两个方法,分别用来操作数据表中,某个人的钱财流动。(即进行数据表的更新操作)
-
第四步:在Service层中,创建一个方法,调用Dao层的这两个方法。
-
本质上气势就是JdbcTemplate中的更新操作,只不过这里有两个更新操作。只要会写sql语句就可以了。
-
但这只是环境的搭建,理论这是可以了,如果转账过程出现了问题,钱财就会莫名的丢失,这肯定是不行,这便是事务诞生的使命。
事务操作
- 根据JavaEE三层结构思想,一般将事务加到Service层上。
编程式
- 用代码实现,基本不用,尤其麻烦
声明式
- 声明式分别注解和xml配置,一般用注解方式进行。
注解方式
-
底层使用AOP原理
-
在Spring事务管理中,为不同框架提供了不同的接口实现类,但这些框架都共用一个接口,叫做事务管理器(PlatformTransactionManager)。
-
所以第一步,我们得先创建事务管理器
<!-- 创建事务管理器,不同jdbc模板,管理不一样--> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- ref为上面的数据库连接部分--> <property name="dataSource" ref="dataSource"></property> </bean>
-
再使用这个事务管理器:
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>
-
在Service类上或者其中的方法上加上事务的注解
- 在其类上,代表所有方法都使用该注解。
- 而配置在方法上,只限定该方法。
@Transactional public class Service{...}
参数配置
-
在@Transactional 里可以填入很多参数
-
propagation :多事务方法之间调用时对事务的管理,称为事务的传播行为。
比如有两个方法,a和b。
事务的传播行为在Spring中一共有七种:
REQUIRED :如果a方法本身有事务,调用了b方法后,b使用当前的a方法里的事务。如果a方法本身没有事务,那么调用了b方法后,会创建一个事务。该行为为propagation默认值。
REQUIRED_NEW :无论a方法有没有事务,调用了b后,都会创建一个新的事务。
-
isolation:事务的隔离级别,是对多事务之间进行隔离,不然会出现脏读,不可重复读,虚读,幻读等影响。
在mysql中,主要有四个隔离级别,每个级别都分别能解决不同的问题。
事务隔离级别 脏读 不可重复读 幻读 读未提交(read-uncommitted) 是 是 是 不可重复读(read-committed) 否 是 是 可重复读(repeatable-read) 否 否 是 串行化(serializable) 否 否 否 -
timeout:超时时间,事务要在一定时间内进行提交,超出时间让事务回滚。默认值为-1,表示不超时,以秒为单位。
-
readOnly:表示只读。默认值是false 表示可以进行一切操作,true表示只能查询。
-
rollbackFor:设置当出现哪些异常时进行实物回滚。
-
noRollbackFor:设置出现哪些异常不进行事务回滚。
-
xml方式(了解)
-
首先,创建事务管理器
<!-- 创建事务管理器,不同jdbc模板,管理不一样--> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- ref为上面的数据库连接部分--> <property name="dataSource" ref="dataSource"></property> </bean>
-
配置通知:
<tx:advice id="txAdvice"><!-- 配置事务参数,标签属性可以配置其隔离级别还有其他--> <tx:attributes> <tx:method name="方法名" 其他参数配置/> </tx:attributes> </tx:advice>
-
配置切入点和切面
<!--配置切入点和切面--><aop:config> <!--配置切入点--> <aop:pointcut id="p" expression="execution表达式"/> <!--配置切面,配置到具体方法上--> <aop:advisor advice-ref="txAdvice" pointcut-ref="p"></aop:advisor></aop:config>
完全注解开发
- 创建配置类
package com.hyb.www.spring5.JDBCTransaction;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration //配置类
@ComponentScan(basePackages = "com.hyb.www.spring5")
//开启事务
@EnableTransactionManagement
public class txConfig {
// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
// prop.driverClass=com.mysql.cj.jdbc.Driver
// prop.url=jdbc:mysql://localhost:3306/hyb
// prop.userName=root
// prop.password=15717747056HYB
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://localhost:3306/hyb");
druidDataSource.setUsername("root");
druidDataSource.setPassword("15717747056HYB");
return druidDataSource;
}
// 创建JdbcTemplate 对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
/*
* 下面的调用可以直接调用上面的方法,但是这样效率不太好,因为每次set都会创建一次
* 因为上面调用一次就创建好了,且IOC容器了已经存在了这个DataSource对象,所以我们直接传参数过来就可以了。
* */
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSourde){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSourde);
return dataSourceTransactionManager;
}
}
Spring5 新功能
日志
-
自带了通用的日志封装,移除了日志封装Log4jConfigListener ,并用Log4j2进行了代替。
-
使用前先导入四个jar包依赖
log4j-1.2-api-2.14.1.jar
log4j-core-2.14.1.jar
log4j-slf4j18-impl-2.14.1.jar slf4j-api.jar
-
创建名字为log4j2的xml文件
<?xml version="1.0" encoding="UTF-8"?> <!-- 日志级别: OFF>FATAL>ERROR>WARN>INFO>DEBUG>TRACE(可以看到日志内部各种详细输出)>ALL... --> <Configuration status="INFO"> <Appenders> <!--输出日志信息到控制台--> <console name="Console" target="SYSTEM_OUT"> <!--控制日志输出的格式--> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"> </PatternLayout> </console> </Appenders> <Loggers> <!--root用于指定根目录,如果没有Loggers,使用root输出--> <root level="info"> <appender-ref ref="Console"> </appender-ref> </root> </Loggers> </Configuration>
-
@NUllable注解
- 可以使用在方法,属性,参数上,表示可以为空。
函数式风格
-
支持Lambda表达式
-
我们先创建一个User类,然后创建一个test
package com.hyb.www.spring5.newSpring5; import org.junit.Test; import org.springframework.context.support.GenericApplicationContext; public class test { @Test public void testGenericApplicationContext(){ GenericApplicationContext context = new GenericApplicationContext(); // 清空内容 context.refresh(); // 注册,使用Lambda表达式 context.registerBean("user",User.class, User::new); /* * getBean方法里的user是上面注册的时候设置的。 * 若是上面注册的时候不设置,下面的name就要是类的包路径 * */ Object user = context.getBean("user"); System.out.println(user); } }
Junit4
-
主要对Junit4进行整合,首先要引入spring-test-5.2.9.RELEASE.jar
-
创建测试类,用注解完成测试,这里我们测试前面事务的Service
package com.hyb.www.spring5.Test; import com.hyb.www.spring5.JDBCTransaction.Service.Service; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; //注解测试固定注解 @RunWith(SpringJUnit4ClassRunner.class) //引入配置文件 @ContextConfiguration("classpath:JDBCT.xml") public class Annotation_Test { // 注入对象 @Autowired private Service service; @Test public void test(){ service.Account(); } }
Junit5
- 使用之前先导入Junit5的包,其他和Junit4一样,但是记住创建@Test的时候,要穿件Junit5的
package com.hyb.www.spring5.Test;
import com.hyb.www.spring5.JDBCTransaction.Service.Service;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:JDBCT.xml")
public class Junit5Test {
@Autowired
private Service service;
@Test
public void test(){
service.Account();
}
}
整合
-
两个测试类上的两个注解,可以用一个注解来整合
@SpringJunitConfig(location="xml文件路径")
SpringWebFlux
特点
- 是Spring5新出的模块,用于web,功能SpringMVC类似,但是底层区别很大,是应对响应式编程而出现的框架。
- 使用传统的web框架,比如SpringMVC,这些基于Servlet容器,而WebFlux是一种异步非阻塞的框架,异步非阻塞的框架在Servlet3.1以后才支持,核心是基于Reactor的相关API实现的。
- 非阻塞式:在有限的资源下,提高系统吞吐量和伸缩性,以Reactor为基础实现响应式编程。
- 函数式编程:基于Java8,可以使用函数式编程,即Lambda表达式。
和SpringMVC进行比较
- 两个框架都可以使用注解方式,都运行在Tomcat服务器中,
- SpringMVC采用命令式编程。
响应式编程 (RP)
- 电子表格就是一种响应式编程,当一个表格的值发生变化,其他有关联的表格会立马发生变化。
- 在java8 中提供了一个叫观察者的模式,其有两个实现类:Observer,Observable ,即观察其他是否发生变化,自己才是否反映。
- 而到了java9,这两个类被Flow 取代,出现Reactor为基础的响应式编程。
Reactor
- 响应式编程操作,Reactor是满足Reactive规范的框架。
- 在Reactor中有两个核心类:Mono,Flux ,其都实现了接口Publisher,提供了丰富的操作符。
- Flux实现发布者,返回多个元素,而Mono返回一个元素 或者 0个元素。两者都是数据流的发布者,都可以发出三种信号:元素值,异常信号,完成信号,后两个信号为终止信号,用于告诉订阅者数据流结束了。异常信号会终止,也会将错误信息传递给订阅者。
- 三种信号的特点:错误信号和完成信号都代表终止,不能共存。如果没有发送任何元素值,而是直接发送信号,代表空流。而如果没有错误信号,也没有完成信号,则代表无限流。
- 操作符:
- Map 将元素映射成新的元素
- flatMap 元素映射成流
举例
-
创建一个SpringBoot项目,new Project->Spring initializr->next 选择java8版本。
-
创建完毕后,修改pom.xml文件
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
其中version标签里的值,变为以上。
<dependencies> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-core</artifactId> <version>3.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
在上面依赖标签中,增加一个新的以来,第一个dependcy为新的依赖。
若是版本标红原因,请在setting里,选择File | Settings | Build, Execution, Deployment | Build Tools | Maven 勾选always update snapshots 然后返回页面,点击窗口右栏的Maven,先点击clean,再点击install,然后左上角的回旋按钮,刷新一段时间,退出。
-
创建完xml文件,请看一个类
package com.hyb.www.demoreactor; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.lang.reflect.Array; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class TestReactor { public static void main(String[] args) { /* *下面只是发送,并没有订阅,当运行程序,只能输出一段日志 * */ /* * Flux返回多个元素 * */ Flux<Integer> flux = Flux.just(1, 2, 3, 4); /* * 返回单个元素 * */ Mono<Integer> mono = Mono.just(1); /* * 只有订阅了,数据流才会发出 * */ Flux.just("a","b").subscribe(System.out::println); Mono.just("ahahha").subscribe(System.out::println); // a // b // ahahha /* * 还有其他方法 * */ /*数组*/ Integer[] integer= {1,2,3,4}; Flux.fromArray(integer); /*集合*/ List asList = Arrays.asList(integer); Flux.fromIterable(asList); /*流*/ Stream stream = asList.stream(); Flux.fromStream(stream); Flux.error(new Exception("错误信号")); } }
基于注解编程模型
-
首先,创建一个Springboot工程,然后,配置xml文件信息,配置与举例相似,只不过不要了加入依赖那步骤要换成另一个
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
上面的要改成下面的。
同样的,若是报红,可以像举例那样去解决。
-
在application.properties下配置端口号
server.port=8081
-
创建一个包,entity,里面有一个类
package com.hyb.www.demoreactor.entity; public class User { private Integer id; private String name; public User() { } public User(Integer id, String name) { this.id = id; this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + '}'; } }
-
再创建一个包,service 在本包下创建一个service 接口
package com.hyb.www.demoreactor.service; import com.hyb.www.demoreactor.entity.User; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public interface UserService { /* * 根据id查询用户,返回一个User * 可以使用Mono,返回一个或者0个元素 * */ Mono<User> queryUserById(Integer id); /* * 查询所有用户,返回多个User * 可以使用Flux * */ Flux<User> queryAllUser(); /* * 添加一个用户 * 可以使用MOno,传入一个对象 * */ Mono<Void> addUser(Mono<User> user); }
-
然后在service包下创建一个ServiceImpl包,在这个包里创建一个接口实现类
package com.hyb.www.demoreactor.service.Impl; import com.hyb.www.demoreactor.entity.User; import com.hyb.www.demoreactor.service.UserService; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.HashMap; import java.util.Map; public class UserServiceImpl implements UserService { /* * 这里应该操作数据库的 * 但是为了方便,我们先在User里放一些固定的数据 * 而这些固定的数据,我们可以通过其空参构造器放入 * */ private final Map<Integer,User> map=new HashMap<>(); public UserServiceImpl() { this.map.put(1,new User(1,"hyb")); this.map.put(2,new User(2,"zyl")); this.map.put(3,new User(3,"hlf")); } @Override public Mono<User> queryUserById(Integer id) { // 该方法表示just返回可以为空 // 参数代表这个类里的map属性里的通过id得到的对象 return Mono.justOrEmpty(this.map.get(id)); } @Override public Flux<User> queryAllUser() { return Flux.fromIterable(this.map.values()); } @Override public Mono<Void> addUser(Mono<User> user) { /* * doOnNext代表遍历user * 然后用Lambda表达式将数据传入map中 * 最后的函数运算,代表设置流为空,让流停止 * 不然前面说过,会变成无限流 * */ return user.doOnNext(person->{ Integer id=map.size(); map.put(id,person); }).thenEmpty(Mono.empty()); } }
-
然后创建一个controller包,里面创建一个包
package com.hyb.www.demoreactor.controller; import com.hyb.www.demoreactor.entity.User; import com.hyb.www.demoreactor.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @RestController public class UserController { // 属性注入 @Autowired private UserService userService; // 添加 /* * 这个注解里,代表名字为user 传入id属性 * 而在参数的注解里,会根据注解自动获取id属性 * */ @GetMapping("/user/{id}") public Mono<User> getUser(@PathVariable Integer id){ return userService.queryUserById(id); } // 查询所有 @GetMapping("/user") public Flux<User> getUsers(){ return userService.queryAllUser(); } // 添加一个 /* * 参数里是SpringMVC里的内容 * 代表获取一个JSon的注解 * */ @PostMapping("/saveuser") public Mono<Void> saveUser(@RequestBody User user){ Mono<User> useMono = Mono.just(user); return userService.addUser(useMono); } }
-
测试
在这个工程项目下有一个这样的类,我们写好代码后,直接启动该类就可以了,DemoreactorApplication。
启动成功后,控制台会显示出我们端口号是8081,这是之前设置的端口号。接下来,我们到浏览器,输入localhost:8081/user/1
代表查询id为1的用户,而localhost:8081/user 代表查询所有用户
同样的,若是版本标红原因或者出错,请在setting里,选择File | Settings | Build, Execution, Deployment | Build Tools | Maven 勾选always update snapshots 然后返回页面,点击窗口右栏的Maven,先点击clean,再点击install,然后左上角的回旋按钮,刷新一段时间,退出。
基于函数式编程模型
要求
- 需要自己初始化服务器。
- 有两个核心接口,一个是RouterFunction 用来请求转发,一个是HandlerFunction用来做响应。
- 其对应Servlet中,对于request和response有两个类型,ServerRequest,ServerResponse。
步骤
-
首先,在前面基于注解编程模型,我们删除controller,其余保留
-
然后新建一个handler包,里面建一个类,名为UserHandler
package com.hyb.www.demoreactor.handler; import com.hyb.www.demoreactor.entity.User; import com.hyb.www.demoreactor.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; public class UserHandler { private final UserService userService; public UserHandler(UserService userService) { this.userService = userService; } // 根据id查询 public Mono<ServerResponse> getUserById(ServerRequest serverRequest){ // 获取id String id = serverRequest.pathVariable("id"); // 将id转化为int类型 Integer i = Integer.valueOf(id); // 空值判断 Mono<ServerResponse> noFound = ServerResponse.notFound().build(); // 根据id查询 Mono<User> userMono = this.userService.queryUserById(i); /* * 下面是将元素映射成流返回 * 注意,flatMap里是Lambda表达式, * ok表示操作成功,然后设置返回类型是Json类型 * body表示需要转换的数据 * 最后的表示空值判断 * */ // userMono.flatMap(person->ServerResponse.ok().bodyValue(person)).switchIfEmpty(noFound); return userMono.flatMap(person->ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userMono,User.class)).switchIfEmpty(noFound); } // 查询所有 public Mono<ServerResponse> getUsers(ServerRequest serverRequest){ // 获取User Flux<User> users = this.userService.queryAllUser(); return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class); } // 添加用户 public Mono<ServerResponse> addUser(ServerRequest serverRequest){ // 调用传来的数据转为Mono类型,因为我们传来的数据是一个,所有要转换成Mono类型 Mono<User> userMono = serverRequest.bodyToMono(User.class); // build表示订阅,只有订阅了,才会有响应,而有了响应,才会添加成功 return ServerResponse.ok().build(this.userService.addUser(userMono)); } }
-
该类如同controller类一般,实现其中方法。
-
其次,我们在这三个包的上一级,创建一个类Service
package com.hyb.www.demoreactor; import com.hyb.www.demoreactor.handler.UserHandler; import com.hyb.www.demoreactor.service.Impl.UserServiceImpl; import com.hyb.www.demoreactor.service.UserService; 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.server.RequestPredicate; 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.http.MediaType.APPLICATION_JSON; 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; public class Service { // 创建路由 public RouterFunction<ServerResponse> routerFunction(){ UserService userService = new UserServiceImpl(); UserHandler userHandler = new UserHandler(userService); // 设置路由 /* * 设置路由为链式编程 * GET为请求方式,里面是请求地址,将id放进去 * accept为接受方式,为JSON形式 * 后面是要调用查询方法的Lambda表达式 * 最后andRoute是链式编程 * */ return RouterFunctions.route( GET("/user/{id}").and(accept(APPLICATION_JSON)), userHandler::getUserById).andRoute(GET("/user").and(accept(APPLICATION_JSON)),userHandler::getUsers); } // 创建服务器完成适配 public void CreateReactorServer(){ // 获取路由 RouterFunction<ServerResponse> route = routerFunction(); // 转发到http协议 HttpHandler httpHandler = toHttpHandler(route); // 获取适配器对象 ReactorHttpHandlerAdapter reactorHttpHandlerAdapter = new ReactorHttpHandlerAdapter(httpHandler); // 创建服务器 HttpServer httpServer = HttpServer.create(); // 完成适配 httpServer.handle(reactorHttpHandlerAdapter).bindNow(); } // 最终调用 public static void main(String[] args) throws IOException { Service service = new Service(); service.CreateReactorServer(); System.out.println("enter out"); System.in.read(); } }
-
启动main方法后,控制台最后会出现一段英文
找到 L:/0:0:0:0:0:0:0:1:61970 最后面的几个数字就是你的端口号,在浏览器访问的时候可以localhost:端口号/ 浏览方式和基于注解编程模型一样,同样的,若是出现错误,首先用老方法解决一下,还是不行就是程序错误。记住,端口号是L开头才有,其余的不是。
使用WebClient浏览
- 前面我们要测试的话,必须去浏览器浏览,但我们也可以在控制台浏览,但浏览之前,我们得首先启动,获取端口号。
package com.hyb.www.demoreactor;
import com.hyb.www.demoreactor.entity.User;
import com.hyb.www.demoreactor.service.Impl.UserServiceImpl;
import com.hyb.www.demoreactor.service.UserService;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
public class Client {
public static void main(String[] args) {
// 创建一个webClient
WebClient webClient=WebClient.create("http://localhost:51401");
// 根据id查询
/*
* get为get请求,uri为请求地址
* accept接受类型为JSon
* 后面将其初始化
* 然后将类转换为Mono
* 最后是订阅
* */
User user = webClient.get().uri("/user/{id}", "1").accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block();
// 查询所有
/*
* buffer()为缓冲数据流
* doOnNext为遍历输出
* blockFirst也类似订阅
* */
Flux<User> userFlux = webClient.get().uri("/user").accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);
userFlux.map(User::toString).buffer().doOnNext(System.out::println).blockFirst();
}
}