Spring_001_框架概述

一、概述

  1. Spring是轻量级的开源的JavaEE框架,
  2. 可以解决企业应用开发的复杂性
  3. 核心部分是IOC和AOP
    3.1 IOC:控制反转,把创建对象的过程交给Spring管理。
    3.2 AOP:面向切面,不修改原代码的情况下进行功能增强。
  4. 特点
    4.1 方便解耦,简化开发
    4.2 AOP编程支持
    4.3 方便程序测试
    4.4 方便和其他框架进行整合
    4.5 方便进行事务操作
    4.6 降低API的使用难度

二、入门案例

2.1 Spring模块

image.png
最基本的Spring程序需要Bean、Core、Context、Expression和commons-logging五个jar包

2.2 Hello World

通过Idea创建一个Spring5程序
编写一个实体类
package site.javaee.spring5;

public class User {
    public void add(){
        System.out.println("add user");
    }
}

编写Spring配置文件

在src目录下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--    配置User类对象-->
    <bean id="user" class="site.javaee.spring5.User"></bean>
</beans>
测试
public class TestSpring5 {
    public static void main(String[] args) {
        testAdd();
    }

    public static void testAdd(){
        //1 加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");

        //2 获取配置创建的对象
        User user = context.getBean("user", User.class);

        System.out.println(user);
        user.add();
    }
}

/*
site.javaee.spring5.User@5cb9f472
add user
*/

Spring_002_IOC容器

一、什么是IOC

  1. 控制反转,把对象的创建和对象之间的调用过程交给Spring管理。
  2. 使用IOC目的:为了降低耦合度,对象之间没有直接关联
  3. 上篇中的入门案例就是IOC实现。

二、图解IOC

原始方式
image.png
工厂模式
image.png
IOC 模式
image.png
IOC需要用到xml解析+反射+工厂模式

三、IOC接口(BeanFactory)

  1. IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
  2. Spring提供IOC实现两种方式:BeanFactory和ApplicationContext
    2.1 BeanFactory是IOC容器基本实现,是Spring中内部使用的接口,一般不适用于开发人员。BeanFactory是懒加载机制,加载配置文件时不创建对象,当获取使用时才创建。
    2.2 ApplicationContext是BeanFactory接口的子接口,提供更强大的功能,一般由开发人员使用。在加载时就会创建配置文件中的对象。
    image.png

四、IOC操作Bean管理(基于xml)

4.1 什么是Bean管理

Bean管理指两个操作,创建对象+注入属性

创建对象

Spring创建对象,使用bean标签,标签里添加对应属性,就可以实现对象创建。

  1. id属性,类的唯一标识名。
  2. class属性,类全路径。
  3. name属性,作用与id类似,name是早期的属性,值可以支持符号,现在主要用id属性。
  4. 创建对象时,默认执行无参构造方法。
Spring注入属性
  1. DI:依赖注入,就是注入属性。DI是IOC的一种具体实现
  2. set注入
  3. 有参构造注入
public class Book {
    public Book(){}

    private String name;
    private String author;

    //set方法注入
    public void setName(String name) {
        this.name = name;
    }
    public void setAuthor(String author) {
        this.author = author;
    }

    //有参构造注入
    public Book(String name, String author) {
        this.name = name;
        this.author = author;
    }
	
	@Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", author='" + author + '\'' +
                '}';
    }
}
     <!--    set注入-->
    <bean id="book" class="site.javaee.spring5.Book">
        <property name="name" value="Java"></property>
        <property name="author" value="Susan"></property>
    </bean>
    <!--    有参构造注入-->
    <bean id="book2" class="site.javaee.spring5.Book">
        <constructor-arg name="name" value="C++"></constructor-arg>
        <constructor-arg name="author" value="Tony"></constructor-arg>
    </bean>
public class TestSpring5 {
    public static void main(String[] args) {
        //testAdd();
        testBook();
    }

    public static void testBook(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        Book book = conte `xt.getBean("book",Book.class);
        Book book2 = context.getBean("book2",Book.class);

        System.out.println(book);
        System.out.println(book2);
    }
}
/*
Book{name='Java', author='Susan'}
Book{name='C++', author='Tony'} 
*/
xml注入其他类型属性

null值

<!--    null值注入-->
<bean id="book" class="site.javaee.spring5.Book">
	<property name="name" value="Java"></property>
	<property name="author">
		<null></null>
	</property> 
</bean>

3.2 值含特殊符号

<!--    特殊注入-->
<bean id="book" class="site.javae e.spring5.Book">
	<!--        转义-->
	<property name="name" value="&lt;&lt;南京&gt;&gt;"></property>
	<!--        CDATA-->
	<property name="author">
		<value>
			<![CDATA[<<南京>>]]>
		</value>
	</property>
</bean>
注入属性-外部bean

创建三个类,UserDao,UserDaoImpl,UserService

public class UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void add(){
        System.out.println("service add");
        userDao.update();
    }
}

public interface UserDao {
    public void update();
}

public class UserDaoImpl implements UserDao{
    @Override
    public void update() {
        System.out.println("dao update");
    }
}

xml注入

    <bean id="userDao" class="site.javaee.spring5.dao.UserDaoImpl"></bean>
    <bean id="userService" class="site.javaee.spring5.service.UserService">
        <property name="userDao" ref="userDao"></property>
    </bean>

测试

public class TestSpring5 {
    public static void main(String[] args) {
        testUser();
    }

    private static void testUser() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        UserService userService = context.getBean("userService",UserService.class);
        userService.add();
    }
}
/*
service add
dao update
*/
注入属性-内部bean

创建Dept部门类、Emp员工类,一个部门对应多个员工,一个员工对应一个部门

public class Dept {
    private String name;
    private ArrayList<Emp> empList;

    public void setName(String name) {
        this.name = name;
    }

    public void setEmpList(ArrayList<Emp> empList) {
        this.empList = empList;
    }

    @Override
    public String toString() {
        return "Dept{" +
                "name='" + name + '\'' +
                ", empList=" + empList +
                '}';
    }
}

public class Emp {
    private String name;
    private String gender;
    private Dept dept;

    public void setName(String name) {
        this.name = name;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public void setDept(Dept dept) {
        this.dept = dept;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", dept=" + dept +
                '}';
    }
}

配置xml
内部Bean和Java的匿名内部类相似,既没有名字,也不能被其他Bean引用,只能在声明处为外部Bean提供实例注入

    <bean id="emp" class="site.javaee.spring5.Emp">
        <property name="name" value="Trump"></property>
        <property name="gender" value="男"></property>
        <property name="dept" >
            <bean id="dept" class="site.javaee.spring5.Dept">
                <property name="name" value="IT"></property>
            </bean>
        </property>
    </bean>

测试

public class TestSpring5 {
    public static void main(String[] args) {
        testEmp();
    }

    private static void testEmp() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        Emp emp = context.getBean("emp",Emp.class);
        System.out.println(emp);
    }
}
/*
Emp{name='Trump', gender='男', dept=Dept{name='IT', empList=null}}
*/
注入属性-级联bean(外部hean)

emp的dept变量需要有get方法

    <bean id="dept" class="site.javaee.spring5.Dept"></bean>
    <bean id="emp" class="site.javaee.spring5.Emp">
        <property name="name" value="Trump"></property>
        <property name="gender" value="男"></property>
        <property name="dept" ref="dept"></property>
        <property name="dept.name" value="IT" ></property>
    </bean>
注入属性-集合类型
public class Stu {
    private String[] courses;
    private List<String> list;
    private Map<String,String> maps;
    private Set<String> sets;

    public void setCourses(String[] courses) {
        this.courses = courses;
    }
    public void setList(List<String> list) {
        this.list = list;
    }

    public void setMaps(Map<String, String> maps) {
        this.maps = maps;
    }

    public void setSets(Set<String> sets) {
        this.sets = sets;
    }

    @Override
    public String toString() {
        return "Stu{" +
                "courses=" + Arrays.toString(courses) +
                ", list=" + list +
                ", maps=" + maps +
                ", sets=" + sets +
                '}';
    }
}
    <bean id="stu" class="site.javaee.spring5.collect.Stu">
        <property name="courses">
            <array>
                <value>Java</value>
                <value>MySQL</value>
            </array>
        </property>
        <property name="list">
            <list>
                <value>ZhangSan</value>
                <value>LiSi</value>
            </list>
        </property>
        <property name="maps">
            <map>
                <entry key="Java" value="80"></entry>
                <entry key="MySQL" value="90"></entry>
            </map>
        </property>
        <property name="sets">
            <set>
                <value>Go</value>
                <value>Rust</value>
            </set>
        </property>
    </bean>
public class TestSpring5 {
    public static void main(String[] args) {
        testCollect();
    }

    private static void testCollect() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        Stu stu = context.getBean("stu", Stu.class);
        System.out.println(stu);
    }
}
/*
Stu{courses=[Java, MySQL], list=[ZhangSan, LiSi], maps={Java=80, MySQL=90}, sets=[Go, Rust]}
*/

Spring支持级联属性的配置,Spring没有对级联属性的层级数进行限制,只要配置的Bean拥有对应于级联属性的类结构,就可以配置任意层级的级联属性

4.2 FactoryBean

Spring有两种类型的Bean,一种是普通Bean,另一种是工厂Bean(FactoryBean)。

  1. 普通Bean:在xml文件中定义的类型就是返回类型。
  2. 工厂Bean:在xml文件中定义的类型和返回的类型不一样。
实例

创建一个工厂Bean,实现接口FactoryBean,在实现的方法中定义实际要返回的Bean类型。

public class MyBean implements FactoryBean<Course> {
    //定义返回Bean
    @Override
    public Course getObject() throws Exception {
        Course course = new Course();
        course.setName("Java");
        return course;
    }

    @Override
    public Class<?> getObjectType() {
        return Course.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}


public class Course {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Course{" +
                "name='" + name + '\'' +
                '}';
    }
}
<bean id="myBean" class="site.javaee.spring5.factory.MyBean"></bean>
public class TestSpring5 {
    public static void main(String[] args) {
        testFactory();
    }

    private static void testFactory() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        Course course = context.getBean("myBean", Course.class);
        System.out.println(course);
    }
}
/*
Course{name='Java'}
*/

4.3 bean的作用域

在Spring里面,设置创建的bean实例是单实例还是多实例。默认情况下创建的bean是一个单实例对象。

    public static void testBook() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        Book book = context.getBean("book", Book.class);
        Book book2 = context.getBean("book", Book.class);
        System.out.println(book == book2); //true
    }

如果要设置多实例,需要在xml中的bean增加scope属性

<bean id="user" class="site.javaee.spring5.User" scope="prototype"></bean>

当设置成prototype时,加载spring文件时并不创建对象,而是每次调用getBean方法时创建一次实例对象。
还可以设置session和request。

4.4 bean生命周期

生命周期指对象从创建到销毁的过程。

  1. 通过构造器创建bean实例(无参数构造)
  2. 为bean的属性设置值和对其他bean引用(调用set方法)
  3. 调用bean的初始化方法(需要进行配置)
  4. bean可以使用了(对象获取到了)
  5. 当容器关闭的时候,调用bean的销毁方法(需要进行配置)
演示
public class Orders {
    private String billNo;

    public Orders() {
        System.out.println("1. 通过构造器创建bean实例(无参数构造)");
    }

    public void setBillNo(String billNo) {
        this.billNo = billNo;
        System.out.println("2. 为bean的属性设置值和对其他bean引用(调用set方法)");
    }

    public void init(){
        System.out.println("3. 调用bean的初始化方法(需要进行配置)");
    }

    public void use(){
        System.out.println("4. bean可以使用了(对象获取到了)");
    }

    public void destroy(){
        System.out.println("5. 当容器关闭的时候,调用bean的销毁方法(需要进行配置)");
    }
}
    <bean id="orders" class="site.javaee.spring5.cycle.Orders" init-method="init" destroy-method="destroy">
        <property name="billNo" value="b1238943233"></property>
    </bean>
    private static void testOrders() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        Orders orders = context.getBean("orders", Orders.class);
        orders.use();
        ((ClassPathXmlApplicationContext)context).close();
    }
	
	/*
1. 通过构造器创建bean实例(无参数构造)
2. 为bean的属性设置值和对其他bean引用(调用set方法)
3. 调用bean的初始化方法(需要进行配置)
4. bean可以使用了(对象获取到了)
5. 当容器关闭的时候,调用bean的销毁方法(需要进行配置)
	*/
完整的生命周期

bean的声明周期除了上面的5个,还有2个后置处理器方法,需要实现接口BeanPostProcessor。

  1. 通过构造器创建bean实例(无参数构造)
  2. 为bean的属性设置值和对其他bean引用(调用set方法)
  3. 在初始化方法之前,把bean传递给后置处理器的方法
  4. 调用bean的初始化方法(需要进行配置)
  5. 在初始化方法之后,把bean传递给后置处理器的方法
  6. bean可以使用了(对象获取到了)
  7. 当容器关闭的时候,调用bean的销毁方法(需要进行配置)
public class BeanPost implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("3. 在初始化方法之前,把bean传递给后置处理器的方法");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("5. 在初始化方法之后,把bean传递给后置处理器的方法");
        return bean;
    }
}
    <bean id="beanPost" class="site.javaee.spring5.cycle.BeanPost"></bean>

后置处理器默认对每个bean都生效

3. 在初始化方法之前,把bean传递给后置处理器的方法
5. 在初始化方法之后,把bean传递给后置处理器的方法
1. 通过构造器创建bean实例(无参数构造)
2. 为bean的属性设置值和对其他bean引用(调用set方法)
3. 在初始化方法之前,把bean传递给后置处理器的方法
4. 调用bean的初始化方法(需要进行配置)
5. 在初始化方法之后,把bean传递给后置处理器的方法
6. bean可以使用了(对象获取到了)
7. 当容器关闭的时候,调用bean的销毁方法(需要进行配置)

4.5 自动装配

根据指定装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入。
通过autowire属性实现自动装配

  1. byName: 根据属性名称注入 (bean的id和类的属性名称要一致)
  2. byType: 根据属性类型注入 (xml里相同类型的bean只能有一次,否则会报错)
<bean id="dept" class="site.javaee.spring5.autowire.Dept" autowire="byName"></bean>
<bean id="emp" class="site.javaee.spring5.autowire.Emp" autowire="byName"></bean>

基本上不会用xml来做自动装配,一般用注解来实现。

4.6 引入外部属性文件

实例,配置数据库信息

直接配置
    <!--    直接配置德鲁伊连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://rm-wz90pb9deb3muoyp5do.mysql.rds.aliyuncs.com:3306/xe"/>
        <property name="username" value="root"/>
        <property name="password" value="aliyunroot"/>
    </bean>
引入属性文件

编写属性文件jdbc.properties

jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://rm-wz90pb9deb3muoyp5do.mysql.rds.aliyuncs.com:3306/xe
jdbc.username=root
jdbc.password=aliyunroot

配置到xml中,需要引入名称空间context引入

<?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="classpath:jdbc.properties"></context:property-placeholder>
    <!--    配置德鲁伊连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClass}"></property>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

五、IOC操作Bean管理(基于注解)

5.1 注解

  1. 注解是代码中一些特殊的标记格式:@注解名称(属性名称=属性值,属性名称=属性值)
  2. 注解可以作用在类、属性、方法上
  3. 使用注解的目的是简化xml配置

5.2 基于注解创建bean对象

以下注解功能一样,都可以用来创建bean实例,使用不同的注解是为了提高可读性

  1. @Component
  2. @Controller
  3. @Service
  4. @Repository

使用注解功能需要引入spring-aop的依赖,并开启组件扫描

<?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="site.javaee.spring5.bean,site.javaee.spring5.controller"></context:component-scan>
</beans>
@Service(value = "userService")
public class UserService {
    public void add(){
        System.out.println("user service...");
    }
}

5.3 基于注解注入bean属性

  1. @AutoWire: 根据属性类型进行自动装配
  2. @Qualifier:根据属性名称进行注入
  3. @Resource:可以根据类型/名称注入,该类不是spring包下的,是javax包下的
  4. @Value:注入普通类型属性

#####实例

//value属性值默认是类名称的首字母小写
@Service(value = "userService")
public class UserService {
    //根据类型注入
    //@Autowired

    //根据名称注入,适用于一个接口多个实现类的场景
    //@Qualifier("userDao")
    //@Autowired

    //@Resource //默认是类型注入
    @Resource(name = "userDao")//根据名称注入
    private UserDao userDao;

    @Value(value = "Tony")
    private String name;
	
    public void add() {
        System.out.println("user service...");
        userDao.add();
    }
}

@Repository
public class UserDao {
    public void add(){
        System.out.println("user dao ...");
    }
}
···

```java
public class TestSpring5 {
    public static void main(String[] args) {
        testAdd();
    }

    public static void testAdd(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}
/*
user service...
user dao ...
*/

5.4 纯注解开发

创建配置类,替代xml配置文件

package site.javaee.spring5.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

//开启组件扫描
@ComponentScan(basePackages = "site.javaee.spring5")
//作为配置类,替代xml配置文件
@Configuration
public class SpringConfig {
}

测试

public class TestSpring5 {
    public static void main(String[] args) {
//        testAdd();
        testDev();
    }

    private static void testDev() {
		//加载配置类
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}
实例

将xml改为java

<!--    开启组件扫描,多个包用,隔开-->
    <context:component-scan base-package="site.javaee.spring5"></context:component-scan>

实际中使用注解开发的场景是springboot

Spring_003_AOP

一、AOP概述

  1. AOP(Aspect Oriented Programming)面向切面编程。通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是函数式编程的一种衍生范型。
  2. 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  3. 主要意图
    将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

即不通过修改原代码方式,在主干功能上添加新功能。

二、AOP底层原理

AOP底层使用动态代理

  1. 有接口的情况使用JDK动态代理
    image.png
  2. 没接口的情况使用CGLIB动态代理
    image.png

三、动态代理

3.1 JDK动态代理

UserDao和UserDaoImpl

public interface UserDao {
    public void add();
    public void normal();
}

public class UserDaoImpl implements UserDao {
    public void add() {
        System.out.println("user daoImpl ...");
    }
    public void normal(){
        System.out.println("normal");
    }
}

实现InvocationHandler接口,创建代理类

public class UserDaoProxy implements InvocationHandler {

    //原对象,在构造函数传进来
    private Object object;
    public UserDaoProxy(Object object) {
        this.object = object;
    }
    
    //增强的逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //只增强UserDaoImpl对象的add方法
        if(object instanceof UserDaoImpl){
            if(method.getName().equals("add")){
                System.out.println("add 增强前+++");
                Object res = method.invoke(object, args);
                System.out.println("add 增强后+++");
                return res;
            }
        }
        Object res = method.invoke(object, args);
        return res;
    }

}

使用java.lang.Proxy类创建代理对象

public class TestSpring5 {
    public static void main(String[] args) {
        testProxy();
    }

    private static void testProxy() {
        Class[] interfaces = {UserDao.class};
        UserDaoImpl userDaoImpl = new UserDaoImpl();
        UserDao userDaoProxy = (UserDao)Proxy.newProxyInstance(UserDaoImpl.class.getClassLoader(), interfaces, new UserDaoProxy(userDaoImpl));
        userDaoProxy.add();
        userDaoProxy.normal();
    }
}
/*
add 增强前+++
user daoImpl ...
add 增强后+++
normal
*/

四、AOP术语

  1. 连接点,可以被增强的方法
  2. 切入点,实际被增强的方法
  3. 通知(增强),实际增强的逻辑部分·
    3.1 前置通知,原方法执行前
    3.2 后置通知,原方法执行后
    3.3 环绕通知,前+后
    3.4 异常通知,相当于try catch finally中的catch
    3.5 最终通知,相当于try catch finally中的finally
  4. 切面,是一个动作,把通知(增强)应用到切入点的过程

五、AOP操作

Spring 一般通过基于AspectJ实现AOP操作,AspectJ不是Spring组成部分,它是可以单独使用的,Spring整合了它进行AOP操作。

5.1 切入点表达式

作用

知道哪个类里面的哪个方法进行增强

语法

excution([权限修饰符][返回类型][类的全路径][方法名称]([参数列表]))

举例

1.userDao中的add方法
execution(* site.javaee.spring5.dao.userDao.add(..))
2.userDao中的所有方法
execution(* site.javaee.spring5.dao.userDao.(..))
3.spring5包中的所有类的所有方法
execution(
site.javaee.spring5..(..))

5.2 基于注解的AOP

配置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"
       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="site.javaee.spring5"></context:component-scan>
    <!--    开启AspectJ生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

被代理类

@Component
public class User {
    public void add(){
        System.out.println("add user");
    }
}

代理增强类

package site.javaee.spring5.proxy;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

//如果有多个增强类对同一个方法增强,可以设置优先级,数字越小优先级越高
@Order(1)
@Component
@Aspect
public class UserProxy {
    //前置通知
    @Before("execution(* site.javaee.spring5.User.*(..))")
    public void before() {
        System.out.println("before 前置通知");
    }

    //后置通知
    @AfterReturning("execution(* site.javaee.spring5.User.*(..))")
    public void afterReturning() {
        System.out.println("afterReturning 后置通知");
    }

    //环绕通知
    @Around("execution(* site.javaee.spring5.User.*(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("around 环绕前通知");
        proceedingJoinPoint.proceed();
        System.out.println("around 环绕后通知");
    }

    //异常通知
    @AfterThrowing("execution(* site.javaee.spring5.User.*(..))")
    public void exception() {
        System.out.println("AfterThrowing 异常通知");
    }


	//相同切入点提取
	@Pointcut(value = "execution(* site.javaee.spring5.User.*(..))")
    public void point(){
    }
	
    //最终通知
    @After(value = "point()")
    public void finale(){
        System.out.println("after 最终通知");
    }
}

测试

public class TestSpring5 {
    public static void main(String[] args) {
        testAop();
    }

 
}
/*
around 环绕前通知
before 前置通知
add user
around 环绕后通知
after 最终通知
afterReturning 后置通知
*/

5.3 基于xml的AOP

public class User {
    public void add(){
        System.out.println("add user");
    }
}

public class PeopleProxy {
    //前置通知
    public void before() {
        System.out.println("before 前置通知");
    }

    //后置通知
    public void afterReturning() {
        System.out.println("afterReturning 后置通知");
    }

    //环绕通知
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("around 环绕前通知");
        proceedingJoinPoint.proceed();
        System.out.println("around 环绕后通知");
    }

    //异常通知
    public void exception() {
        System.out.println("AfterThrowing 异常通知");
    }

    //最终通知
    public void finale() { System.out.println("after 最终通知");}
}

<?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">

    <!--    创建对象-->
    <bean id="user" class="site.javaee.spring5.User"></bean>
    <bean id="peopleProxy" class="site.javaee.spring5.proxy.PeopleProxy"></bean>
    <!--    配置AOP增强-->
    <aop:config>
        <!--        配置切入点-->
        <aop:pointcut id="p" expression="execution(* site.javaee.spring5.User.*(..))"/>
        <!--        配置切面-->
        <aop:aspect ref="peopleProxy">
            <!--            增强作用在具体方法(切入点)上-->
            <aop:before method="before" pointcut-ref="p"></aop:before>
            <aop:after-returning method="afterReturning" pointcut-ref="p"></aop:after-returning>
            <aop:around method="around" pointcut-ref="p"></aop:around>
            <aop:after method="finale" pointcut-ref="p"></aop:after>
            <aop:after-throwing method="exception" pointcut-ref="p"></aop:after-throwing>
        </aop:aspect>
    </aop:config>
</beans>

5.4 纯注解开发

//开启组件扫描
@ComponentScan(basePackages = "site.javaee.spring5")
//开启AOP,使用CGLIB
@EnableAspectJAutoProxy(ProxyTargetClass=true)
//作为配置类,替代xml配置文件
@Configuration
public class SpringConfig {
}

Spring_004_JdbcTemplate

一、增删改查

Spring框架对JDBC进行了封装,使用JdbcTemplate很方便的对数据库进行操作。
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:component-scan base-package="site.javaee.spring5"></context:component-scan>

    <!--    配置外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!--    配置德鲁伊连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClass}"></property>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--    配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--        注入datasource-->
        <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
    </bean>
</beans>

外部属性文件

jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://rm-wz90pb9deb3muoyp5do.mysql.rds.aliyuncs.com:3306/xe
jdbc.username=root
jdbc.password=aliyunroot

Book,BookService,BookDao

@Component
public class Book {
    public Book(){}

    private String name;
    private String author;

    public String getName() {
        return name;
    }

    public String getAuthor() {
        return author;
    }

    public void setName(String name) {
        this.name = name;
    }
    public void setAuthor(String author) {
        this.author = author;
    }

    public Book(String name, String author) {
        this.name = name;
        this.author = author;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", author='" + author + '\'' +
                '}';
    }
}

@Service
public class BookService {
    @Autowired
    private BookDao bookDao;

    public int add(Book book) {
        int result = bookDao.add(book);
        return result;
    }

    public int updateByName(Book book) {
        int result = bookDao.updateByName(book);
        return result;
    }

    public int deleteByName(String name) {
        int result = bookDao.deleteByName(name);
        return result;
    }

    public int count() {
        int result = bookDao.count();
        return result;
    }

    public Book selectByName(String name) {
        Book book = bookDao.selectByName(name);
        return book;
    }
}

@Repository
public class BookDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public int add(Book book) {
        String sql = "insert into book values(?,?)";
        int result = jdbcTemplate.update(sql, book.getName(), book.getAuthor());
        return result;
    }

    public int updateByName(Book book) {
        String sql = "update book set author=? where name=?";
        int result = jdbcTemplate.update(sql, book.getAuthor(), book.getName());
        return result;
    }

    public int deleteByName(String name) {
        String sql = "delete from book where name=?";
        int result = jdbcTemplate.update(sql, name);
        return result;
    }

    public Book selectByName(String name) {
        String sql = "select * from book where name = ?";
        Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class),name);
        return book;
    }

    public int count() {
        String sql = "select count(*) from book";
        int result = jdbcTemplate.queryForObject(sql, Integer.class);
        return result;
    }

}

测试

public class TestSpring5 {
    public static void main(String[] args) {
        testJdbc();
    }

    private static void testJdbc() {
        ApplicationContext context = new ClassPathXmlApplicationContext("jdbc.xml");
        BookService bookService = context.getBean("bookService", BookService.class);

        int add = bookService.add(new Book("红楼梦", "曹雪芹"));
        System.out.println("增加了 " + add + " 条记录");
        int update = bookService.updateByName(new Book("西游记", "吴承恩"));
        System.out.println("更新了 " + update + " 条记录");
        int delete = bookService.deleteByName("红楼梦");
        System.out.println("删除了 " + update + " 条记录");
        int count = bookService.count();
        System.out.println("book表中一共有 " + count + " 条记录");
        Book book = bookService.selectByName("西游记");
        System.out.println(book);
    }
/*
增加了 1 条记录
更新了 1 条记录
删除了 1 条记录
book表中一共有 1 条记录
Book{name='西游记', author='吴承恩'}
*/
}

二、批量

@Repository
public class BookDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;


    public int deleteByName(String name) {
        String sql = "delete from book where name=?";
        int result = jdbcTemplate.update(sql, name);
        return result;
    }


    public List<Book> selectAll() {
        String sql = "select * from book";
        List<Book> books = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class));
        return books;
    }

    public void batchAddBook(List<Book> books) {
        List<Object[]> args = books.stream().map(item -> {
            return new Object[]{item.getName(), item.getAuthor()};
        }).collect(Collectors.toList());
        String sql = "insert into book values(?,?)";
        jdbcTemplate.batchUpdate(sql, args);
    }

    public void batchUpdateByName(List<Book> books) {
        List<Object[]> args = books.stream().map(item -> {
            return new Object[]{item.getName(), item.getAuthor()};
        }).collect(Collectors.toList());
        String sql = "update book set author=? where name=?";
        jdbcTemplate.batchUpdate(sql, args);
    }

}
    private static void testJdbcBatch() {
        ApplicationContext context = new ClassPathXmlApplicationContext("jdbc.xml");
        BookDao bookDao = context.getBean("bookDao", BookDao.class);

        List<Book> books = bookDao.selectAll();
        System.out.println(books);

        List<Book> bookList = new ArrayList<>();
        bookList.add(new Book("红楼梦", "曹雪芹"));
        bookList.add(new Book("红楼梦", "曹雪芹"));
        bookDao.batchAddBook(bookList);
        System.out.println(bookDao.selectAll());
        bookDao.deleteByName("红楼梦");

        List<Book> updateBookList = new ArrayList<>();
        bookList.add(new Book("西游记", "吴承恩2"));
        bookDao.batchUpdateByName(updateBookList);
        System.out.println(bookDao.selectAll());
    }

Spring_005_事务管理

一、什么是事务

  1. 事务是数据库操作最基本单元,指逻辑上的一组操作要么都成功,要么都失败。
  2. 事务的四大特性ACID:原子性,一致性,隔离性,持久性
    2.1 原子性:要么都成功,要么都失败。
    2.2 一致性:指执行事务前后的状态要一致,可以理解为数据一致性
    2.3 隔离性:侧重指事务之间相互隔离,不受影响,与事务设置的隔离级别有密切的关系。
    2.4 持久性:事务产生的影响会持久化到数据库中

二、实例

银行转账

数据库表
image.png
配置文件

<?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="site.javaee.spring5"></context:component-scan>

    <!--    配置外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!--    配置德鲁伊连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClass}"></property>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--    配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--        注入datasource-->
        <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
    </bean>
</beans>

userService, userDao, Test

public class TestSpring5 {
    public static void main(String[] args) {
        testTx();
    }

    private static void testTx() {
        ApplicationContext context = new ClassPathXmlApplicationContext("tx.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.transMoney();
    }
}


@Service(value = "userService")
public class UserService {
    @Autowired
    private UserDao userDao;

    public void transMoney() {
        userDao.sub("lucy", 100);
        userDao.plus("mary", 100);
        System.out.println("转账成功!");
    }
}


@Component
public class UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void plus(String name,int amount){
        String sql = "update account set money=money+? where username=?";
        jdbcTemplate.update(sql, amount,name);
    }

    public void sub(String name,int amount){
        String sql = "update account set money=money-? where username=?";
        jdbcTemplate.update(sql, amount,name);
    }
}

/*
转账成功!
*/

三、Spring中的事务

  1. 一般事务放在Service层管理。
  2. 在Spring中一般使用声明式事务管理,编程式事务管理过于繁琐且对业务代码侵入严重。
    2.1 编程式事务
    image.png
  3. 声明式事务管理支持注解方式(常用)和xml方式。
  4. Spring的声明式事务底层使用AOP原理。
  5. Spring事务管理的API
    5.1 提供一个接口PlatformTransactionManager代表事务管理器,不同的框架有不同的实现,对于JDBC是DataSourceTransactionManager。

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"
       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="site.javaee.spring5"></context:component-scan>

    <!--    配置外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
	
    <!--    配置德鲁伊连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClass}"></property>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--    配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--        注入datasource-->
        <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
    </bean>

    <!--    配置事务处理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--    开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

service

//在类上或者方法上加上事务注解
@Transactional
@Service(value = "userService")
public class UserService {
    @Autowired
    private UserDao userDao;

    public void transMoney() {
        userDao.sub("lucy", 100);
        int i = 1 / 0;
        userDao.plus("mary", 100);
        System.out.println("转账成功!");
    }
}

测试

public class TestSpring5 {
    private PlatformTransactionManager transactionManager;

    public static void main(String[] args) {
        testTx();
    }

    private static void testTx() {
        ApplicationContext context = new ClassPathXmlApplicationContext("tx.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.transMoney();
    }
}
/*
执行报错,Exception in thread "main" java.lang.ArithmeticException: / by zero
但数据库金额不变,说明事务起了作用
*/
@Transactional 配置

image.png

  1. propagation():事务传播行为,如何管理多个事务方法的调用
    1.1 用得比较多的是required和required_new
    image.png
  2. isolation():事务隔离级别,多事务操作不考虑隔离性会产生三个读问题,脏读,不可重复读,幻读
    2.1 脏读:一个未提交事务读到另一个未提交事务的数据。脏读是一个问题,绝对不能产生。
    2.2 不可重复读:一个未提交事务读到另一提交事务修改的数据,前后两次读取的数不一致。
    2.3 幻读:一个未提交事务读到另一提交事务新增/删除的数据,前后两次读取的数据条数不一致。
    image.png
  3. timeout():超时时间,限制事务在一定时间内进行提交,否则回滚
  4. readOnly():是否只读,true的话无法对数据库进行增删改操作
  5. rollbackFor():回滚,设置出现哪些异常才进行回滚
  6. rollbackFor():不回滚,设置出现哪些异常不回滚

3.2 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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-tx.xsd">

    <!--    开启组件扫描-->
    <context:component-scan base-package="site.javaee.spring5"></context:component-scan>

    <!--    配置外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!--    配置德鲁伊连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClass}"></property>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--    配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--        注入datasource-->
        <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
    </bean>

    <!--    配置事务处理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--    配置通知-->
    <tx:advice id="txAdvice">
        <!--        配置事务参数-->
        <tx:attributes>
            <!--            指定在哪种规则的方法上面添加事务-->
            <tx:method name="transMoney" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
            <tx:method name="trans*"/>
        </tx:attributes>
    </tx:advice>

    <!--    配置切入点和切面-->
    <aop:config>
        <!--        配置切入点-->
        <aop:pointcut id="pt" expression="execution(* site.javaee.spring5.service.*.*(..))"/>
        <!--        配置切面-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
    </aop:config>

</beans>

3.3 纯注解开发

package site.javaee.spring5.config;
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.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

//开启事务
@EnableTransactionManagement
//开启组件扫描
@ComponentScan(basePackages = "site.javaee.spring5")
//作为配置类,替代xml配置文件
@Configuration
public class SpringConfig {
    //创建数据库连接池
    //Bean默认名为首字母小写的类名
    @Bean
    public DruidDataSource getDruidDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://rm-wz90pb9deb3muoyp5do.mysql.rds.aliyuncs.com:3306/xe");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("aliyunroot");
        return druidDataSource;
    }
    //创建JdbcTemplate对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DruidDataSource druidDataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(druidDataSource);
        return  jdbcTemplate;
    }
    //创建事务管理器
	@Bean
    public DataSourceTransactionManager getTransactionManager(DruidDataSource druidDataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(druidDataSource);
        return transactionManager;
    }
}

测试

public class TestSpring5 {
    public static void main(String[] args) {
        testTx();
    }

    private static void testTx() {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        userService.transMoney();
    }
}

Spring_006_新特性

一、JDK版本

Spring5框架的代码基于Java8,运行时兼容Java9

二、日志框架

Spring5自带了日志框架
1 Spring5已经移除了Log4jConfigListener
2 官方建议使用Log4j2
3 导入log4j-api、log4j-core、slf4j-api、log4j-slf4j-impl、log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--status:Log4j2内部日志的输出级别,设置为TRACE对学习Log4j2非常有用 -->
<!--monitorInterval:定时检测配置文件的修改,有变化则自动重新加载配置,时间单位为秒,最小间隔为5s -->
<Configuration status="WARN" monitorInterval="600">
    <!--properties:设置全局变量 -->
    <properties>
        <!--LOG_HOME:指定当前日志存放的目录 -->
        <property name="LOG_HOME">logs</property>
        <!--FILE_NAME:指定日志文件的名称 -->
        <property name="FILE_NAME">test</property>
    </properties>
    <!--Appenders:定义日志输出目的地,内容和格式等 -->
    <Appenders>
        <!--Console:日志输出到控制台标准输出 -->
        <Console name="Console" target="SYSTEM_OUT">
            <!--pattern:日期,线程名,日志级别,日志名称,日志信息,换行 -->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%L] - %msg%n" />
        </Console>
        <!--RollingFile:日志输出到文件,下面的文件都使用相对路径 -->
        <!--fileName:当前日志输出的文件名称 -->
        <!--filePattern:备份日志文件名称,备份目录为logs下面以年月命名的目录,备份时使用gz格式压缩 -->
        <RollingFile name="RollingFile" fileName="${LOG_HOME}/${FILE_NAME}.log"
            filePattern="${LOG_HOME}/$${date:yyyy-MM}/${FILE_NAME}-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%L] - %msg%n" />
            <!--Policies:触发策略决定何时执行备份 -->
            <Policies>
                <!--TimeBasedTriggeringPolicy:日志文件按照时间备份 -->
                <!--interval:每1天生成一个新文件,时间单位需要结合filePattern时间%d{yyyy-MM-dd} -->
                <!--同理,如果要每1小时生成一个新文件,则改成%d{yyyy-MM-ddHH} -->
                <!--modulate:对备份日志的生成时间纠偏,纠偏以0为基准进行,"0+interval"决定启动后第一次备份时间 -->
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
                <!--SizeBasedTriggeringPolicy:日志文件按照大小备份 -->
                <!--size:指定日志文件最大为100MB,单位可以为KB、MB或GB -->
                <SizeBasedTriggeringPolicy size="100MB" />
            </Policies>
            <!--DefaultRolloverStrategy:翻转策略决定如何执行备份 -->
            <!--max:最多保存5个备份文件,结合时间使用后,在每个时间段内最多有5个备份,多出来的会被覆盖 -->
            <!--compressionLevel:配置日志压缩级别,范围0-9,0不压缩,1压缩速度最快,9压缩率最好,目前只对于zip压缩文件类型有效 -->
            <DefaultRolloverStrategy max="5" compressionLevel="1">
                <!--Delete:删除匹配到的过期备份文件 -->
                <!--maxDepth:由于备份文件保存在${LOG_HOME}/$${date:yyyy-MM},所以目录深度设置为2 -->
                <Delete basePath="${LOG_HOME}" maxDepth="2">
                    <!--IfFileName:匹配文件名称 -->
                    <!--glob:匹配2级目录深度下的以.log.gz结尾的备份文件 -->
                    <IfFileName glob="*/*.log.gz" />
                    <!--IfLastModified:匹配文件修改时间 -->
                    <!--age:匹配超过180天的文件,单位D、H、M、S分别表示天、小时、分钟、秒-->
                    <IfLastModified age="180D" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>
    </Appenders>
    <!--Loggers:定义日志级别和使用的Appenders -->
    <Loggers>
        <!--name: 打印日志的类的包路径 -->
        <!--additivity: true当前的Logger打印的日志附加到Root,false仅仅打印到RollingFile -->
        <Logger name="org.apache.logging.log4j" level="ERROR" additivity="true">
            <AppenderRef ref="RollingFile" />
        </Logger>
        <!--Root:日志默认打印到控制台 -->
        <!--level日志级别: ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF -->
        <Root level="ERROR">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

测试

public class TestSpring5 {
    private static final Logger log = LoggerFactory.getLogger(TestSpring5.class);

    public static void main(String[] args) {
        testTx();
    }

    private static void testTx() {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        userService.transMoney();
        log.info("转账成功");
    }
}

三、核心容器

  1. 支持@Nullable注解,可以使用在方法/属性/参数上,表示方法返回值/属性值/参数值可以为空。
  2. 支持函数式风格,GenericApplicationContext
    		private static void testFunctional() {
    			//函数式编程
    			GenericApplicationContext context = new GenericApplicationContext();
    			//刷新容器
    			context.refresh();
    			//注册对象
    			context.registerBean("user",User.class, () -> new User());
    			//获取对象
    			User user = (User)context.getBean("user");
    			System.out.println(user);
    		}
    

四、测试框架

4.1 JUnit4

需要引入junit-4.13.jar junit-4.13.1.jar

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfig.class})
//@ContextConfiguration("classpath:tx.xml")
public class JTest4 {
    @Autowired
    private UserService userService;

    @Test
    public void testTx(){
        userService.transMoney();
    }
}

4.2 JUnit5

需要引入junit-jupiter-api
image.png

package site.javaee.spring5.test;



import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import site.javaee.spring5.config.SpringConfig;
import site.javaee.spring5.service.UserService;

/**
 * @author Tao
 * @Date 2020/11/6
 * @Time 16:14
 */
//@ExtendWith(SpringExtension.class)
//@ContextConfiguration(classes = SpringConfig.class)
@SpringJUnitConfig(classes = SpringConfig.class)
public class JTest5 {

    @Autowired
    private UserService userService;

    @Test
    public void textTx(){
        userService.transMoney();
    }

}

五、WebFlux

5.1 简介

  1. WebFlux是Spring5的新模块,用于Web开发,功能与SpringMVC类似。是当前一种比较流行的响应式编程框架。
  2. 传统Web框架,比如SpringMVC都是基于Servlet容器,Servlet在3.1以后的版本才支持异步非阻塞。
  3. 而WebFlux是一种异步非阻塞的框架,核心是基于Reactor相关API实现。
  4. 同步/异步是针对调用者来说的,调用者发送请求,如果需要等待对方回应之后再去做其他事情就是同步,发送请求后不等对方回应直接去处理其他事情则是异步。
  5. 阻塞/非阻塞是针对被调用者来说的,被调用者收到一个请求之后,做完请求任务之后才给出反馈就是阻塞,而先马上给出反馈再去处理任务则是非阻塞。
优势
  1. 异步非阻塞,在有限资源下,提高系统的吞吐量和伸缩性,处理更多的请求,以Reactor为基础实现响应式编程。
  2. 支持函数式编程,Spring5基于Java8,WebFlux使用函数式编程来实现路由功能。
对比Spring MVC

image.png

  1. 两个框架都可以使用注解方式,都能运行在Tomcat等容器中。
  2. SpringMVC采用命令式编程,WebFlux采用异步响应式编程。
  3. SpringMVC+Servlet+Tomcat=同步阻塞,SpringWebFlux+Reactor+Netty=异步非阻塞

5.2 响应式编程

  1. 响应式编程是一种面向数据流和变化传播的编程范式。可以在编程语言中使用静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。命令式编程中,a=b+c,b和c再后续变化与a无关,而在响应式编程中a会一直关联b和c的变化。
  2. java8提供了观察者模式两个类,Observer和Observable来实现(伪)响应式编程,java9提供了Flow类来实现真正的响应式编程。
    	public class ObserverDemo extends Observable {
    		public static void main(String[] args) {
    			ObserverDemo observer = new ObserverDemo();
    			//添加观察者
    			observer.addObserver((o,arg)->{
    				System.out.println("发生了变化");
    			});
    			observer.addObserver((o,arg)->{
    				System.out.println("收到观察对象的变化,准备改变");
    			});
    			//数据变化
    			observer.setChanged();
    			//通知
    			observer.notifyObservers();
    		}
    	}
    

5.3 Reactor 框架。

  1. 在响应式编程的操作用,需要满足Reactive规范,Reactor是满足Reactive的一种框架。
  2. Reactor有两个核心类,Mono和Flux都继承了接口Publisher,提供丰富操作符。
    2.1 Flux对象实现发布者,返回N个元素;
    2.2 Mono对象实现发布者,返回0或者1个元素。
  3. Flux和Mono都可以发出三种数据信号:元素值、错误信号、完成信号
    3.1 错误信合和完成信号都属于终止信号,不能共存。
    3.2 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流。
    3.3 如果没有错误信号,没有完成信号,表示是无限数据流。
  4. 调用just或其他声明方法,只是声明数据流,数据流并没有发出,只有进行订阅subscribe()之后才会触发数据流。
  5. 操作符,对数据流进行一道道操作,称为操作符,如工厂流水线。
    5.1 map,将元素映射为新的元素
    5.2 flatMap,将元素映射为流
代码

引入Reactor依赖

        <!--        引入Reactor-->
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
            <version>3.3.11.RELEASE</version>
        </dependency>

测试

@SpringBootTest
class ReactorApplicationTests {

    @Test
    //声明
    void declare() {
        //just方法直接声明
        Flux.just(1,2,3,4);
        Mono.just(1);

        //声明数组
        Integer[] array = {1,2,3,4,5,6};
        Flux.fromArray(array);

        //声明集合
        List<Integer> list = Arrays.asList(array);
        Flux.fromIterable(list);

        //Steam流
        Stream<Integer> stream = list.stream();
        Flux.fromStream(stream);
    }

    //输出
    @Test
    void output(){
        Flux.just(1,2,3,4).subscribe(System.out::print);
        Mono.just(1).subscribe(System.out::print); 
    }
}

5.4 WebFlux执行流程

image.png

  1. WebFlux基于Reactor,默认容器是Netty,Netty是一个高性能的基于事件驱动的异步非阻塞框架。
  2. WebFlux执行过程和SpringMVC相似,核心控制器为DispatchHandler,实现接口WebHandler。
    image.png
    2.1 DispatchHandle:负责请求的处理。
    2.2 HanddlerMapping:请求查询到处理的方法。
    2.3 HandlerAdapter:适配器,真正负责请求处理。
    2.4 HandlerResultHandler:响应结果处理。
  3. WebFlux要实现函数式编程,有RouterFunction和HandlerFunction

5.5 代码实现

注解方式
  1. 使用注解的方式,跟SpringMVC很相似,只需要把相关依赖配置到项目中。
  2. Springboot会自动配置相关运行容器,默认情况下使用Netty服务器。

基于springboot并引入webflux依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

controller层+service层

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    //根据id查用户
    @GetMapping("/user/{id}")
    public Mono<User> getUserById(@PathVariable("id") int id) {
        return userService.getUserById(id);
    }

    //查询所有用户
    @GetMapping("/user")
    public Flux<User> getAllUser() {
        return userService.getAllUser();
    }

    //添加用户
    @PostMapping("/saveuser")
    public Mono<Void> saveUserInfo(@RequestBody User user) {
        Mono<User> userMono = Mono.just(user);
        return userService.saveUserInfo(userMono);
    }
}


public interface UserService {
    //根据id查用户
    Mono<User> getUserById(int id);
    //查询所有用户
    Flux<User> getAllUser();
    //添加用户
    Mono<Void> saveUserInfo(Mono<User> userMono);
}

@Service
public class UserServiceImpl implements UserService {

    //创建map集合模拟数据库
    private final Map<Integer, User> users = new HashMap<>();

    public UserServiceImpl() {
        users.put(1, new User("lucy", "男", 20));
        users.put(2, new User("mary", "女", 24));
        users.put(3, new User("jack", "男", 23));
    }

    //根据id查用户
    @Override
    public Mono<User> getUserById(int id) {
        return Mono.justOrEmpty(users.get(id));
    }

    //查询所有用户
    @Override
    public Flux<User> getAllUser() {
        return Flux.fromIterable(users.values());
    }

    //添加用户
    @Override
    public Mono<Void> saveUserInfo(Mono<User> userMono) {
        return userMono.doOnNext(person -> {
            int size = users.size() + 1;
            users.put(size, person);
        }).thenEmpty(Mono.empty());
        //把Mono清空,清空也是终止信号
    }
}
函数式编程
  1. 需要自己初始化服务器
  2. 两个核心接口:RouterFunction和HandlerFunction
    2.1 RouterFunction是线路由功能,将请求转发给对应的handler
    2.2 HandlerFunction处理请求生成响应的函数
  3. SpringWebflux请求和响应不再是ServletRequest和ServletResponse,而是ServerRequest和ServerResponse。

代码
handle

package site.javaee.reactor.controller;

import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import site.javaee.reactor.entity.User;
import site.javaee.reactor.service.UserService;

import static org.springframework.web.reactive.function.BodyInserters.fromValue;

/**
 * @author shkstart
 * @create 2020-11-06 22:40
 */
public class UserHandler {

    private UserService userService;

    public UserHandler(UserService userService) {
        this.userService = userService;
    }


    //根据id查用户
    public Mono<ServerResponse> getUserById(ServerRequest serverRequest) {
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();

        Integer id = Integer.valueOf(serverRequest.pathVariable("id"));
        Mono<User> userMono = this.userService.getUserById(id);
        Mono<ServerResponse> result = userMono.flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(fromValue(person))).switchIfEmpty(notFound);
        return result;
    }

    //查询所有用户
    public Mono<ServerResponse> getAllUser(ServerRequest serverRequest) {
        Flux<User> allUser = this.userService.getAllUser();
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(allUser,User.class).switchIfEmpty(ServerResponse.notFound().build());
    }

    //添加用户
    public Mono<ServerResponse> saveUserInfo(ServerRequest serverRequest) {
        Mono<User> userMono = serverRequest.bodyToMono(User.class);
        return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
    }
}

server

package site.javaee.reactor.controller;

import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.util.MimeTypeUtils;
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 site.javaee.reactor.service.UserService;
import site.javaee.reactor.service.impl.UserServiceImpl;

import java.io.IOException;

import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;

/**
 * @author shkstart
 * @create 2020-11-06 23:06
 */
public class Server {
    public static void main(String[] args) throws IOException {
        Server server = new Server();
        server.createReactorServer();
        System.out.println("enter to exit");
        System.in.read();
    }
    //创建路由
    public RouterFunction<ServerResponse> routingFunction() {
        //创建handle对象
        UserService userService = new UserServiceImpl();
        UserHandler userHandler = new UserHandler(userService);
        //设置路由
        RouterFunction<ServerResponse> route = RouterFunctions.route(
                GET("/users/{id}").and(accept(MediaType.APPLICATION_JSON)), userHandler::getUserById)
                .andRoute(GET("/users").and(accept(MediaType.APPLICATION_JSON)), userHandler::getAllUser);
        return route;
    }

    //创建服务器,完成适配
    public void createReactorServer(){
        //适配路由和handler
        RouterFunction<ServerResponse> router = routingFunction();
        HttpHandler httpHandler = toHttpHandler(router);
        ReactorHttpHandlerAdapter handlerAdapter = new ReactorHttpHandlerAdapter(httpHandler);

        //创建服务器
        HttpServer httpServer = HttpServer.create();
        httpServer.handle(handlerAdapter).bindNow();
    }
}

客户端
image.png
image.png

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议