Spring Framework 5

336 阅读17分钟

image.png

一、引言

1.1 原生Web开发中存在哪些问题

传统Web开发存在硬编码所造成的过度程序耗合(例如:Service中作为属性Dao对象)。

部分Java EE API较为复杂,使用效率低(例如:JDBC开发步骤)。

侵入性强,移植性差(例如:DAO实现的更换,从Connection到sqlSession)。

二、Spring框架

2.1 概念

  • Spring是一个项目管理框架,同时也是一套Java EE解决方案。
  • Spring是众多优秀设计模式的组合(工厂、单例、代理、适配器、包装器、观察者、模板、策略)。
  • Spring并未替代现有框架产品,而是将众多框架进行有机整合,简化企业级开发,俗称“胶水框架”。

2.2 访问与API文档

官网:spring.io/

API文档:docs.spring.io/spring-fram…

三、Spring架构的组成

Spring架构由诸多模块组成,可分类为

  • 核心技术:依赖注入,事件,资源,i18n,验证,数据绑定,类型转换,SpEL,AOP
  • 测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient
  • 数据访问:事务,DAO支持,JDBC,ORM,封送XML
  • Spring MVC 和 Spring WebFlux Web框架
  • 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
  • 语言:Kotlin,Groovy,动态语言。
Spring架构组成
image.png
GroupldArtifactid说明
org-springframeworkspring-beansBeans支持,包含Groovy
org-springframeworkspring-aop基于代理的AOP支持
org.springframeworkspring-aspects基于AspectJ的切面
org.springframeworkspring-context应用上下文运行时,包括调度和远程抽象
org-springframeworkspring-context-support支持将常见的第三方类库集成到Spring应用上下文
org.springframeworkspring-core其他模块所依赖的核心模块
org-springframeworkspring-expressionSpring表达式语言,SpEL
org.springframeworkspring-instrumentJVM引导的仪表(监测器)代理
org.springframeworkspring-instrument-tomcatTomcat的仪表(监测器)代理
org-springframeworkspring-jdbc支持包括数据源设置和JDBC访问支持
org.springframeworkspring-jms支持包括发送/接收JMS消息的助手类
org-springframeworkspring-messaging对消息架构和协议的支持
org.springframeworkspring-om对象/关系映射,包括对JPA和Hibernate的支持
org.springframeworkspring oxm对象/XML映射(Object/XML Mapping, OXM)
org.springframeworkspring-test单元测试和集成测试支持组件
org.springframeworkspring-tx事务基础组件,包括对DAO的支持及JCA的集成
org-springframeworkspring-webweb支持包,包括客户端及web远程调用
org.springframeworkspring-webmvcREST web服务及web应用的MVC实现
org.springframeworkspring-webmvc-portlet用于Portlet环境的MVC实现
org.springframeworkspring-websocketWebSocket和SockJS实现,包括对STOMP的支持
org.springframeworkspring-jclJakarta Commons Logging日志系统

四、Spring环境搭建

4.1 导入依赖

<!-- Spring常用依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>

4.2 创建Spring配置文件

命名无限制,约定俗成命名有:spring-context.xml、applicationContext.xml、beans.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">
</beans>

五、Spring工厂编码

定义目标Bean类型

public interface UserDAO {
    public void deleteUser(Integer id);
}

public class UserDAOImpl implements UserDAO{
    @Override
    public void deleteUser(Integer id) {
        System.out.println("delete User in DAO");
    }
}

spring-context.xml中的<beans>内部配置bean标签

<bean id="userDAO" class="com.techoc.dao.UserDAOImpl"></bean>

调用Spring工厂API(ApplicationContext接口)

    @Test
    public void testSpringFactory(){
        // 启动工厂
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
        // 获取对象
        UserDAO userDAO = (UserDAO)context.getBean("userDAO");
        userDAO.deleteUser(1);
//        context.close();
    }

六、依赖与配置文件详解

6.1 Spring依赖关系

Spring框架包含多个模块,每个模块各司其职,可结合需求引入相关依赖Jar包实现功能。

image.png

  • 注意:Jar包彼此存在依赖,只需引入最外层Jar即可由Maven自动将相关依赖Jar引入到项目中。

6.2 schema

配置文件中的顶级标签中包含了语义化标签的相关信息

  • xmlns:语义化标签所在的命名空间。
  • xmlns:xsi:XMLSchema-instance标签遵循Schema标签标准。
  • xsi:schemaLocation:xsd文件位置,用以描述标签语义、属性、取值范围等。

七、IoC(Inversion of Control)控制反转【重点】

Inverse Of Controll:控制反转

反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。(变主动为被动,即反转)

解决了具有依赖关系的组件之间的强精合,使得项目形态更加稳健

7.1 项目中的强耦合问题

public class UserServiceImpl implements UserService{

    // 强耦合了UserDAOImpl 使得UserServiceImpl变得不稳健
    private UserDAO userDAO = new UserDAOImpl();
    @Override
    public void deleteUser(Integer id) {
        System.out.println("delete User in Service");
        userDAO.deleteUser(id);
    }

7.2 解决方案

// 不引用任何一个具体的组件(实现类),在需要其他组件的位置预留存取值入口(set/get)
public class UserServiceImpl implements UserService{

    // 满足依赖关系  强耦合
    private UserDAO userDAO;
    @Override
    public void deleteUser(Integer id) {
        System.out.println("delete User in Service");
        userDAO.deleteUser(id);
    }

    public UserDAO getUserDAO() {
        return userDAO;
    }

    public void setUserDAO(UserDAO userDAO) {
        this.userDAO = userDAO;
    }
}
<bean id="userDAO" class="com.techoc.dao.UserDAOImpl"></bean>
<bean id="userService" class="com.techoc.service.UserServiceImpl">
    <!-- userDAO属性赋值, 值是id为useDAO的一个bean
         注入:属性值  依赖关系
         依赖注入
    -->
            <property name="userDAO" ref="userDAO"/>
</bean>

此时,如果需要更换其他UserDAO实现类,则UserServicelmpl不用任何改动!

则此时的UserServicelmpl组件变得更加稳健!

八、DI(Dependency Injection)依赖注入【重点】

8.1 概念

在Spring创建对象的同时,为其属性赋值,称之为依赖注入。

8.2 Set注入

创建对象时,Spring工厂会通过Set方法为对象的属性赋值。

8.2.1 定义目标Bean类型

public class User {
    private Integer id;
    private String password;
    private String sex;
    private Integer age;
    private Date bornDate;
    private String[] hobbys;
    private Set<String> phones;
    private List<String> names;
    private Map<String,String> countries;
    private Properties files;
    //get and set
}

8.2.2 基本类型+字符串类型+日期类型

<bean id="user" class="com.techoc.entity.User">

    <!-- 简单:jdk8种基本数据类型  String  Date -->
    <property name="id" value="10"/>
    <property name="password" value="123abc"/>
    <property name="sex" value="male"/>
    <property name="age" value="19"/>
    <property name="bornDate" value="2020/12/12 12:20:30"/>
</bean>

8.2.3 容器类型

<bean id="user" class="com.techoc.entity.User">
    
    <!-- List -->
    <property name="names">
        <list>
            <value>tom</value>
            <value>jack</value>
        </list>
    </property>
    
    <!-- set -->
    <property name="phones">
        <set>
            <value>131111111</value>
            <value>131222222</value>
        </set>
    </property>
    
    <!-- map -->
    <property name="countries">
        <map>
            <entry key="zh" value="china"></entry>
            <entry key="en" value="english"></entry>
        </map>
    </property>
    
    <!-- Properties -->
    <property name="files">
        <props>
            <prop key="url">jdbc:mysql:xxx</prop>
            <prop key="username">root</prop>
        </props>
    </property>
</bean>

8.2.4 自建类型

<!-- 次要bean 被作为属性 -->
<bean id="addr" class="com.techoc.entity.Address">
    <property name="id" value="1"/>
    <property name="city" value="北京"/>
</bean>

<bean id="user" class="com.techoc.entity.User">
    <!-- 自建类型 -->
    <property name="address" ref="addr"/>
</bean>

8.3 构造注入

创建对象时,Spring工厂会通过构造方法为对象的属性赋值。

8.3.1 定义目标Bean类型

public class User {
    private Integer id;
    private String password;
    private String sex;
    private Integer age;
    
    //构造方法
    public User(Integer id, String password, String sex, Integer age) {
        this.id = id;
        this.password = password;
        this.sex = sex;
        this.age = age;
    }
}

8.3.2 注入

<!-- 构造注入 -->
<bean id="student" class="com.techoc.entity.Student">
    <constructor-arg name="id" value="1"/>
    <constructor-arg name="name" value="shine"/>
    <constructor-arg name="sex" value="male"/>
    <constructor-arg name="age" value="19"/>
</bean>

8.4 自动注入

不用在配置中指定为哪个属性赋值,及赋什么值.

由spring自动根据某个“原则”,在工厂中查找一个bean,为属性注入属性值

<bean id="userDAO" class="com.techoc.dao.UserDAOImpl"/>
<!-- 基于类型注入 -->
<bean id="userService" class="com.techoc.service.UserServiceImpl" autowire="byType">
</bean>
<bean id="userDAO" class="com.techoc.dao.UserDAOImpl"/>
<!-- 基于名称注入 -->
<bean id="userService" class="com.techoc.service.UserServiceImpl" autowire="byName">
</bean>

九、Bean的一些细节

9.1 控制简单对象的单例、多例模式

配置<bean scope="singleton | prototype"/>

  • singleton(默认):每次调用工厂,得到的都是同一个对象
  • prototype:每次调用工厂,都会创建新的对象
<!--
    singleton(默认):每次调用工厂,得到的都是同一个对象。
    prototype:每次调用工厂,都会创建新的对象。
-->
<bean id="userService" class="com.techoc.service.UserServiceImpl" autowire="byType" scope="prototype">
  • 注意:需要根据场景决定对象的单例、多例模式
  • 可以共用:Service、DAO、SqlSessionFactory(或者是所有的工厂)
  • 不可共用:Connection、SqlSession、ShoppingCart

9.2 FactoryBean创建复杂对象

作用:让Spring可以创建复杂对象、或者无法直接通过反射创建的对象。

image.png

9.2.1 实现FactoryBean接口

image.png

  • 注意:isSingleton方法的返回值,需根据所创建对象的特点决定返回true/false。
  • 例如:Connection不应该被多个用户共享,返回false。
  • 例如:SqlSessionFactory重量级资源,不该过多创建,返回true。

9.2.2 配置Spring-Context.xml

image.png

9.2.3 特例

image.png

十、Spring工厂特性

10.1 饿汉式创建优势

工厂创建之后,会将Spring配置文件中的所有对象都创建完成(饿汉式)。

提高程序运行效率。避免多次10,减少对象创建时间。(概念接近连接池,一次性创建好,使用时直接获取)

10.2 生命周期方法

  • 自定义初始化方法:添加“init-method"属性,Spring则会在创建对象之后,调用此方法。

  • 自定义销毁方法:添加“destroy-method"属性,Spring则会在销毁对象之前,调用此方法。

  • 销毁:工厂的close()方法被调用之后,Spring会毁掉所有已创建的单例对象。

  • 分类:Singleton对象由Spring容器销毁、Prototype对象由JVM销毁。

10.3 生命周期注解

初始化注解、销毁注解

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class Test {

    //初始化
    @PostConstruct
    public void init() {
        System.out.println("init method executed");
    }

    //销毁
    @PreDestroy
    public void destroy() {
        System.out.println("destroy method executed");
    }
}

10.4 生命周期阶段

单例bean:singleton

随工厂启动创建构造方法set方法(注入值)init(初始化)构建完成随工厂关闭销毁

多例bean:prototype 被使用时创建构造方法set方法(注入值)init(初始化)构建完成JVM垃圾回收销毁

十一、代理设计模式

11.1 概念

将核心功能与辅助功能(事务、日志、性能监控代码)分离,达到核心业务功能更纯粹、辅助业务功能可复用。

image.png

11.2 静态代理设计模式

通过代理类的对象,为原始类的对象(目标类的对象)添加辅助功能,更容易更换代理实现类、利于维护。

image.png

  • 代理类=实现原始类相同接口+添加辅助功能+调用原始类的业务方法。
  • 静态代理的问题
    • 代理类数量过多,不利于项目的管理。
    • 多个代理类的辅助功能代码穴余,修改时,维护性差。

11.3 动态代理设计模式

动态创建代理类的对象,为原始类的对象添加辅助功能。

11.3.1 JDK动态代理实现(基于接口)

public class DynamicProxyTest {

    @Test
    public void testJDK() {

        // 目标
        FangDongService fangDongService = new FangDongServiceImpl();

        // 额外功能 基于接口
        InvocationHandler ih = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 辅助功能、额外功能
                System.out.println("发布租房信息1");
                System.out.println("带租客看房1");
                // 核心
                fangDongService.zufang();
                return null;
            }
        };

        // 动态生成 代理类
        FangDongService proxy = (FangDongService) Proxy.newProxyInstance(
                DynamicProxyTest.class.getClassLoader(),
                fangDongService.getClass().getInterfaces(),
                ih
        );

        proxy.zufang();
    }
}

11.3.2 CGlib动态代理实现(基于继承)

public class DynamicProxyTest {

    @Test
    public void testCGLIB() {
        // 目标
        FangDongService fangDongService = new FangDongServiceImpl();

        //
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(FangDongServiceImpl.class);
        enhancer.setCallback(new org.springframework.cglib.proxy.InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                // 辅助功能、额外功能
                System.out.println("发布租房信息2");
                System.out.println("带租客看房2");
                // 核心
                fangDongService.zufang();
                return null;
            }
        });

        // 动态生成代理类
        FangDongServiceImpl proxy = (FangDongServiceImpl) enhancer.create();

        proxy.zufang();
    }
}

十二、AOP面向切面编程【重点】

12.1 概念

AOP (Aspect Oriented Programming),即面向切面编程,利用一种称为"横切"的技术,

剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。

所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,

便于减少系统的重复代码,降低模块之间的藕合度,并有利于未来的可操作性和可维护性。

12.2 AOP开发术语

  • 连接点(Joinpoint):连接点是程序类中客观存在的方法,可被Spring拦截并切入内容。
  • 切入点(Pointcut):被Spring切入连接点。
  • 通知、增强(Advice):可以为切入点添加额外功能,分为:前置通知、后置通知、异常通知、环绕通知等。
  • 目标对象(Target):代理的目标对象
  • 引介(Introduction):一种特殊的增强,可在运行期为类动态添加Field和Method。
  • 织入(Weaving):把通知应用到具体的类,进而创建新的代理类的过程。
  • 代理(Proxy):被AOP织入通知后,产生的结果类。
  • 切面(Aspect):由切点和通知组成,将横切逻辑织入切面所指定的连接点中。

12.3 作用

Spring的AOP编程即是通过动态代理类为原始类的方法添加辅助功能。

12.4 环境搭建

导入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>

12.5 开发流程

定义原始类

public interface UserService {
    public void save();
}
public class UserServiceImpl implements UserService {
    public void save(){
        System.out.println("save method executed");
    }
}

定义通知类(添加额外功能)

//method:要执行的目标对象的方法
//args:参数
//target: 目标
public class MyAdvice implements MethodBeforeAdvice{//实现前置通知接口
	public void before(Method method, Object[] args, Object target) throws Throwable {
		System.out.println("before advice executed...");
	}
}

定义Bean标签

<!--原始对象-->
<bean id="us" class="com.techoc.aop.UserServiceImpl"/>

<!--辅助对象-->
<bean id="myAdvice" class="com.techoc.aop.MyAdvice"/>

定义切入点(PointCut)

形成切面(Aspect)

<aop:config>
    <!--切入点 expression:表达式 execution(要执行的位置 *(修饰词) *(返回值) *(类名) *(方法名) *(参数))-->
    <aop:pointcut id="myPointCut" expression="execution(* save())" />
</aop:config>
<aop:config>
    <!--组装切面-->
    <aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut" />
</aop:config>

12.6 AOP小结

  • 通过AOP提供的编码流程,更便利的定制切面,更方便的定制了动态代理。
  • 进而彻底解决了辅助功能见余的问题;
  • 业务类中职责单一性得到更好保障;
  • 辅助功能也有很好的复用性。

12.7 通知类

定义通知类,达到通知效果

前置通知:MethodBeforeAdvice

后置通知:AfterAdvice

后置通知:AfterReturningAdvice//有异常不执行,方法会因异常而结束,无返回值

异常通知:ThrowsAdvice

环绕通知:MethodInterceptor

12.8 通配切入点

根据表达式通配切入点

<!--匹配参数-->
<aop:pointcut id="myPointCut" expression="execution(* *(com.techoc.entity.User))"/>
<!--匹配方法名(无参)-->
<aop:pointcut id="myPointCut" expression="execution(* save())"/>
<!--匹配方法名(任意参数)-->
<aop:pointcut id="myPointCut" expression="execution(* save(..))"/>
<!--匹配返回值类型-->
< aop:pointcut id = "myPointCut" expression="execution(com.techoc.entity.User *(..))"/>
<!--匹配类名-->
< aop:pointcut id = "myPointCut" expression="execution(* com.techoc.service.UserServiceImpl.*(..))"/>
<!--匹配包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.techoc.service.*.*(..))" />
<!--匹配包名、以及子包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.techoc..*.*(..))" />

12.9 JDK和CGLIB选择

  • spring底层,包含了jdk代理和cglib代理两种动态代理生成机制

  • 基本规则是:目标业务类如果有接口则用JDK代理,没有接口则用CGLib代理

12.10 后处理器

  • spring中定义了很多后处理器;
  • 每个bean在创建完成之前,都会有一个后处理过程,即再加工,对bean做出相关改变和调整;
  • spring-AOP中,就有一个专门的后处理器,负责通过原始业务组件(Service),再加工得到一个代理组件。

image.png

12.10.1 后处理器定义

/**
*定义bean后处理器
*作用:在bean的创建之后,进行再加工
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
    /**
    *在bean的init方法之前执行
    *@param bean 原始的bean对象
    *@param beanName
    *@return
    *@throws BeansException
    */
    public object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException{
        System.out.println("后处理器在init之前执行~~~"+bean.getClass());
        return bean;
    }
    /**
    *在bean的init方法之后执行
    *@param bean postProcessBeforeInitialization返回的bean
    *@param beanName
    *@return
    *@throws BeansException
    */
    @0verride
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后处理器在init之后执行~~~"+bean.getClass());
        return bean;//此处的返回是getBean()最终的返回值
    }
}

12.10.2 配置后处理器

<1--配置后处理器,将对工厂中所有的bean声明周期进行干预-->
<bean class="com.techoc.beanpostprocessor.MyBeanPostProcessor"></bean>

12.10.3 bean生命周期

构造-->注入属性 满足依赖-->后处理器前置过程-->初始化-->后处理器后置过程-->返回-->销毁

十三、Spring+MyBatis【重点】

13.1 配置数据源

将数据源配置到项目中

13.1.1 引入jdbc.properties配置文件

jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=123456
jdbc.init=1
jdbc.minIdle=1
jdbc.maxActive=3

13.1.2 整合Spring配置文件和properties配置文件

<!-- 配置文件参数化(参数占位)-->
<context:property-placeholder location=" classpath:jdbc.properties"/>

<!-- 与PooledDataSource集成(二选一) -->
<bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
    <property name="driver"value="${jdbc.driverClass}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<!-- 与DruidDataSource集成(二选一)-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <!-- 基本配置 -->
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

13.1.3 Druid连接池可选参数

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <!-- 基本配置 -->
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>

    <!--配置初始化大小、最小、最大-->
    <property name="initialSize" value="${jdbc.init}"/>
    <property name="minIdle" value="${jdbc.minIdle}"/>
    <property name="maxActive" value="${jdbc.maxActive}"/>
    <!--配置获取连接等待超时的时间-->
    <property name="maxWait" value="60000"/>
    <!--配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒-->
    <property name="timeBetweenEvictionRunsMillis" value="60000"/>
    <!--配置一个连接在池中最小生存的时间,单位是毫秒-->
    <property name="minEvictableIdleTimeMillis" value="300000"/>
</bean>

13.1.4 Druid监控中心

<!--web.xml-->
<servlet>
    <servlet-name>DruidStatView</servlet-name>
    <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>DruidStatView</servlet-name>
    <url-pattern>/druid/*</url-pattern>
</servlet-mapping>

13.1.5 测试监控中心

配置tomcat,并访问protocol://ip:port/project/druid/index.html

13.2 整合Mybatis方法一

将SqlSessionFactory、DAO、Service配置到项目中

13.2.1 导入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.6.RELEASE</version>
</dependency>

<!-- spring+mybatis集成依赖 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.1</version>
</dependency>

13.2.2 配置sqlSessionFactory

<!-- 工厂bean:生成Sq1SessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- 注入连接池 -->
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations">
        <list>
            <value>classpath:com/techoc/dao/*.xml</value>
        </list>
    </property>
    <property name="typeAliasesPackage" value="com.techoc.entity"/>
</bean>

13.2.3 配置MapperScannerConfigurer

管理DAO实现类的创建,并创建DAO对象,存入工厂管理

  • 扫描所有DAO接口,去构建DAO实现

  • 将DAO实现存入工厂管理

  • DAO实现对象在工厂中的id是:“首字母小写的-接口的类名”,

    例如:UserDAO==>userDAO,OrderDAO==orderDAO

<!-- mapper ScannerConfigurer -->
<bean id="mapperScannerConfigurer9" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--dao接口所在的包如果有多个包,可以用逗号或分号分隔
    <property name="basePackage" value="com.a.dao,com.b.dao">/property>
    -->
    <property name="basePackage" value="com.techoc.dao"/>
    <!--如果工厂中只有一个Sq1SessionFactory的bean,此配置可省略-->
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

13.2.4 配置Service

<bean id="userService" class="com.techoc.service.UserServiceImpl">
    <!--注意ref中的值是对应DA0接口的首字母小写的接口名-->
    <property name="userDA0"ref="userDA0"/>
</bean>

13.3 整合MyBatis方法二

13.3.1 Spring接管Mybatis的实现类

  • 创建UserMapper接口、创建UserMapper.xml

  • 创建UserMapperImpl实现类

  • 在Spring配置文件中使用构造器注入SqlSessionTemplate

public class UserMapperImpl implements UserMapper {
	//之前的所有操作,都使用sqLSession来执行,整合Spring后现在都使用SqLSessionTemplate
    private SqlSessionTemplate sqlSession;

    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

    @Override
    public List<User> selectUser() {
        return sqlSession.getMapper(UserMapper.class).selectUser();
    }
}
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <!-- 构造器注入 -->
    <constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>

<bean id="userMapper" class="com.techoc.mapper.UserMapperImpl">
    <property name="sqlSession" ref="sqlSession"/>
</bean>

测试方法

@Test
public void test1(){
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");

    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

    List<User> users = userMapper.selectUser();

    for (User user :
         users) {
        System.out.println(user);
    }
}

13.4 整合MyBatis方法三

13.4.1 继承SqlSessionDaoSupport

  • 创建UserMapper接口、创建UserMapper.xml
  • 创建UserMapperImpl实现类
  • UserMapperImpl内容如下
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
    @Override
    public List<User> selectUser() {
        return getSqlSession().getMapper(UserMapper.class).selectUser();
    }
}

当实现类继承SqlSessionDaoSupport后,无需在Spring配置文件中注入SqlSessionTemplate

<bean id="userMapper2" class="com.techoc.mapper.UserMapperImpl">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

测试方法

@Test
public void test2(){
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");

    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

    List<User> users = userMapper.selectUser();

    for (User user :
         users) {
        System.out.println(user);
    }
}

13.5 整合MyBatis方法四

13.5.1 让Spring创建实现类

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    <property name="mapperInterface" value="com.techoc.mapper.UserMapper"/>
</bean>

测试方法

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");

    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
    List<User> users = userMapper.selectUser();

    for (User user : users) {
        System.out.println(user);
    }
}

十四、事务【重点】

14.1 配置DataSourceTransactionManager

事务管理器,其中持有DataSource,可以控制事务功能(commit,rollback等)

<!--1.引入一个事务管理器,其中依赖DataSource,借以获得连接,进而控制事务逻辑-->
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

注意:DataSourceTransactionManager和SqlSessionFactoryBean要注入同一个DataSource的Bean,否则事务控制失败

14.2 配置事务通知

基于事务管理器,进一步定制,生成一个额外功能:Advice。

此Advice可以切入任何需要事务的方法,通过事务管理器为方法控制事务。

<tx:advice id="txManager" transaction-manager="tx">
    <!--定义事务属性-->
    <tx:attributes>
        <!--propagation="REQUIRED" read-only="false"/>-->
        <tx:method name="queryUser" isolation="DEFAULT" propagation="SUPPORTS" read-only="true" timeout="-1"
                   rollback-for="Exception"/>
        <!--以User结尾的方法,切入此方法时,采用对应事务实行-->
        <tx:method name="*User" isolation="DEFAULT" propagation="REQUIRED" read-only="false" timeout="-1"
                   rollback-for="Exception"/>
        <!--剩余所有方法-->
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

14.3 事务属性

14.3.1 隔离级别

14.3.1.1 概念

isolation 隔离级别

名称描述
default(默认值) (采用数据库的默认的设置) (建议)
read-uncommited读未提交
read-commited读提交(Oracle数据库默认的隔离级别)
repeatable-read可重复读(MySQL数据库默认的隔离级别)
serialized-read序列化读

隔离级别由低到高为:read-uncommited<read-commited<repeatable-read<serialized-read

14.3.1.2 特性
  • 安全性:级别越高,多事务并发时,越安全。因为共享的数据越来越少,事务间彼此干扰减少。

  • 并发性:级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。

14.3.1.3 并发问题

事务并发时的安全问题

问题描述
脏读一个事务读取到另一个事务还未提交的数据。大于等于read-commited可防止
不可重复读一个事务内多次读取一行数据的相同内容,其结果不一-致。大于等于repeatable -read可防止
幻影读一个事务内多次读取一-张表中的相同内容,其结果不- -致。serialized-read可防止

14.3.2 传播行为

propagation 传播行为

当涉及到事务嵌套(Service调用Service)时,可以设置:

  • SUPPORTS=不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)
  • REQUIRED=不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。(默认值)(适合增删改)
REQUIRED支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY支持当前事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。

14.3.3 读写性

readonly 读写性

  • true:只读,可提高查询效率。(适合查询)
  • false:可读可写。(默认值)(适合增删改)

14.3.4 事务超时

timeout 事务超时时间

当前事务所需操作的数据被其他事务占用,则等待。

  • 100:自定义等待时间100(秒)。
  • -1:由数据库指定等待时间,默认值。(建议)

14.3.5 事务回滚

rollback-for 回滚属性

  • 如果事务中抛出RuntimeException,则自动回滚
  • 如果事务中抛出CheckException(非运行时异常Exception),不会自动回滚,而是默认提交事务
  • 处理方案:将CheckException转换成RuntimException上抛,或设置rollback-for="Exception"

14.4 编织

将事务管理的Advice切入需要事务的业务方法中

<aop:config>
    <aop:pointcut id="pc_tx" expression="execution(* com.techoc.service.UserServiceImpl.*(..))"/>
    <aop:advisor advice-ref="txManager" pointcut-ref="pc_tx"/>
</aop:config>

十五、注解开发

15.1 声明Bean

用于替换自建类型组件的<bean…>标签;可以更快速的声明bean

  • @Service 业务类专用
  • @Repository dao实现类专用
  • @Controller web层专用
  • @Component 通用
  • @Scope用户控制bean的创建模式
// @Service说明此类是一个业务类,需要将此类纳入工厂等价替换掉<bean class="xxx.UserServiceImp1">
// @Service默认beanId==首字母小写的类名"userServiceImpl"
// @Service("userService")自定义beanId为"userService"
@Service	//声明bean,且id="userServiceImpl"
@Scope("singleton")	//声明创建模式,默认为单例模式;@Scope("prototype")即可设置为多例模式
public class UserServiceImpl implements UserService {
    ...
}

15.2 注入(DI)

用于完成bean中属性值的注入

  • @Autowired基于类型自动注入
  • @Resource基于名称自动注入
  • @Qualifier("userDAO")限定要自动注入的bean的id,一般和@Autowired联用
  • @Value注入简单类型数据(jdk8种+String)
@Service
public class UserServiceImpl implements UserService {
    
    @Autowired//注入类型为UserDA0的bean
    @Qualifier("userDA02")//如果有多个类型为UserDA0的bean,可以用此注解从中挑选一个
    private UserDAO userDAO;
}
@Service
public class UserServiceImpl implements UserService {
    
    @Resource("userDA03")//注入id="userDA03"的bean
    private UserDAO userDAO;
    /*
    @Resource//注入id="userDA0"的bean
    private UserDAO userDAO;
	*/
}
public class XX{
    @Value("100")//注入数字
    private Integer id;
    @Value("shine")//注入String
    private String name;
}

15.3 事务控制

用于控制事务切入

  • @Transactional
  • 工厂配置中的<tx:advice...和<aop:config...可以省略
//类中的每个方法都切入事务(有自己的事务控制的方法除外)
@Transactional(isolation=Isolation.READ_COMMITTED,propagation=Propagation.REQUIRED,readonly=false,rollbackFor=Exception.class,timeout=-1)
public class UserServiceImpl implements UserService {
    
    //该方法自己的事务控制,仅对此方法有效
    @Transactional(propagation=Propagation.SUPPORTS)
    public List<User> queryA11(){
        return userDao.queryA11();
    }
    public void save(User user){
        userDao.save(user);
    }
}

15.4 注解所需配置

<!--告知spring,哪些包中有被注解的类、方法、属性-->
<context:component-scan base-package = "com.techoc"/>
<!--告知spring,@Transactional在定制事务时,基于txManager=DataSourceTransactionManager-->
<tx:annotation-driven transaction-manager = "txManager"/>

15.5 AOP开发

15.5.1 注解使用

@Aspect // 声明此类是一个切面类:会包含切入点(pointcut)和通知(advice)
@Component //声明组件,进入工厂
public class MyAspect {
    // 定义切入点
    @Pointcut("execution(* com.techoc.service.UserServiceImpl.*(..))")
    public void pc(){}

    @Before("pc()") // 前置通知
    public void mybefore(JoinPoint a) {
        System.out.println("target:"+a.getTarget());
        System.out.println("args:"+a.getArgs());
        System.out.println("method's name:"+a.getSignature().getName());
        System.out.println("before~~~~");
    }

    @AfterReturning(value="pc()",returning="ret") // 后置通知
    public void myAfterReturning(JoinPoint a,Object ret){
        System.out.println("after~~~~:"+ret);
    }

    @Around("pc()") // 环绕通知
    public Object myInterceptor(ProceedingJoinPoint p) throws Throwable {
        System.out.println("interceptor1~~~~");
        Object ret = p.proceed();
        System.out.println("interceptor2~~~~");
        return ret;
    }
    @AfterThrowing(value="pc()",throwing="ex") // 异常通知
    public void myThrows(JoinPoint jp,Exception ex){
        System.out.println("throws");
        System.out.println("===="+ex.getMessage());
    }
}

15.5.2配置

<!-- 添加如下配置 启用aop注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

十六、集成Junit

16.1 导入依赖

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

16.2 编码

  • 可以免去工厂的创建过程;
  • 可以直接将要测试的组件注入到测试类。
// 测试启动,启动spring工厂,并且当前测试类也会被工厂生产
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestSpringMybatis {
    @Autowired	//自动注入
    @Qualifier("userService2")
    private UserService userService;

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Test
    public void testSpringJunit() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> users = mapper.queryUsers();
        for (User user : users) {
            System.out.println(user);
        }
    }
}