SpringAOP听过!懂没?直击企业笔试面试最常问到的Spring框架原理

课代表笔记(本篇文章的重点):

  1. 本篇文章从代理模式出发,介绍静态代理模式和动态代理模式,了解AOP的底层的实现
    接下来详细介绍AOP思想和Spring中AOP的开发流程

  2. Spring框架和MyBatis框架整合,让开发的效率更高
    两者都是企业面试笔试最常问到的知识点,或者换一句话说,这是企业招人最低的要求

  3. 最后讲Spring的注解开发,省去了配置实体类和注入实体类烦杂的流程

  4. Spring集成JUnit,让测试写更少的代码

前文推荐:
Spring基础入门到Spring IOC介绍(本篇文章竟然将这么多Spring的底层实现,面试官又得问了)

在讲SpringAOP之前,先介绍一下代理设计模式,能更好的理解AOP的思想和AOP的底层实现

代理设计模式

概念

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

功能分离
image-20200619211037147

静态代理模式

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

静态代理
image-20200619211311775
结构
image-20200619212008972
  • 代理类和原始类中要保证实现功能一致,因此让他们实现同一接口

接口

package per.leiyu.entiry;

/**
 * @author 雷雨
 * @date 2020/6/19 21:15
 */
public interface FangdongService {
    public void zufang();
}

房东

package per.leiyu.entiry;

/**
 * @author 雷雨
 * @date 2020/6/19 21:15
 */
public class FangdongServiceImpl implements FangdongService{

    @Override
    public void zufang() {

        //核心功能
        System.out.println("签合同");
        System.out.println("收房租");
    }
}

代理

package per.leiyu.entiry;

/**
 * @author 雷雨
 * @date 2020/6/19 21:17
 */
public class FangdongServiceProxy implements FangdongService {
    private FangdongService fangdongService = new FangdongServiceImpl();
    @Override
    public void zufang() {
        //辅助功能呢
        System.out.println("发布租房信息");
        System.out.println("带租客看房");
        //核心功能
        //核心功能仍然由原始类完成
        fangdongService.zufang();

    }
}

静态代理的特点

  1. 的确解决了原始业务类中核心功能和辅助功能的耦合
  2. 但是在代理类中又出现了核心功能和辅助功能的耦合

因为在静态代理中我们明确的创建了代理对象,那么就增加了代理类的维护成本,我们想要的是既能将辅助功能分离,还不需要我们自己创建代理类对象,那么就使用动态代理.在程序运行过程中,通过反射机制,动态的为我们生成一个类来解决原始类中的问题

动态代理设计模式

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

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

package per.leiyu.factoryTest;

import org.junit.Test;
import per.leiyu.entiry.FangdongService;
import per.leiyu.entiry.FangdongServiceImpl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author 雷雨
 * @date 2020/6/19 21:38
 */
public class TestProxyByjdk {
    @Test
    public void test1(){
        //目标
        FangdongService fangdongService = new FangdongServiceImpl();
        //额外功能
        InvocationHandler hi= new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //辅助功能呢
                System.out.println("发布租房信息");
                System.out.println("带租客看房");
                //核心功能
                //核心功能仍然由原始类完成
                fangdongService.zufang();
                return null;
            }
        };
        //动态生成代理类
        //因为这个代理是目标接口的实现类(和静态代理一样,要保证目标类和代理的功能统一),因此用接口接收代理实现类没有问题
        FangdongService proxy = (FangdongService)Proxy.newProxyInstance(TestProxyByjdk.class.getClassLoader(), fangdongService.getClass().getInterfaces(), hi);

        proxy.zufang();

    }

}

动态代理-基于接口
image-20200619214853356

CGLIB动态代理

@Test
    public void test2(){
        //目标
        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("发布租房信息");
                System.out.println("带租客看房");
                //核心功能
                //核心功能仍然由原始类完成
                fangdongService.zufang();
                return null;
            }
        });

        //动态创建代理类
        FangdongServiceImpl proxy = (FangdongServiceImpl)enhancer.create();
        proxy.zufang();
    }
动态代理-基于继承
image-20200619215524327
  • 也就是把目标类当做了父类,其子类肯定继承了父类的核心功能,子类再实现一些额外的功能

面向切面编程

前面讲到动态代理模式的确能够把类之间的耦合去掉,但是为每一个类都编写这么大量的代码费事费力,在Spring中封装了动态代理的功能,并为我们提供了友好的方法.

概念

AOP(Aspect Oriented Programming),即面向切面编程,利用一种称为"横切"的技术,刨开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性

AOP开发术语

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

作用

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

环境搭建

引入AOP相关依赖

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

开发流程

定义原始类

package per.leiyu.service.userServiceImpl;

import per.leiyu.service.Userservice;

import java.util.List;

/**
 * @author 雷雨
 * @date 2020/6/20 8:31
 */
public class UserserviceImpl implements Userservice {
    @Override
    public Integer queryUserByid() {
        System.out.println("核心功能" +"查询用户通过id值");
        return 1;
    }

    @Override
    public void queryUserByName(String name) {

        System.out.println("核心功能查询用户通过用户名");

    }

    @Override
    public List queryUserByNameAndPassword(String name, String password) {

        System.out.println("核心功能查询用户通过用户名和密码");
        return null;
    }
}

package per.leiyu.service;

import java.util.List;

/**
 * @author 雷雨
 * @date 2020/6/20 8:29
 */
public interface Userservice {

   Integer queryUserByid();

    public void queryUserByName(String name);

    List queryUserByNameAndPassword(String name, String password);
}

定义通知类(实现MethodxxxAdvice)

package per.leiyu;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

/**
 * @author 雷雨
 * @date 2020/6/20 8:34
 */
public class myAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("辅助功能1");
        System.out.println("辅助功能2");
    }
}

定义bean标签

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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-3.0.xsd
              http://www.springframework.org/schema/aop
              http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="userService" class="per.leiyu.service.userServiceImpl.UserserviceImpl"></bean>

    <bean id="before" class="per.leiyu.myAdvice"></bean>
    <!--编织 -->
    <aop:config>
        <!--定义切入点 修饰符 返回值 包.类 方法名 参数表-->
        <aop:pointcut id="pc_leiyu" expression="execution(* queryUserByid())"/>
        <!-- 组装
        advice-ref:通知
         pointcut-ref:切入点
        -->
        <aop:advisor advice-ref="before" pointcut-ref="pc_leiyu"/>
    </aop:config>

</beans>
  • 注意配置头中添加了aop的配置

定义切入点

package service;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import per.leiyu.service.Userservice;

/**
 * @author 雷雨
 * @date 2020/6/20 8:36
 */
public class MyserviceAdvice {
    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
        Userservice proxy = (Userservice)context.getBean("userService");
        System.out.println(proxy.getClass());
        proxy.queryUserByid();
    }
}

运行结果

如果运行出来有辅助功能说明我们的动态代理实现成功了

运行结果
image-20200620104122021
  • 注意:通过Spring AOP实现的动态代理获得的代理类可以用原始类接口接收(原因和JDK实现动态代理的原因相同)

AOP中的各种通知类

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

  • 前置通知:MethodBeforeAdvice
  • 后置通知:AfterAdvice
  • 后置通知:AfterReturningAdvice 有异常不执行,方法会因异常而结束,无返回值
  • 异常通知:ThrowsAdvice
  • 环绕通知:MethodInterceptor

切入点表达式

根据表达式通配切入点

修饰符 返回值 包.类 方法名 参数列表

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

Spring AOP中使用JDK和CGlib选择

在Spring中实现动态代理的功能来解耦合,可以使用两种方式JDK的方法和CGlib的方式

那么Spring底层是如何选择代理模式的?

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

看看java中的源码
image-20200620115736761

后处理器

Spring中定义了很多后处理器

每个bean在创建完成之前,都会有一个后处理过程,即再加工,对bean做出相应的改变和调整.

Spring-AOP中,就有一个专门的后处理器,负责通过原始业务组件(Service),再加工得到一个代理组件

自定义一个后处理器

自定义后处理器了解后处理器的运行

实体类

package per.leiyu.entity;

import javax.print.attribute.standard.PrinterURI;

/**
 * @author 雷雨
 * @date 2020/6/20 12:04
 */
public class User {
    private Integer id;

    public User() {
        System.out.println("User的构造方法");

    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
        System.out.println("User的set");
    }
    public void initUser(){
        System.out.println("初始化方法");
    }
}

后处理类

需要继承BeanPostProcessor接口

package per.leiyu.entity;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * @author 雷雨
 * @date 2020/6/20 12:03
 *
 * 定义后处理器:
 * 作用在bean创建之后,对bean进行再加工
 */
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
        System.out.println("后处理器1");
        System.out.println("后处理器1"+o+s);
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
        System.out.println("后处理器2");
        System.out.println("后处理器2"+o+s);
        return null;
    }
}

Spring配置

    <!-- 后处理器-->
    <bean id="user1" class="per.leiyu.entity.User" init-method="initUser">
        <property name="id" value="1"></property>
    </bean>
    <bean id="beanPostProcessor" class="per.leiyu.entity.MyBeanPostProcessor"></bean>

测试类

package service;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author 雷雨
 * @date 2020/6/20 12:08
 */
public class TestMyBeanPostProcessor {
    @Test
    public void  test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");

    }
}

结果
image-20200620122845521

在Spring中集成了很多后处理器,不需要我们自己来编写代码

常用处理器
image-20200620123227032

由此可得:我们的动态代理是在目标Bean的后处理过程中生成的

完成的Bean声明周期

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

Spring和MyBatis整合

配置数据源

将数据源配置到项目中

引入jdbc.properties配置文件

#jdbc.properties
jdbc.driver =com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true
jdbc.username=root
jdbc.passward=123456
#jdbc.xxx的jdbc是标识名,可以修改,是为了我们方便记忆和书写,你如果愿意,也可以修改为aaa.xxx

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

创建一个Spring的配置文件,然后把MyBatis的配置可以直接写入,这样就不用再单独给MyBatis建立配置文件

<?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-3.0.xsd
               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:property-placeholder location="jdbc.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!--使用$占位符索引到jdbc.properties配置文件中的具体内容-->
        <property name="driver" value="${jdbc.driver}" />
        <!--  转义字符&  方法在下面标签中有具体演示-->
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.passward}" />
    </bean>

</beans>

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>

测试监控中心

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

整合MyBatis

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

导入依赖

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <!-- MyBatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.3.0</version>
        </dependency>
        <!--Spring整合MyBatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!-- Mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>

配置SqlSessionFactory

首先这个是一个FactoryBean,而FactoryBean是用来创建复杂对象的,那么SqlSessionFactory就是为了创建SqlSessionFactory的复杂的对象

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入连接池-->
        <property name="dataSource" ref="dataSource"></property>
        <property name="mapperLocations">
            <list>
                <value>classpath:per/leiyu/dao/*.xml</value>
            </list>
        </property>
<!--        为 dao-mapper文件中的实体 定义缺省包路径-->
<!--        如:<select id="queryAll" resultType="User"> 中User类可以不定义包-->
        <property name="typeAliasesPackage" value="per.leiyu.entity"></property>
    </bean>

创建一个UserDao

为了测试我们的MyBatis的整合

package per.leiyu.dao;

import per.leiyu.entity.User;

import java.util.List;

/**
 * @author 雷雨
 * @date 2020/6/20 16:54
 */
public interface UserDao {
     List<User> queryUsers();
}

创建实体类
package per.leiyu.entity;

import java.util.Date;

/**
 * @author 雷雨
 * @date 2020/6/20 16:55
 */
public class User {
    private  Integer id;
    private  String username;
    private String passward;
    private Boolean gender;
    private Date registTime;

    public User() {
        super();
    }

    public User(Integer id, String username, String passward, Boolean gender, Date registTime) {
        this.id = id;
        this.username = username;
        this.passward = passward;
        this.gender = gender;
        this.registTime = registTime;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", passward='" + passward + '\'' +
                ", gender=" + gender +
                ", registTime=" + registTime +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassward() {
        return passward;
    }

    public void setPassward(String passward) {
        this.passward = passward;
    }

    public Boolean getGender() {
        return gender;
    }

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

    public Date getRegistTime() {
        return registTime;
    }

    public void setRegistTime(Date registTime) {
        this.registTime = registTime;
    }
}

配置对象关系映射

这里是基于UserDao接口

写sql语句

这里的配置方式和MyBatis中完全相同

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 指明该xml文件时用来描述那个dao接口的-->
<mapper namespace="per.leiyu.dao.UserDao">
    <select id="queryUsers" resultType="User">
        select id,username,passward,gender,regist_time
        from t_user
    </select>
</mapper>
创建测试类
package per.leiyu;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import per.leiyu.dao.UserDao;
import per.leiyu.entity.User;

import java.util.List;

/**
 * @author 雷雨
 * @date 2020/6/20 17:08
 */
public class TestSpringMyBatis {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
        SqlSessionFactory sqlSessionFactory =(SqlSessionFactory) context.getBean("sqlSessionFactory");
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> users = mapper.queryUsers();
        for (User user : users) {
            System.out.println(user);
        }
    }
}

成功读取到了数据库中数据
image-20200620201616640
给出文件结构
image-20200620201658141

配置MapperScannerConfigurer

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

  1. 扫描所有DAO接口,去构建DAO实现
  2. 将DAO实现存入工厂管理
  3. DAO实现对象在工厂中的id是:首字母小写的-接口的类名,例如:UserDAO==>userDAO,OrderDAO==>orderDAO
    • 意思就是如果你的Dao接口是UserDAO,那么它在工厂中的bean对象就是userDAO,虽然这个bean我们看不到,但是可以正常的使用,是Spring动态为我们创建的
    <!-- mapperScannerConfigurer-->
    <bean id="mapperScannerConfigurer2" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--dao接口所在的包,如果有多个包,可以用逗号或分号分隔
         <property name="basePackage" value="dao接口所在的包,多个包用逗号分隔"></property>
        -->
        <property name="basePackage" value="per.leiyu.dao"></property>
        <!-- 如果工厂中只有一个SqlSessionFactory的bean,此配置可省略-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>

    </bean>

测试Spirng为我们动态的生成了Dao接口的实现类对象

那么我们能够使用这个动态创建出来的对象调用Dao接口的方法就代表Spirng为我们动态的生成了Dao接口的实现类对象

    @Test
    public void testMapperScannerConfigurer(){
        ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
        //这个bean对象在Spring的配置文件中没有注册,这是我们根据规则推测出来的
        UserDao userDao=(UserDao) context.getBean("userDao");
        List<User> users = userDao.queryUsers();
        for (User user : users) {
            System.out.println(user);
        }
    }
也能正常的打印我们查询的结果
image-20200620204510578

配置service

创建接口和实现类

都是基于我们之前的UserDao(我们做的就是关于UserDao的service实现)

package per.leiyu.service;

import per.leiyu.entity.User;

import java.util.List;

/**
 * @author 雷雨
 * @date 2020/6/20 20:49
 */
public interface UserService {
     public List<User> queryUser();
}

package per.leiyu.service;

import per.leiyu.dao.UserDao;
import per.leiyu.entity.User;

import java.util.List;

/**
 *
 * @author 雷雨
 * @date 2020/6/20 20:50
 */
public class UserServiceImpl implements UserService {
	//这里需要用到UserDao,我们使用了Spring IOC的思想
    private UserDao userDao;

    public UserDao getUserDao() {
        return userDao;
    }

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

    @Override
    public List<User> queryUser() {
        return userDao.queryUsers();
    }
}

将service注册进Spring
    <!-- UserService-->
    <bean id="userService" class="per.leiyu.service.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>
创建测试
    @Test
    public void testUserService(){
        ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
        UserService userService =(UserService) context.getBean("userService");
        List<User> users = userService.queryUser();
        for (User user : users) {
            System.out.println(user);
        }
    }
service成功打印数据库数据
image-20200620205905400

事务

配置DataSourceTransactionManager

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

 <!--DataSourceTransactionManager -->
    <!-- 引入一个事务管理器,其中依赖DataSource,借以获得连接,进而控制事务逻辑-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
  • 注意:DataSourceTransactionManager和SqlSessionFactoryBean要注入同一个DataSource的Bean,否则事务控制失败
配置事务通知

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

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

我们之前讲过额外功能,而事务就是不属于我们业务的额外功能,而且事务有一个特点:在核心功能之前开启,在核心功能执行之后提交----因此是一个环绕通知

Spring中为我们定制了管理事务的通知的标签


  • 为了不同方法都写一个事务属性,这样做的好处是,当执行不同的方法时,可以切换到不同的事务属性执行-----控制事务更加精密
事务属性
1.隔离级别

isolation 隔离级别

当多事务并发时,事务之间是有隔离级别的,隔离级别越高,说明共享的数据越少,并发的效率就越低,但是对应着安全性就越高

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

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

  • 安全性:级别越高,多事务并发时,越安全.因为共享的数据越来越少,事务间批次干扰减少
  • 并发性:级别越高,多事务并发时,并发越差.因为共享的数据越来越少,事务间阻塞情况增多

事务并发时的安全问题:

  1. 脏读:读脏数据,一个事务读取到另一个事务还未提交的数据
    • 大于等于read-commited 可防止
  2. 不可重复读:一个事务内多次读取一行数据的相同内容,其结果不一致(主要针对更新中发生的,事务A读取一行数据,事务B也读取这行数据并更新了数据,事务A重新读取这行数据的时候发现这一行内容前后不一致)
    • 大于等于 repeatable-read可防止
  3. 幻影读:一个书屋内多次读取一张表中的相同内容,其结果不一致(主要指的是行数不一致)(主要针对的是删除和添加中发生的,事务A读取这个表的数据,事务B也读取这个表数据并删除(或者添加)了部分数据,事务A重新读取这个表数据的时候发现这一格表的内容(行数都不相同)前后不一致)
    • serialized-read 可防止
2.传播行为

propagation 传播行为

事务嵌套:事务A(Service A)中要调用事务B(Service B),那么如果事务B发生了错误会回滚,但是由于原子的隔离性(事务A和事务B不属于同一个原子),那么可能事务A的操作就没能得到回滚

假如事务A是一个查询操作,本身不存在对数据的操作,那么不会发生错误

假如事务A是一个增删改的操作,那么A不回滚,就会造成错误

所以我们要讨论事务之间的传播性

当涉及到事务嵌套(Service调用Service)中,可能会出现问题

  • SUPPORTS = 不存在外部事务,则不开启新事物;存在外部事务,则合并到外部事务中.适合查询
  • REQUIRD = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中.(默认值)适合增删改
3.读写性

readonly 读写性

  • true:只读,可提高查询效率(适合查询)
  • false:可读可写.(默认值)(适合增删改)
4.事务超时

timeout:事务超时时间

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

  • 100:自定义等待时间为100(秒)
  • -1:由数据指定等待时间,默认值.(建议)
5.事务回滚

roolback 回滚属性

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

将刚才配置为事务通知编织到我们我们的Service的实现类中

注解开发

声明bean

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

@Service 业务类专用

@Repository dao实现类专用

@Controller web层专用

@Component 通用

@Scope 用户控制bean的创建模式(默认为单例创建模式,可以根据需要设置为多例的创建模式)

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

注入

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

@Autowired 基于类型自动注入

@Resource 基于名称自动注入

@Qualifier("userDao") 限定要自动注入的bean的id,一般和@Autowired联用

@Value 注入简单类型数据(jdk8种+String)

@Service
public class UserService implements UserService{
    @Autowired //注入类型为UserDAO的bean
    @Qualifier("userDAO2")//如果有多个类型为UserDAO的bean,可以用此注解在其中选在其中一个
    private UserDAO userDAO;
    @Value("leiyu")//注入String
    private String name;
    ...
}
  • 因为仅仅通过类型注入的话,可以会有多个类型都相同,但是注入的bean只能是一个,所以需要再控制bean的id

事务控制

用于控制事务切入

@Transactional

工厂配置中的<tx:advice…和<aop:config…可以省略

//类中的每个方法都切入事务(有自己的事务控制的方法除外)
@Transactional(isolation=Isolation.READ_COMMITED,proagation=Propagation.REQUIRED,readonly=false,rollbackFor=Exception.class,timeout=-1)
public class UserServiceImpl implements UserService{
    ...
    //该方法自己的事务控制,仅对该方法有效
    //如果即在类上加了事务控制,又在方法上加了事务控制,那么以方法上的为准
    @Transactional(propagation=Propagation.SUPPORTS)
    public List<User> queryAll(){
        return userDao.queryAll();
    }
    public void save(User user){
        userDao.save(user);
    }
}

注解所需的配置

<!-- 告知Spring,那些包中  有被注解的类.方法  属性-->
<!-- 只有告知了Spring被注解的地方,Spring才能去找到这些类并去帮我们创建对象 -->
<!-- <context:component-scan base-package="per.leiyu"></context:component> -->
<context:component-scan base-package="per.leiyu"></context:component>

<!--告知Spring,@Transactional在定制事务时,是基于txManager=DataSourceTransactionManager -->
<!-- 事务的管理是需要我们的事务管理器的,事务管理器的功能在上面有说明 -->
<tx:annotation-driven transaction-manager="txManager">

AOP开发

注解使用

package per.leiyu;

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

import java.util.Properties;

/**
 * @author 雷雨
 * @date 2020/6/21 16:08
 */
@Aspect //声明此类是一个切面类:会包含切入点(pointcut)和通知(advice)
@Component //声明组件 进入工厂
public class MyAspect {
    //定义切入点
    // 这里为了定义切入点,因为基于声明的Spring中切入点定义是有一个id值的,我们这的方法名就相当于id值
    @Pointcut("execution(* per.leiyu.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);
    }
    public void myInterceptor(ProceedingJoinPoint p) throws  Throwable{ //环绕通知
        System.out.println("interceptor1");
        Object ret = p.proceed(); //调用核心功能
        System.out.println("interceptor2");
    }
    @AfterThrowing(value = "pc()",throwing = "ex")
    public void myThrows(JoinPoint jp,Exception ex){   //异常通知
        System.out.println("throws");
        System.out.println("====="+ex.getMessage());
    }
}

给AOP注解添加Spring配置才能使用

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

Spring集成JUnit

导入依赖

    <!--Junit单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>

编码

可以免去工厂的创建过程

可以直接将要测试的组件注入到测试类

//  测试启动   启动Spring工厂  并且当前测试类也会被工厂启动
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-context.xml")
public class TestSpringMyBatis {
    //这两个注解是将属性UserService属性注入
    @Autowired
    @Qualifier("userService")
    private UserService userService;

    @Test
    public void testUserService2(){
        List<User> users = userService.queryUser();
        for (User user : users) {
            System.out.println(user);
        }
    }
}
测试结果
image-20200621201443643

我是雷雨,一个普本科的学生,主要专注于Java后端和大数据开发

如果这篇文章有帮助到你,希望你给我一个大大的赞
如果有什么问题,希望你能留言和我一起研究,学习靠自觉,分享靠自愿

转载注明出处
https://blog.csdn.net/qq_40742223

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

炒冷饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值