Spring 5 学习整理


一、Spring 概述

① Spring框架是一个开源的J2EE应用程序框架,是针对bean的生命周期进行管理的轻量级容器。
② Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。
③ Spring可以单独应用于构筑应用程序,也可以和其它众多框架组合使用。
④ Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。

特点

  • 方便解耦,简化开发
  • AOP编程的支持
  • 声明式事务的支持
  • 方便程序的测试
  • 方便集成各种优秀框架
  • 降低Java EE API的使用难度

二、Spring 初体验

1、引入spring依赖

<dependencies>
<!--        基本框架和日志包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
<!--        aop相关依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.0</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
        <!--    JdbcTemplate相关依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>
        <!-- druid连接池和mysql的包 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.23</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
        </dependency>


        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>


        <dependency>
            <!-- jsoup HTML parser library @ https://jsoup.org/ -->
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.14.3</version>
        </dependency>

        <!--  日志相关-->
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.13.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.13.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

2、创建一个对象

public class User {
    private String name;
    private int age;

    public void run(){
        System.out.println("run");
    }

}

3、创建bean的配置文件

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

    <bean id="user" class="cn.ityang.pojo.User"></bean>

</beans>

4、测试spring的方式创建bean

public class TestSpring {
    @Test
    public void test1(){
    	// 加载spring的配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        // 获取配置创建对象
        User user = context.getBean("user", User.class);
        // 执行对象中的方法
        user.run();
    }
}

三、IoC

1、IoC 是什么

控制反转(Inversion of Control,缩写为IoC),把对象创建和对象之间的调用过程,交给spring进行管理。目的就是为了降低代码的耦合度。

2、IoC 底层原理

以前我们创建对象是通过new来创建,那么我们现在把对象创建交给spring来管理了,它底层是怎么创建的呢?
xml 解析、工厂模式、反射
1) xml 配置文件中配置对象

<bean id="user" class="cn.ityang.pojo.User"></bean>

2) 使用工厂模式创建对象

public class BeanFactoryTest {
    public static User getUser() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        // 解析xml文件获得xml中的属性值,属性值中有class对象
        // <bean id="user" class="cn.ityang.pojo.User"></bean>
        String classValue = "cn.ityang.pojo.User";
        // 通过反射可以获得类的实例
        Class<?> aClass = Class.forName(classValue);
        return (User) aClass.newInstance();
    }
}

3、IoC 容器(接口)

IoC 思想是基于IoC容器完成,IoC 容器底层就是对象工厂。
Spring 提供IoC容器两种实现方式:(2个接口)
1)BeanFactory:IoC 容器的基本实现,是Spring 内部的使用接口,不提供开发人员进行使用
2)ApplicationContext:BeanFactory的子接口,提供更多更强大的功能,一般由开发人员进行使用
两者的区别:
① BeanFactory在加载配置文件时候不会创建对象,在获取对象的时候才去创建。ApplicationContext在加载配置文件的时候就会把配置文件中的配置对象创建
② BeanFactory通常以编程的方式创建,ApplicationCotext还能以声明的方式创建
③ BeanFactory,ApplicationCotext都支持BeanPostProcessor,BeanFactoryPostProcessor的使用,两者区别:BeanFactory需要手动注册,ApplicationContext是自动注册

ApplicationContext 接口有两个主要的实现类:
FileSystemXmlApplicationContext:加载磁盘路径下的xml文件
ClassPathXmlApplicationContext:加载classpath下的xml文件

4、Bean管理(对象创建和属性注入)

4.1 基于xml配置文件对bean的管理

1)基于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">

    <!--
        1、在spring配置文件中,使用bean标签,标签里面添加对应的属性,就可以实现对象的创建
        2、bean标签中有很多属性,常用的有
            id属性:唯一标识
            class属性:类全路径
        3、创建对象时候,默认的是执行无参构造方法完成对象创建
    -->
    <bean id="user" class="cn.ityang.pojo.User"></bean>

</beans>

② 注入属性
依赖注入(DI):IoC容器属性注入的具体实现
属性注入的前提是要创建对象,所以我们在spring配置文件中配置对象,然后在对象里面配置属性注入。
通过set方法注入属性
前提是类中有set方法

    <bean id="user" class="cn.ityang.pojo.User">
        <!--使用property完成属性注入
            name:类里面属性名称
            value:向属性中注入的值
        -->
        <property name="name" value="jack"/>
        <property name="age" value="18"/>
    </bean>

p命名空间注入:简化set注入(不常用)
前提需要引入xmlns:p="http://www.springframework.org/schema/p"

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
       
    <bean id="user" class="cn.ityang.pojo.User" p:name="jack" p:age="22"></bean>

</beans>

通过有参构造注入属性
前提是类中有有参构造方法

    <bean id="user" class="cn.ityang.pojo.User">
        <!--使用constructor-arg完成属性注入
            name:类里面属性名称
            value:向属性中注入的值
        -->
        <constructor-arg name="name" value="jack"/>
        <constructor-arg name="age" value="20"/>
    </bean>

字面量属性注入(包含注入空值和特殊符号的处理)

    <bean id="user" class="cn.ityang.pojo.User" >
        <property name="age" value="18"/>
        <!-- 给属性注入null -->
        <property name="phone">
            <null/>
        </property>
        <!-- 特殊符号的处理 -->
        <property name="name">
            <value><![CDATA[<<jack>>]]></value>
        </property>
    </bean>

bean的注入
外部bean的注入

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


    <bean id="userServiceImpl" class="com.study.service.impl.UserServiceImpl">
        <!-- 注入外部bean对象
             name属性:类里面属性名称
             ref属性:需要注入的bean的id
        -->
        <property name="userDao" ref="userDaoImpl"></property>
    </bean>
    <bean id="userDaoImpl" class="com.study.dao.impl.UserDaoImpl"></bean>

</beans>

内部bean注入

    <bean id="employee" class="com.study.domain.Employee">
        <property name="name" value="jack"></property>
        <property name="phone" value="13888888888"></property>
        <!-- 内部bean的注入方式 -->
        <property name="department">
            <bean id="department" class="com.study.domain.Department">
                <property name="name" value="财务部"></property>
            </bean>
        </property>
    </bean>

级联赋值

    <bean id="employee" class="com.study.domain.Employee">
        <property name="name" value="jack"></property>
        <property name="phone" value="13888888888"></property>
        <property name="department" ref="department"></property>
        <!-- 级联赋值,前提是employee实体中要有get方法 -->
        <property name="department.name" value="技术部"></property>
    </bean>
    <bean id="department" class="com.study.domain.Department"></bean>

集合的注入

    <bean id="user" class="cn.ityang.pojo.User">
    <!--数组属性注入-->
        <property name="arr">
            <array>
                <value>java</value>
                <value>python</value>
            </array>
        </property>
        <!--list集合属性注入-->
        <property name="list">
            <list>
                <value>apple</value>
                <value>banana</value>
            </list>
        </property>
        <!--map集合属性注入-->
        <property name="map">
            <map>
                <entry key="name" value="age"></entry>
                <entry key="age" value="18"></entry>
            </map>
        </property>
        <!--set属性注入-->
        <property name="set">
            <set>
                <value>1</value>
                <value>2</value>
            </set>
        </property>
       <!--对象类型list属性注入-->
        <property name="students">
            <list>
                <ref bean="student"></ref>
                <ref bean="student1"></ref>
            </list>
        </property>
    </bean>

<bean id="student" class="cn.ityang.pojo.Student">
        <property name="name" value="amy"></property>
    </bean>
    <bean id="student1" class="cn.ityang.pojo.Student">
        <property name="name" value="jack"></property>
    </bean>

引用公共部分

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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="studentList">
        <ref bean="student"></ref>
        <ref bean="student1"></ref>
    </util:list>


    <bean id="user" class="cn.ityang.pojo.User">
        <!--引用公共部分的对象集合-->
        <property name="students" ref="studentList"></property>

    </bean>

    <bean id="student" class="cn.ityang.pojo.Student">
        <property name="name" value="amy"></property>
    </bean>
    <bean id="student1" class="cn.ityang.pojo.Student">
        <property name="name" value="jack"></property>
    </bean>

</beans>

2)基于注解方式实现

3)FactoryBean
Spring 有两种类型bean,一种是普通的bean,另外一种就是工厂bean(FactoryBean)
① 普通bean:在配置文件中定义什么bean类型就返回什么类型

<bean id="student" class="cn.ityang.pojo.Student">
        <property name="name" value="amy"></property>
    </bean>

② 工厂bean:在配置文件中定义什么bean类型,实际返回的类型可以与定义的bean类型不一样

public class MyBean implements FactoryBean<Student>{

    public Student getObject() throws Exception {
        Student student = new Student();
        student.setName("jack");
        return student;
    }

    public Class<?> getObjectType() {
        return null;
    }
}

4)bean 的作用域

    <!--
        scope属性可以设置bean的作用域
        singleton:默认值,整个ioc容器中只有单个实例对象,在加载配置文件的时候就创建了这个单例对象
        prototype:原型,多例对象,每次getBean的时候创建多例对象
        request:一次request请求创建自己的实例对象,web环境下有效
        session:一次session会话中创建自己的实例对象,web环境下有效
        application:单个bean定义的作用域限定为ServletContext的生命周期,web环境下有效
        websocket:将单个bean定义的作用域限定为WebSocket的生命周期,web环境下有效
    -->
    <bean id="mybean" class="cn.ityang.pojo.MyBean" scope="prototype"></bean>

5)bean 的生命周期
生命周期:对象从创建到销毁的一个过程。

第一步:通过构造器创建bean实例(无参构造)
第二步:为bean的属性设置值和对其他bean的引用(调用set方法)
第三步:把bean实例传递给bean后置处理器的方法postProcessBeforeInitialization()
第四步:调用bean的初始化方法(需要进行配置初始化的方法)
第五步:把bean实例传递给bean后置处理器的方法postProcessAfterInitialization()
第六步:bean可以使用了(对象获取到了)
第七步:当容器关闭的时候,调用bean的销毁方法(需要进行配置)

// bean对象
public class Student {
    private String name;

    public Student() {
        System.out.println("第一步:调用无参构造实例化对象");
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("第二步:利用set方法设置属性");
    }

    public void initMethod(){
        System.out.println("第三步:执行初始化方法");
    }

    public void detroyMethod(){
        System.out.println("第五步:执行bean的销毁方法");
    }

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

// 后置处理器
public class MyBeanPost implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean初始化之前执行的方法");
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean初始化之后执行的方法");
        return bean;
    }
}
// 测试
public class TestSpring {
    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Student student = context.getBean("student", Student.class);
        System.out.println("第四步:获取对象bean,并且使用");
        System.out.println(student);


        ((ClassPathXmlApplicationContext) context).close();
    }
}
    <bean id="student" class="cn.ityang.pojo.Student" init-method="initMethod" destroy-method="detroyMethod">
        <property name="name" value="amy"></property>
    </bean>
    <!--需要配置post才能生效-->
    <bean id="beanPost" class="cn.ityang.pojo.MyBeanPost"></bean>

5)bean 的自动装配(xml方式)
自动装配:根据指定装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入

<!--
    autowire="byName":根据属性名称注入,注意属性名要与注入bean的id一样
    autowire="byType":根据属性类型注入,注意该类型声明的bean只能有一个,如果有多个就识别不出
    -->
    <bean id="emp" class="cn.ityang.pojo.Emp" autowire="byName"></bean>

    <bean id="dept" class="cn.ityang.pojo.Dept">
        <property name="dname" value="财务部"></property>
    </bean>

6)引入外部属性管理
引入德鲁伊依赖,待会用于配置jdbc连接池

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
<?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">


    <!--直接配置druid连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!--<property name="driverClassName" value="com.mysql.jdbc.Driver"/>-->
        <!--<property name="url" value="jdbc:mysql://localhost:3306/test"/>-->
        <!--<property name="username" value="root"/>-->
        <!--<property name="password" value="123"/>-->
        <property name="driverClassName" value="${prop.driverClassName}"/>
        <property name="url" value="${prop.url}"/>
        <property name="username" value="${prop.username}"/>
        <property name="password" value="${prop.password}"/>
    </bean>

    <!--引入配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

</beans>

外部配置:jdbc.properties

prop.driverClassName=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/test
prop.username=root
prop.password=123

4.2 基于注解方式对bean的管理

4.2.0 开启注解扫描

<?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="cn.ityang.controller, cn.ityang.service, cn.ityang.dao"/>-->
    <context:component-scan base-package="cn.ityang"/>
</beans>

    <!-- 额外的一些配置扫描组件的方式,理解即可 -->
    <!--
        use-default-filters="false":表示不适用默认的扫描规则,后面自己定义扫描内容
        <context:include-filter:设置扫描内容
    -->
    <context:component-scan base-package="cn.ityang" use-default-filters="false">
        <!-- 设置扫描base-package下使用Controller注解的所有类-->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--
        配置扫描所有base-package包下的类,但是除了@Controller注解的类
    -->
    <context:component-scan base-package="cn.ityang">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

4.2.1 创建对象
@Component、@Controller、@Repository、@Service
上面四个注解功能是一样的,都可以用来创建对象,分那么多注解主要是对应java的三层架构

// 相当于<bean id="userService" ...></bean>
// value可以不写,默认是类名将首字母小写
@Service(value = "userService")
public class UserService {
    public void add(){
        System.out.println("service add......");
    }
}
// 测试
public class TestSpring {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println(userService);
        userService.add();
    }
}

4.2.2 注入属性
@Autowired:根据属性类型注入
@Qualifier:根据属性名称注入,必须和@Autowired一起使用,不然注入不了
@Resource:如果没有name属性值,则默认使用类型注入,如果有name属性值则按名称注入,此注解是javax.annotation.Resource的注解,我们一般不用这个,用前面两个spring提供的注解
@Value:注入普通属性

@Service
public class UserService {

// 注入普通属性
	@Value(value = "hello")
    private String name;
	/**
		根据类型注入属性,UserDao实现类中不需要提供set方法,底层默认封装了
	*/
    @Autowired
    private UserDao userDao;
    // @Qualifier:根据属性名称注入,必须和@Autowired一起使用,不然注入不了
//    @Autowired
//    @Qualifier("userDaoImpl")
//    private UserDao userDao;

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

    public void add(){
        System.out.println("service add......");
        userDao.getUser();
    }
}
// 定义接口
public interface UserDao {
    void getUser();
}
// 接口实现类
@Repository
public class UserDaoImpl implements UserDao{
    public void getUser() {
        System.out.println("return user......");
    }
}
//测试
public class TestSpring {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println(userService);
        userService.add();
    }
}

4.2.3 纯注解开发

// 创建一个注解的配置类来代替xml配置文件
@Configuration
@ComponentScan(basePackages = {"com.study"})
public class SpringConfig {
}
// service对象
@Service
public class UserService {
    @Autowired
    private UserDao userDao;
    
    public void test(){
        System.out.println("test");
        userDao.getUser();
    }
}
// dao
public interface UserDao {
    void getUser();
}
// dao实现
@Repository
public class UserDaoImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("get user");
    }
}
// 测试
@Test
public void test05(){
    ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    UserService userService = context.getBean("userService", UserService.class);
    userService.test();
}

四、AOP

4.1 基本概念

面向切面编程(Aspect Oriented Programming),通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。(通俗说就是不修改源代码的情况下添加新功能)

4.2 底层原理

AOP 底层使用动态代理(两种)
1)有接口的情况,使用 JDK 动态代理(创建接口实现类代理对象,增强类的方法)

// 接口
public interface UserDao {
    int add(int i, int j);
}
// 实现类
public class UserDaoImpl implements UserDao{
    public int add(int i,int j) {
        System.out.println("add......");
        return i +j;
    }
}


public class JDKProxy {
    public static void main(String[] args) {
        ClassLoader classLoader = JDKProxy.class.getClassLoader();
        Class[] classes = {UserDao.class};
        UserDaoImpl userDaoImpl = new UserDaoImpl();
        UserDao proxy = (UserDao)Proxy.newProxyInstance(classLoader, classes, new UserDaoProxy(userDaoImpl));
        int result = proxy.add(1, 2);
        System.out.println(result);
    }
}

// 代理类对象
class UserDaoProxy implements InvocationHandler{

    // 目标代理对象
    private Object target;

    // 利用构造方法来接收目标代理对象
    public UserDaoProxy(Object target){
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName() + "方法执行前增强,方法参数" + Arrays.toString(args));
        Object result = method.invoke(target, args);
        System.out.println(method.getName() + "方法执行后增强,方法参数" + Arrays.toString(args));
        return result;
    }
}

2)没有接口的情况,使用 CGLIB 动态代理(创建子类的代理对象,增强类的方法)

<!-- 引下依赖,不然用不了cglib相关API -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>asm</groupId>
            <artifactId>asm</artifactId>
            <version>3.1</version>
        </dependency>
// 目标类
public class SuperStar {
    public String sing(String song){
        System.out.println("sing a song");
        return "sing a song";
    }
    public String eat(String food){
        System.out.println("eat some food");
        return "eat some food";
    }
}
// 代理类
public class CglibProxy implements MethodInterceptor{

    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        SuperStar proxy = (SuperStar) cglibProxy.createProxyObj(SuperStar.class);
        proxy.eat("炸鸡");
        System.out.println("------");
        proxy.sing("国歌");
    }

    /**
     * 根据一个类产生一个代理类
     * @param clazz
     * @return
     */
    public Object createProxyObj(Class<?> clazz){
        // 通过CGLIB动态代理获取代理对象的过程
        Enhancer enhancer = new Enhancer();
        // 设置enhancer对象的父类
        enhancer.setSuperclass(clazz);
        // 设置enhancer的回调对象
        enhancer.setCallback(this);
        // 创建代理对象
        return enhancer.create();
    }


    /**
     * 增强方法
     * @param obj 代理类本身
     * @param method 拦截的目标方法
     * @param args 方法参数
     * @param methodProxy cglib方法:intercept方法完成增强后,我们调用cglib方法间接调用父类方法完成整个方法链的调用
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("目标方法执行前增强:" + Arrays.toString(args));
        Object result = methodProxy.invokeSuper(obj, args);
        System.out.println("目标方法执行后增强:" + result);
        return result;
    }
}

4.3 AOP相关术语

1、JoinPoint(连接点)
目标类中哪些方法可以被增强,那么这些方法被称为连接点

2、Pointcut(切入点)
目标类中实际被增强的方法,称为切入点。比如类中有A、B、C三个方法,其中只有A被增强了,那么A就是切入点,其它两个只能被称为连接点。

3、Advice(增强/通知)
实际增强的逻辑部分称为通知,比如在切入点前面做了权限校验,那么权限校验就是通知。
通知有多种类型:
前置通知
后置通知
环绕通知
异常通知
最终通知

4、Aspect(切面)
切面是一个动作,表示把通知应用到切入点的过程。

4.4 AOP准备工作

Spring 框架一般都是基于AspectJ实现AOP操作,AspectJ是一个独立的AOP框架,所以这边要引入相关依赖

<!--        aop相关依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.0</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

切入点表达式介绍
1、作用
通过表达式指定对哪个类里面的哪个方法进行增强
2、语法结构
execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
举例一:对 com.study.dao.BookDao类里面的add方法进行增强
execution(* com.study.dao.BookDao.add(..))
举例二:对 com.study.dao.BookDao类里面的所有方法进行增强
execution(* com.study.dao.BookDao.*(..))
举例三:对 com.study.dao包里面的所有类,类中的所有方法进行增强
execution(* com.study.dao.*.*(..))

4.4 AOP操作(基于注解)

// spring配置类
@Configuration
@ComponentScan(basePackages = {"com.study"})
@EnableAspectJAutoProxy(proxyTargetClass = true) // 开启Aspect生成代理对象
public class SpringConfig {}
// 目标类
@Component
public class Book {
    public void look(){
//        int i = 10 / 0;
        System.out.println("look book");
    }
}
// 代理类
@Component
@Aspect // 生成代理对象
public class BookProxy {

    /**
     * 前置通知
     */
    @Before(value = "execution(* com.study.aop.Book.look(..))")
    public void before(){
        System.out.println("before");
    }

    /**
     * 最终通知
     * 方法执行后执行,无论方法是否发生异常
     */
    @After(value = "execution(* com.study.aop.Book.look(..))")
    public void after(){
        System.out.println("after");
    }

    /**
     * 后置通知
     * 方法返回后执行,有异常则不执行
     */
    @AfterReturning(value = "execution(* com.study.aop.Book.look(..))")
    public void afterReturning(){
        System.out.println("afterReturning");
    }

    /**
     * 异常通知:有异常后通知
     */
    @AfterThrowing(value = "execution(* com.study.aop.Book.look(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }

    /**
     * 环绕通知
     */
    @Around(value = "execution(* com.study.aop.Book.look(..))")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("around before");
        joinPoint.proceed();
        System.out.println("around after");
    }
}
// 测试
    @Test
    public void testAop(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        Book book = context.getBean("book", Book.class);
        book.look();
    }

    /** 
     * 正常结果
     * around before
     * before
     * look book
     * around after
     * after
     * afterReturning
     */
    /**
     * 有异常的结果
     * around before
     * before
     * after
     * afterThrowing
     */

切入点抽取与多个增强类的排序

@Component
@Aspect
@Order(1) // 多个增强类的排序,数字越小优先级越高
public class BookProxy {

// 公共切入点抽取
    @Pointcut(value = "execution(* com.study.aop.Book.look(..))")
    public void pointCut(){}

    /**
     * 前置通知
     */
    @Before(value = "pointCut()")
    public void before(){
        System.out.println("before");
    }
}

4.5 AOP操作(基于xml)

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


    <bean id="book" class="com.study.aop.Book"/>
    <bean id="bookProxy" class="com.study.aop.BookProxy"/>
<!--  配置aop增强  -->
    <aop:config>
<!--        切入点-->
        <aop:pointcut id="p" expression="execution(* com.study.aop.Book(..))"/>
<!--        配置切面-->
        <aop:aspect ref="bookProxy">
<!--            增强执行在具体方法上-->
            <aop:before method="before" pointcut-ref="p"></aop:before>
        </aop:aspect>
    </aop:config>

</beans>

五、JdbcTemplate

5.1 引入JdbcTemplate相关依赖

        <!--    JdbcTemplate相关依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>
        <!-- druid连接池和mysql的包 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.23</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
        </dependency>

5.2 配置数据源和JdbcTemplate

1、外部配置:jdbc.properties

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=123

2、Spring 配置类

@Configuration
@ComponentScan(basePackages = {"com.study"})
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {

    @Value("${jdbc.url}")
    private String jdbcUrl;

    @Value("${jdbc.username}")
    private String jdbcUsername;

    @Value("${jdbc.password}")
    private String jdbcPassword;

    @Value("${jdbc.driverClassName}")
    private String jdbcDriverClass;

    /**
     * 创建数据库连接池
     * @return
     */
    @Bean
    public DruidDataSource druidDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(jdbcDriverClass);
        druidDataSource.setUrl(jdbcUrl);
        druidDataSource.setUsername(jdbcUsername);
        druidDataSource.setPassword(jdbcPassword);
        return druidDataSource;
    }
    // 以上代码 基于配置文件 实现连接数据库
    // 相当于 xml中
//        <bean id="druidDataSource" 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>

    /**
     * 创建JdbcTemplate对象
     * @param dataSource
     * @return
     */
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        //到ioc 容器中 根据类型找到相对应得 DataSource 对象 进行注入
        JdbcTemplate jdbcTemplate=new JdbcTemplate();
        //注入DruidDataSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
    // 以上代码 基于配置文件 实现连接数据库
    // 相当于 xml中
//    <!--    创建JdbcTemplate对象-->
//    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
//<!--       注入 DruidDataSource  数据库连接配置 -->
//        <property name="dataSource" ref="druidDataSource" ></property>
//     </bean>


}

5.3 JdbcTemplate 进行增删改查操作

// user实体
public class User {
    private Long id;
    private String userName;
    private String userStatus;
}
// service
@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public int add(User user){
        return userDao.add(user);
    }
    public int update(User user){
        return userDao.update(user);
    }

    public int delete(Long id){
        return userDao.delete(id);
    }

}
// dao
public interface UserDao {
    /**
     * 新增一个用户
     * @param user
     * @return
     */
    int add(User user);
        /**
     * 修改
     * @param user
     * @return
     */
    int update(User user);

    /**
     * 删除
     * @param id
     * @return
     */
    int delete(Long id);
}

@Repository
public class UserDaoImpl implements UserDao{

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 新增
     * @param user
     * @return
     */
    public int add(User user) {
        String sql = "insert into t_user values (?,?,?)";
        Object[] args = {user.getId(),user.getUserName(),user.getUserStatus()};
        return jdbcTemplate.update(sql, args);
    }
        /**
     * 更新
     * @param user
     * @return
     */
    public int update(User user) {
        String sql = "update t_user set user_name = ?, user_status = ? where id = ?";
        Object[] args = {user.getUserName(),user.getUserStatus(),user.getId()};
        return jdbcTemplate.update(sql, args);
    }

    /**
     * 删除
     * @param id
     * @return
     */
    public int delete(Long id) {
        String sql = "delete from t_user where id = ?";
        Object[] args = {id};
        return jdbcTemplate.update(sql, args);
    }
    /**
     * 查询记录数
     * @return
     */
    public int queryCount() {
        String sql = "select count(*) from t_user";
        // 这里的参数2是返回值类型
        return jdbcTemplate.queryForObject(sql, Integer.class);
    }

    /**
     * 根据id查询用户信息
     * @param id
     * @return
     */
    public User queryUser(Long id){
        String sql = "select * from t_user where id = ?";
        return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), id);

    }


    /**
     * 查询所有用户信息
     * @return
     */
    public List<User> queryAllUser(){
        String sql = "select * from t_user";
        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class));
    }


    /**
     * 批量新增
     * @param users
     */
    public void addBatch(List<Object[]> users){
        String sql = "insert into t_user values (?,?,?)";
        jdbcTemplate.batchUpdate(sql, users);
    }

    /**
     * 批量更新
     * @param users
     */
    public void updateBatch(List<Object[]> users) {
        String sql = "update t_user set user_name = ?, user_status = ? where id = ?";
        jdbcTemplate.batchUpdate(sql,users);
    }

    /**
     * 批量删除
     * @param users
     */
    public void deleteBatch(List<Object[]> users) {
        String sql = "delete from t_user where id = ?";
        jdbcTemplate.batchUpdate(sql,users);
    }
}
// test
public class TestSpring {
    @Test
    public void testAdd(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        User user = new User();
        user.setUserName("jack");
        user.setUserStatus("ok");
        int add = userService.add(user);
        System.out.println(add);
    }
    @Test
    public void testUpdate(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        User user = new User();
        user.setId(1L);
        user.setUserName("lucy");
        user.setUserStatus("yes");
        int update = userService.update(user);
        System.out.println(update);
    }


    @Test
    public void testDelete(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        int delete = userService.delete(1L);
        System.out.println(delete);
    }

}

六、Spring 事务

6.1 事务概念

1、什么是事务
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败。

2、事务的四大特性

  • 原子性(atomicity):即不可分割性,事务要么全部被执行,要么就全部不被执行。
  • 一致性(consistency):事务的执行使得数据库从一种正确状态转换成另一种正确状态。
  • 隔离性(isolation):在并发环境中,并发的事务是互相隔离的,一个事务的执行不能被其它事务干扰。
  • 持久性(durability):事务一旦提交后,数据库中的数据必须被永久的保存下来。

6.2 基础环境

// service
@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    /**
     * 转账操作
     */
    public void accountMoney(){
        userDao.reduceMoney();
        userDao.addMoney();
    }

}
// dao
public interface UserDao {
    void addMoney();
    void reduceMoney();
}
@Repository
public class UserDaoImpl implements UserDao{
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void addMoney(){
        String sql = "update t_account set money = money + ? where user_name = ?";
        jdbcTemplate.update(sql, 100, "mary");
    }
    public void reduceMoney(){
        String sql = "update t_account set money = money - ? where user_name = ?";
        jdbcTemplate.update(sql, 100, "lucy");
    }

}
// test
public class TestSpring {

    @Test
    public void testAccount(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        userService.accountMoney();
    }

}

6.3 声明式事务管理(注解)

1、配置事务管理器和开启事务管理注解

@Configuration
@ComponentScan(basePackages = {"com.study"})
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement // 开启事务
public class SpringConfig {

    @Value("${jdbc.url}")
    private String jdbcUrl;

    @Value("${jdbc.username}")
    private String jdbcUsername;

    @Value("${jdbc.password}")
    private String jdbcPassword;

    @Value("${jdbc.driverClassName}")
    private String jdbcDriverClass;

    /**
     * 创建数据库连接池
     * @return
     */
    @Bean
    public DruidDataSource druidDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(jdbcDriverClass);
        druidDataSource.setUrl(jdbcUrl);
        druidDataSource.setUsername(jdbcUsername);
        druidDataSource.setPassword(jdbcPassword);
        return druidDataSource;
    }
    // 以上代码 基于配置文件 实现连接数据库
    // 相当于 xml中
//        <bean id="druidDataSource" 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>


    /**
     * 事务管理器
     * @return
     */
	@Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
    // 相当于 xml中
//        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
//        <property name="dataSource" ref="druidDataSource"></property>
//    </bean>

    /**
     * 创建JdbcTemplate对象
     * @param dataSource
     * @return
     */
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        //到ioc 容器中 根据类型找到相对应得 DataSource 对象 进行注入
        JdbcTemplate jdbcTemplate=new JdbcTemplate();
        //注入DruidDataSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
    // 以上代码 基于配置文件 实现连接数据库
    // 相当于 xml中
//    <!--    创建JdbcTemplate对象-->
//    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
//<!--       注入 DruidDataSource  数据库连接配置 -->
//        <property name="dataSource" ref="druidDataSource" ></property>
//     </bean>


}

2、使用事务

@Service
// @Transactional 
public class UserService {

    @Autowired
    private UserDao userDao;

    /**
     * 转账操作
     */
    @Transactional
    public void accountMoney(){
        userDao.reduceMoney();
        int i = 10 / 0;
        userDao.addMoney();
    }

}

6.4 事务参数

在这里插入图片描述
1、传播行为 @Transactional(propagation = Propagation.NESTED)

REQUIRED(默认):如果当前没有事务,就新建一个事务,如果已经存在一个事务中,就加入到这个事务中
REQUIRED_NEW:新建一个事务,如果已经存在一个事务中,则将外层事务挂起
SUPPORTS:如果当前没有事务,以非事务方式运行,如果已经存在一个事务中,则加入到这个事务中
NOT_SUPPORTS:如果当前没有事务,以非事务方式运行,如果已经存在一个事务中,则将外层事务挂起
MANDATORY:如果外层事务存在则加入到事务中,否则抛出异常
NEVER:非事务方式运行,如果存在外层事务,则抛出异常
NESTED:如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行与REQUIRED类似的操作

2、隔离级别 @Transactional(isolation = Isolation.REPEATABLE_READ)
事务并发情况下,如果不考虑隔离性会出现三个读问题:
脏读:一个事务读到另外一个事务未提交的数据
不可重复读:一个事务读到另外一个事务已经提交的数据(update),多次读取数据时,发现每次读的都不一样
虚(幻)读:一个事务读取到另外一个提交事务添加数据(insert),一般是批量读取一批数据时发现数据量不一致

通过设置事务的隔离级别来解决以上问题
在这里插入图片描述
3、超时时间 @Transactional(timeout = -1)
事务需要在一定时间内进行提交,如果不提交则进行回滚,默认值是 -1 ,设置时间以秒为单位进行计算

4、是否只读 @Transactional(readOnly = true)
默认值是false,代表即可以查询,也可以增删改,如果设置为true,那么只能查询

5、回滚@Transactional(rollbackFor = RuntimeException.class)
设置出现哪些异常进行事务回滚,默认只在遇到运行时异常和Error时才会回滚,非运行时异常不回滚

6、不回滚 @Transactional(noRollbackFor = RuntimeException.class)
设置出现哪些异常不进行事务回滚

6.5 声明式事务管理(xml)了解

这里主要放置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-aop.xsd">


    <!--直接配置druid连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${prop.driverClassName}"/>
        <property name="url" value="${prop.url}"/>
        <property name="username" value="${prop.username}"/>
        <property name="password" value="${prop.password}"/>
    </bean>

    <!--引入配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--    创建JdbcTemplate对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--       注入 DruidDataSource  数据库连接配置 -->
        <property name="dataSource" ref="dataSource" ></property>
    </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="account*" propagation="REQUIRED" isolation="DEFAULT"/>
        </tx:attributes>
    </tx:advice>

    <!--配置切入点和切面-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="pointcut" expression="execution(* cn.ityang.service.UserService.*(..))"/>
        <!--配置切面-->
        <aop:advisor advice-ref="txadvice" pointcut-ref="pointcut"/>
    </aop:config>

</beans>

七、Spring 新功能

7.1 整合log4j2

1、引入依赖

        <!--  日志相关-->
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.13.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.13.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>

2、 配置文件log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="INFO">
    <!--先定义所有的appender-->
    <appenders>
        <!--输出日志信息到控制台-->
        <console name="Console" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </console>
    </appenders>
    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
    <loggers>
        <root level="info">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>

3、手动输出日志

private static final Logger logger = LoggerFactory.getLogger(UserService.class);

public static void main(String[] args) {
    logger.info("info======");
    logger.debug("debug======");
}

7.2 @Nullable 和 函数式注册对象

1、@Nullable
@Nullable 注解可以使用在方法、属性、参数上,分别表示方法返回可以为空、属性值可以为空、参数值可以为空

2、函数式注册对象

    @Test
    public void testGeneric(){
        GenericApplicationContext context = new GenericApplicationContext();
        context.refresh();
        context.registerBean("user",User.class, () -> new User());
        User user = (User)context.getBean("user");
        System.out.println(user);
    }

7.3 整合单元测试

1、整合Junit4

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>compile</scope>
        </dependency>

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:bean.xml")
public class TestJunit4 {

    @Autowired
    private UserService userService;

    @Test
    public void test01(){
        userService.testJunit4();
    }
}

2、整合Junit5
引入依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>RELEASE</version>
        </dependency>

测试

//@ExtendWith(SpringExtension.class)
//@ContextConfiguration("classpath:bean.xml")
// 复合注解,相当于上面两个注解
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class TestJunit5 {


    @Autowired
    private UserService userService;

    @Test
    public void test01(){
        userService.testJunit5();
    }

}

八、Spring Webflux

8.1 SpringWebflux介绍

1、基本概念
SpringWebflux 是 Spring5 添加的新模块,用于 web 开发的,功能和 SpringMVC 类似,Webflux 使用当前一种比较流行的响应式编程出现的框架。
Webflux 是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet3.1 以后才支持,核心是基于Reactor 的相关 API 实现的。

2、关于异步非阻塞
同步:就是在发出一个调用时,在没有得到结果之前, 该调用就不返回。
异步:调用在发出之后,这个调用就直接返回了,所以没有返回结果。
阻塞:指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞:指在不能立刻得到结果之前,该调用不会阻塞当前线程。

3、Webflux 特点
① 非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以 Reactor 为基础实现响应式编程
② 函数式编程:Spring5 框架基于 java8,Webflux 使用 Java8 函数式编程方式实现路由请求

4、与 SpringMVC 的异同
SpringMVC 采用命令式编程,Webflux 采用异步响应式编程;两个框架都可以使用注解方式,都运行在 Tomcat 等容器中。

8.2 响应式编程

1、基本介绍
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便
地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。

电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1"的公
式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。

2、基于 Java8 简单实现

public class ObserverDemo extends Observable {
    public static void main(String[] args) {
        // 基于观察者模式两个类 Observer 和 Observable
        ObserverDemo observer = new ObserverDemo();
        // 添加观察者
        observer.addObserver((o, arg) -> System.out.println("检测到数据发生变化"));
        observer.addObserver((o, arg) -> System.out.println("收到被观察者通知,准备改变"));
        // 数据发生变化
        observer.setChanged();
        // 通知
        observer.notifyObservers();
    }
}

3、基于 Reactor 简单实现
响应式编程操作中,Reactor 是满足 Reactive 规范框架。
Reactor 有两个核心类,Mono 和 Flux,这两个类实现接口 Publisher,提供丰富操作符。Flux 对象实现发布者,返回 N 个元素;Mono 实现发布者,返回 0 或者 1 个元素。
Flux 和 Mono 都是数据流的发布者,使用 Flux 和 Mono 都可以发出三种数据信号:元素值,错误信号,完成信号,错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流,同时把错误信息传递给订阅者。

三种数据信号特点
① 错误信号和完成信号都是终止信号,不能共存的
② 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流
③ 如果没有错误信号,没有完成信号,表示是无限数据流

常用操作符 map、flatMap、filter、zip

引入依赖

<dependency>
	<groupId>io.projectreactor</groupId>
	<artifactId>reactor-core</artifactId>
	<version>3.1.5.RELEASE</version>
</dependency>

代码示例

public class ReactorDemo {
    public static void main(String[] args) {
        // 直接发布元素
        Flux.just(1,2,3).subscribe(System.out::print); // 123
        System.out.println();
        Mono.just(0).subscribe(System.out::print); // 0
        System.out.println();
        // 数组
        Integer[] arr = {1,2,3,4};
        Flux.fromArray(arr).subscribe(System.out::print); // 1234
        System.out.println();
        // 集合
        Flux.fromIterable(Arrays.asList(arr)).subscribe(System.out::print); // 1234
        System.out.println();
        // stream流
        Flux.fromStream(Arrays.asList(arr).stream()).subscribe(System.out::print); // 1234

    }
}

8.3 SpringWebflux 执行流程和核心 API

SpringWebflux 基于 Reactor,默认使用容器是 Netty,Netty 是高性能的 NIO 框架,异步非阻塞的框架
1、执行流程

  • SpringWebflux 核心控制器 DispatchHandler,实现接口 WebHandler
  • 接口 WebHandler 有一个方法 handle()
// 参数中放http请求响应信息
    public Mono<Void> handle(ServerWebExchange exchange) {
        if (this.handlerMappings == null) {
            return this.createNotFoundError();
        } else {
        // 根据请求地址获取对应的mapping
            return CorsUtils.isPreFlightRequest(exchange.getRequest()) ? this.handlePreFlight(exchange) : Flux.fromIterable(this.handlerMappings).concatMap((mapping) -> {
                return mapping.getHandler(exchange);
            }).next().switchIfEmpty(this.createNotFoundError()).flatMap((handler) -> {
            // 调用具体的业务方法
                return this.invokeHandler(exchange, handler);
            }).flatMap((result) -> {
            // 处理结果返回
                return this.handleResult(exchange, result);
            });
        }
    }
  • handle() 负责请求的处理,首先根据请求地址获取对应的 HandlerMapping
  • 根据HandlerMapping 获取HandlerAdapter 负责请求的处理
  • HandlerResultHandler 响应结果处理

2、相关API
SpringWebflux 实现函数式编程,有两个接口:
RouterFunction(路由处理)
HandlerFunction(处理函数)

8.4 SpringWebflux(基于注解编程模型)

1、创建springboot工程
2、引入相关依赖

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

3、编写代码
controller

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user/{id}")
    public Mono<User> getUserId(@PathVariable int id) {
        return userService.getUserById(id);
    }

    @GetMapping("/user")
    public Flux<User> getUsers() {
        return userService.getAllUser();
    }

    @PostMapping("/save/user")
    public Mono<Void> saveUser(@RequestBody User user) {
        Mono<User> userMono = Mono.just(user);
        return userService.saveUserInfo(userMono);
    }
}

service

public interface UserService {
    /**
     * 根据 id 查询用户
     * @param id
     * @return
     */
    Mono<User> getUserById(int id);

    /**
     * 查询所有用户
     * @return
     */
    Flux<User> getAllUser();

    /**
     * 添加用户
     * @param user
     * @return
     */
    Mono<Void> saveUserInfo(Mono<User> user);

}

@Service
public class UserServiceImpl implements UserService {
    private final Map<Integer, User> users = new HashMap<>();

    public UserServiceImpl(){
        this.users.put(1, new User("lucy", "nan", 20));
        this.users.put(2, new User("mary", "nv", 30));
        this.users.put(3, new User("jack", "nv", 50));
    }

    @Override
    public Mono<User> getUserById(int id) {
        return Mono.justOrEmpty(this.users.get(id));
    }

    @Override
    public Flux<User> getAllUser() {
        return Flux.fromIterable(this.users.values());
    }

    @Override
    public Mono<Void> saveUserInfo(Mono<User> user) {
        return user.doOnNext(u -> {
            int id = users.size() + 1;
            users.put(id, u);
        }).thenEmpty(Mono.empty());
    }
}

entity

public class User {
    private String name;
    private String gender;
    private Integer age;

    public User() {
    }

    public User(String name, String gender, Integer age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public String getGender() {
        return gender;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

4、与SpringMVC的区别
SpringMVC 方式实现,同步阻塞的方式,基于 SpringMVC+Servlet+Tomcat
SpringWebflux 方式实现,异步非阻塞 方式,基于 SpringWebflux+Reactor+Netty

8.5 SpringWebflux(基于函数式编程模型)

1、函数式编程与注解方式的区别
① 在使用函数式编程模型操作时候,需要自己初始化服务器
② 基于函数式编程模型时候,有两个核心接口:RouterFunction(实现路由功能,请求转发
给对应的 handler)和 HandlerFunction(处理请求生成响应的函数)。核心任务定义两个函数
式接口的实现并且启动需要的服务器
③ SpringWebflux 请 求 和 响 应 不 再 是 ServletRequest 和 ServletResponse ,而是ServerRequest 和 ServerResponse

2、编写service 与 entity
service

public interface UserService {
    /**
     * 根据 id 查询用户
     * @param id
     * @return
     */
    Mono<User> getUserById(int id);

    /**
     * 查询所有用户
     * @return
     */
    Flux<User> getAllUser();

    /**
     * 添加用户
     * @param user
     * @return
     */
    Mono<Void> saveUserInfo(Mono<User> user);

}

@Service
public class UserServiceImpl implements UserService {
    private final Map<Integer, User> users = new HashMap<>();

    public UserServiceImpl(){
        this.users.put(1, new User("lucy", "nan", 20));
        this.users.put(2, new User("mary", "nv", 30));
        this.users.put(3, new User("jack", "nv", 50));
    }

    @Override
    public Mono<User> getUserById(int id) {
        return Mono.justOrEmpty(this.users.get(id));
    }

    @Override
    public Flux<User> getAllUser() {
        return Flux.fromIterable(this.users.values());
    }

    @Override
    public Mono<Void> saveUserInfo(Mono<User> user) {
        return user.doOnNext(u -> {
            int id = users.size() + 1;
            users.put(id, u);
        }).thenEmpty(Mono.empty());
    }
}

entity

public class User {
    private String name;
    private String gender;
    private Integer age;

    public User() {
    }

    public User(String name, String gender, Integer age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public String getGender() {
        return gender;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

3、编写 Handler

public class UserHandler {
    private final UserService userService;

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

    /**
     * 根据 id 查询
     * @param request
     * @return
     */
    public Mono<ServerResponse> getUserById(ServerRequest request) {
        //获取 id 值
        int userId = Integer.valueOf(request.pathVariable("id"));
        //空值处理
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();
        //调用 service 方法得到数据
        Mono<User> userMono = this.userService.getUserById(userId);
        //把 userMono 进行转换返回
        //使用 Reactor 操作符 flatMap
        return userMono.flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(fromObject(person)))
                .switchIfEmpty(notFound);
    }

    /**
     * 查询所有
     */
    public Mono<ServerResponse> getAllUsers(ServerRequest request) {
        //调用 service 得到结果
        Flux<User> users = this.userService.getAllUser();
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users, User.class);
    }

    /**
     * 添加
     * @param request
     * @return
     */
    public Mono<ServerResponse> saveUser(ServerRequest request) {
        //得到 user 对象
        Mono<User> userMono = request.bodyToMono(User.class);
        return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
    }
}

4、编写路由 和 适配器,并且初始化服务

public class Service {
    //1 创建 Router 路由
    public RouterFunction<ServerResponse> routingFunction() {
        //创建 hanler 对象
        UserService userService = new UserServiceImpl();
        UserHandler handler = new UserHandler(userService);
        //设置路由
        return RouterFunctions.route(
                GET("/users/{id}").and(accept(APPLICATION_JSON)),handler::getUserById)
                .andRoute(GET("/users").and(accept(APPLICATION_JSON)),handler::getAllUsers);
    }
    //2 创建服务器完成适配
    public void createReactorServer() {
        //路由和 handler 适配
        RouterFunction<ServerResponse> route = routingFunction();
        HttpHandler httpHandler = toHttpHandler(route);
        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter( httpHandler);
        //创建服务器
        HttpServer httpServer = HttpServer.create();
        httpServer.handle(adapter).bindNow();
    }
    public static void main(String[] args) throws Exception{
        Service server = new Service();
        server.createReactorServer();
        System.out.println("enter to exit");
        System.in.read();
    }
}

到这里位置已经编写完毕,可以启动之后用地址访问接口

5、使用 WebClient 调用

public class Client {
    public static void main(String[] args) {
        //调用服务器地址
        WebClient webClient = WebClient.create("http://127.0.0.1:62825");
        //根据 id 查询
        String id = "1";
        User userresult = webClient.get().uri("/users/{id}", id).accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block();
        System.out.println(userresult.getName());
        //查询所有
        Flux<User> results = webClient.get().uri("/users").accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);
        results.map(stu -> stu.getName()).buffer().doOnNext(System.out::println).blockFirst();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值