Spring框架

193 阅读13分钟

一、Spring概述

1.Spring是一个控制反转和面向切面的轻量级的开源的JavaEE框架

2.Spring可以解决企业应用开发的复杂性

3.Spring有两个核心部分:IOC和AOP

(1)IOC:控制反转,把创建对象的过程交给Spring管理
(2)AOP:面向切面编程,不修改源代码的情况下进行功能的增强和增加

4.Spring特点

(1)方便解耦,简化开发
(2)AOP编程的支持
(3)声明式事务的支持
(4)方便程序的测试
(5)方便和其他框架进行整合
(6)降低Java EE API的使用难度
(7)Java 源码是经典学习范例

二、IOC底层原理

1.xml解析、工厂模式、反射

2.使用IOC的目的:降低耦合度

原始方式:一个类调用另外一个类的对象使用 图片.png 工厂模式:解耦,但不是完全解耦 图片.png

3.IOC过程

图片.png

4.IOC接口

(1)IOC思想

基于IOC容器完成

(2)IOC容器底层

对象工厂

(3)Spring提供IOC容器实现两种方式:(两个接口)

BeanFactory:IOC容器基本实现,是Spring 内部的使用接口,不提供开发人员进行使用
"加载配置文件的时候不会创建对象,在获取对象(也就是说在使用的时候)才会去创建对象"(感觉像懒汉式)

ApplicationContext,是BeanFactory 接口的子接口,提供更多强大的功能,一般由开发人员使用
“加载配置文件的时候会把在配置文件中的对象进行创建”(感觉像饿汉式)

(4)ApplicationContext 接口的实现类

图片.png

5.IOC操作Bean管理(基于xml配置文件方式实现Spring框架中创建对象和注入属性)(参考大佬来源:blog.csdn.net/Tough_dxx/a…)

(1)基于xml方式创建对象

图片.png

1.在Spring框架配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象的创建
2.在bean标签中有很多属性,常用的属性介绍:id属性——唯一标识\ class属性——类全路径
3.创建对象时默认执行无参构造方法完成对象的创建

(2)基于xml方式注入属性

先了解一下DI:依赖注入,也就是注入属性

第一种注入方式:使用set方法进行注入
第一步:
图片.png
第二步:
图片.png

第二种注入方式:使用有参构造进行注入
第一步:
图片.png
第二步:
图片.png

6.IOC操作bean管理

(1)、Spring有两种类型 bean,一种普通bean,另外一种工厂bean (FactoryBean)

(2)、普通bean:在配置文件中定义bean类型就是返回类型。

(3)、工厂bean:在配置文件定义bean类型可以和返回类型不一样

第一步创建类,让这个类作为工厂bean,实现接口FactoryBean 第二步实现接口里面的方法,在实现的方法中定义返回的bean类型

(4)自动装配

(5)外部属性文件

7.bean的作用域

  1. 在Spring容器中,Bean包含五种作用域:singleton、prototype、request、session、globalSession。
  2. 在Spring里面,默认情况下,bean是单实例

图片.png

图片.png 3. 如何设置多实例

1.在Spring配置文件bean标签中有属性scope用于设置单实例还是多实例

2.默认值——singleton(单实例) prototype(多实例)

单实例和多实例的区别?

单实例是在加载Spring配置文件时候就会创建单实例对象

多实例不是在加载配置文件时创建对象,而是在调用getBean方法时创建对象

8.bean的生命周期

  1. 生命周期——指对象从创建到销毁的过程
  2. bean的生命周期
  • 通过构造器创建bean实例(无参构造)
  • 为bean的属性设置值和对其他bean引用(使用set方式)
  • 调用bean的初始化方法(需要进行配置)
  • 获取bean对象(bean可以使用了)
  • 当容器关闭时,调用bean的销毁方法destroy(需要配置销毁方法)
public class Orders {
    //无参数构造 
    public Orders() { 
        System.out.println("第一步 执行无参数构造创建bean实例"); 
    } 
    private String oname; 
    
    public void setOname(String oname) { 
        this.oname = oname; 
        System.out.println("第二步 调用set方法设置属性值"); 
    } 
    //创建执行的初始化的方法 
    public void initMethod() { 
        System.out.println("第三步 执行初始化的方法"); 
    } 
    //创建执行的销毁的方法 
    public void destroyMethod() { 
        System.out.println("第五步 执行销毁的方法"); 
    } 
}

<bean id="orders" class="com.example.controller1.spring.Orders" init-method="initMethod"     destroy-method="destroyMethod"> 
    <property name="oname" value="芜湖"></property> 
</bean>

@Test 
public void testBean3() { 
    ClassPathXmlApplicationContext context = new     ClassPathXmlApplicationContext("User.xml"); Orders orders = context.getBean("orders",     Orders.class); 
    System.out.println("第四步 获取创建bean实例对象"); 
    System.out.println(orders); 
    //手动让bean实例销毁 
    context.close(); 
}

9.基于注解实现IOC操作Bean管理

(1)什么是注解

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

(2)Spring针对bean管理中创建对象提供的注解

  1. @Commponent——放在类上,把普通类实例化到spring容器中。
  2. @Service——一般使用在Service层
  3. @Controller——一般使用在Web层
  4. Repository——一般使用在Dao层

第一步:引入依赖

图片.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:p="http://www.springframework.org/schema/p"
       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/beans/spring-context.xsd">

    <!--开启组件扫描 1 如果扫描多个包,多个包使用逗号隔开 2 扫描包上层目录 -->
<context:component-scan base-package="com.example"></context:component-scan>

图片.png

第三步:创建类,在类上面添加创建对象注解

//在注解里面value属性值可以省略不写, 
//默认值是类名称,首字母小写 
//即 UserService -- userService
@Component(value = "userService") //<bean id="userService"   class=".."/> 
public class UserService { 
    public void add() { 
        System.out.println("service add"); 
    } 
}

(3)使用注解方式实现属性的注入

@Autowired:根据属性类型进行自动装配

@Service 
public class UserService { 
    //定义dao类型属性 
    //不需要添加set方法
    //添加注入属性注解 
    @Autowired 
    private UserDao userDao; 
    public void add() { 
        System.out.println("service add"); 
        userDao.add(); 
    }
    

@Qualifier:根据名称进行注入 这个@Qualifier注解的使用,和上面@Autowired一起使用

//定义dao类型属性 
//不需要添加set方法
//添加注入属性注解 
@Autowired 
//根据类型进行注入 因为userDao接口的实现类可以有很多个
@Qualifier(value = "userDaoImpl1") 
//根据名称进行注入 
private UserDao userDao;

@Resource:可以根据类型注入,可以根据名称注入

@Resource //根据类型进行注入 

@Resource(name = "userDaoImpl1") //根据名称进行注入
private UserDao userDao;

@Value:注入普通类型属性

@Value(value = "lallala") 
private String name;

(4)纯注解开发

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

@Configuration//作为配置类,替代xml配置文件
@ComponentScan(basePackages = {"com.example"})
public class SpringConfig{}

2.编写测试类

@Test
public void testService2(){
//加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService",UserService.class);
System.out. println(userService) ;
userService.add() ;
}

三、AOP

1.什么是AOP

(1)面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
(2)不修改源代码的情况下,在主干功能里添加新功能。

2.AOP底层原理

(1)AOP底层使用动态代理

有两种情况动态代理

第一种:有接口情况,使用JDK动态代理

  • 创建接口实现类代理对象,增强类的方法 图片.png

第二种:没有接口情况,使用CGLIB动态代理

  • 创建子类的代理对象,增强类的方法 图片.png

(2)JDK动态代理(使用Proxy类里面的方法创建代理对象)JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口

其中有个newProxyInstance方法 图片.png 方法有三个参数:
第一参数,类加载器
第二参数,增强方法所在的类,这个类实现的接口,支持多个接口
第三参数,实现这个接口InvocationHandler,创建代理对象,写增强的

第一步:创建接口

 public interface Person {
   //交作业
  void giveTask();
 }

第二步:创建需要被代理的实际类

public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }
 
    public void giveTask() {
        System.out.println(name + "交作业");
    }
}

第三步:使用Proxy 类创建接口代理对象

//Proxy中有个InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。
public class StuInvocationHandler<T> implements InvocationHandler {
    //invocationHandler持有的被代理对象
    T target;

    public StuInvocationHandler(T target) {
        this.target = target;
    }
 
    /**
     * proxy:代表动态代理对象
     * method:代表正在执行的方法
     * args:代表调用目标方法时传入的实参
     */
    public Object invoke(Object proxy, Method     method, Object[] args) throws Throwable {
        System.out.println("代理执行" +method.getName() + "方法");
        Object result = method.invoke(target,     args);
        return result;
    }
}

第四步:测试

public class Test {
    public static void main(String[] args) {
 
        //创建一个实例对象,这个对象是被代理的对象
        Person linqian = new Student("aaa");
 
        //创建一个与代理对象相关联的InvocationHandler
        InvocationHandler stuHandler = new StuInvocationHandler<Person>(bbb);
 
        //创建一个代理对象stuProxy来代理linqian,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
 
        //代理执行交作业的方法
        stuProxy.giveTask();
    }
}

(3)CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

(4)AOP术语

  • Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。(把通知应用到切入点过程)
  • Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。(类里面哪些方法可以被增强)
  • Pointcut(切入点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。(实际被真正增强的方法)
  • Advice(增强或者叫通知):(实际增强的逻辑部分)
    前置通知:@Before 在目标业务方法执行之前执行
    后置通知:@After 在目标业务方法执行之后执行
    返回通知:@AfterReturning 在目标业务方法返回结果之后执行
    异常通知:@AfterThrowing 在目标业务方法抛出异常之后
    环绕通知:@Around 功能强大,可代替以上四种通知,还可以控制目标业务方法是否执行以及何时执行
  • Target(目标对象):织入 Advice 的目标对象.。 Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

(5)切入点表达式

Pointcut("execution(* com.cn.springaop.service...* . * (...))"):  第一个表示匹配任意的方法返回值,上面的第一个..表示service包及其子包,第二个表示所有类,第三个*表示所有方法,第二个..表示方法的任意参数个数

四.事务

1.概念

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

2.事务四个特性

  • 原子性:事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
  • 一致性:事务在完成时,必须是所有的数据都保持一致状态。
  • 隔离性:并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性。
  • 持久性:一旦事务完成,数据库的改变必须是持久化的。

3.事务管理操作

(1)编程式 (2)声明式(一般使用这个)

声明式事务管理(两个)

  • 基于注解方式
    (1)在spring配置文件配置事务管理器
<!--配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
     <!--数据库连接地址-->
     <property name="url" value="xxx"/> 
     <!--数据库的用户名-->
     <property name="username" value="xxx"/>
     <!--数据库的密码-->
     <property name="password" value="xxx"/>
     <!--数据库驱动-->
     <property name="driverClassName" value="xxx"/>
 </bean>
 <!--配置事务管理器,以 JDBC 为例-->
 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <property name="dataSource" ref="dataSource"></property>
 </bean>

(2)在spring中配置文件,开启事务注解

引入 tx 命名空间
<?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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.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
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--开启事务注解-->
<tx; annotation-driven transaction manager="transactionManager"></tx;annotation-driven>

(3).在service类上面(或者service类里面方法上面)添加事务注解@Transactional

(1)@Transactional,这个注解添加到类上面,也可以添加方法上面 (2)如果把这个注解添加类上面,这个类里面所有的方法都添加事务 (3)如果把这个注解添加方法上面,为这个方法添加事务

(4)事务的传播行为

图片.png

(5)事务的隔离级别

1)事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
2)有三个读问题:脏读、不可重复读、虚(幻)读

  • 1.脏读:一个事务读到另一个事务未提交的更新数据。
  • 2.不可重复读:一个事务两次读同一行数据,可是这两次读到的数据不一样。
  • 3.幻读:一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行。
  1. READ UNCOMMITTED:读未提交。
  2. READ COMMITTED:读已提交。
  3. REPEATABLE READ:可重复读。
  4. SERIALIZABLE:序列化。

图片.png

  • 基于xml配置文件方式
    (1)配置事务管理器

基于注解中的(1)和(2)中的引入 tx 命名空间部分

(2)配置事务通知

<!--配置通知-->
<tx:advice id="tx-advice" transaction-manager="transactionManager">
    <!--配置事务参数-->
    <tx:attributes>
        <tx:method name="create*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/>
    </tx:attributes>
</tx:advice>

(3)配置切入点和切面

<!--配置切点和切面-->
<aop:config>
    <!--配置切点-->
    <aop:pointcut id="tx-pt" expression="execution(* net.biancheng.c.service.impl.OrderServiceImpl.*(..))"/>
    <!--配置切面-->
    <aop:advisor advice-ref="tx-advice" pointcut-ref="tx-pt"></aop:advisor>
</aop:config>

4、在 Spring进行声明式事务管理,底层使用AOP原理

5、Spring事务管理API

提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类

五.spring5框架新功能

1.整个spring5框架的代码基于Java8,运行是兼容jdk9,许多不建议使用的类和方法在代码库中删除

2.spring5框架自带了通用日志封装

  • (1)Spring5已经移除Log4jConfigListerer,官方建议使用Log4j2
  • (2)Spring5框架整合Log4j2

第一步:引入jar包

图片.png

第二步:编写配置文件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>

第三步: 测试类

public class TestLog {

    @Test
    public void test_logbymyself(){
        Logger logger = LoggerFactory.getLogger(TestLog.class);
        logger.info("log by myself grade is info");
        logger.warn("warn");
    }
}

3.支持@Nullable注解

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

4.函数式风格GenericApplicationContext/AnnotationConfigApplicationContext

5.整合Junit5

第一步:引入Junit依赖
第二步:创建测试类,使用注解

图片.png

6.使用Spring WebFlux执行集成测试

1、Spring Webflux介绍
(1) 是Spring5添加新的模块,用于 web 开发的,功能和EpringMVC.类似的,Webflux.使用当前一种比较流程响应式编程出现的框架。
(2)使用传统web框架,比如SpringMVC,这些基于Servlet,容器,Webflux是一种异步非阻塞的框架,异步非阻塞的框架在Servlet3.1以后才支持,核心是基于Reactor的相关 API实现的(3)Webflux

  • 第一非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以Reactor为基础实现响应式编程
  • 第二函数式编程: Spring5框架基于java8,Webflux使用Java8函数式编程方式实现路由请求
    (4)比较springmvc 图片.png

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

第一步:引入依赖

图片.png

第二步:基本特点

图片.png