欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

知识梳理:Spring&SpringAop&MyBatis

程序员文章站 2022-03-30 13:12:23
...
目录:Spring&SpringAop&MyBatis

Spring Framework主要包括几个模块:

  • 支持IoC和AOP的容器;
  • IOC: 依赖注入(Spring容器能力) ,AOP:面向切面编程 .低侵入式为现有业务添加额外功能(切入点编程)
  • 支持JDBC和ORM的数据访问模块;
  • 支持声明式事务的模块;
  • 支持基于Servlet的MVC开发;
  • 支持基于Reactive的Web开发;
  • 以及集成JMS、JavaMail、JMX、缓存等其他模块。

IoC容器 IOC: 控制反转 Inverse of control DI

Spring的核心就是提供了一个IoC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.softeem</groupId>
    <artifactId>spring_first</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <java.version>1.8</java.version>

        <spring.version>5.2.3.RELEASE</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>
</project>

在resources下新建文件 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">
    <bean id="userDao" class="com.softeem.dao.impl.UserDaoImpl"></bean>
    <bean id="userService" class="com.softeem.service.UserService">
        <property name="userDao" ref="userDao"></property>
    </bean>
</beans>

控制反转(Inversion of Control)就是依赖倒置原则的一种代码设计的思路。具体采用的方法就是所谓的依赖注入(Dependency Injection)

使用Annotation配置

使用Spring的IoC容器,实际上就是通过类似XML这样的配置文件,把我们自己的Bean的依赖关系描述出来,然后让容器来创建并装配Bean。一旦容器初始化完毕,我们就直接从容器中获取Bean使用它们。

Spring默认使用Singleton创建Bean,也可指定Scope为Prototype;

可将相同类型的Bean注入List

可用@Autowired(required=false)允许可选注入;

可用使用@Bean标注的方法创建Bean; bean:被容器所管理的类,我们自己定义的类,标记了component之后也是被容器管理的组件,@Bean在spring中通常用来引入第三方组件

可使用@PostConstruct@PreDestroy对Bean进行初始化和清理;

相同类型的Bean只能有一个指定为@Primary,其他必须用@Quanlifier("beanName")指定别名;

注入时,可通过别名@Quanlifier("beanName")指定某个Bean;

SpringAop

AOP是Aspect Oriented Programming,即面向切面编程。

OOP:Object Oriented Programming,OOP作为面向对象编程的模式,获得了巨大的成功,OOP的主要功能是数据封装、继承和多态。

而AOP是一种新的编程方式,它和OOP不同,OOP把系统看作多个对象的交互,AOP把系统分解为不同的关注点,或者称之为切面(Aspect)。

AOP原理

如何把切面织入到核心逻辑中?这正是AOP需要解决的问题。换句话说,如果客户端获得了BookService的引用,当调用bookService.createBook()时,如何对调用方法进行拦截,并在拦截前后进行安全检查、日志、事务等处理,就相当于完成了所有业务功能。

在Java平台上,对于AOP的织入,有3种方式:

  1. 编译期:在编译时,由编译器把切面调用编译进字节码,这种方式需要定义新的关键字并扩展编译器,AspectJ就扩展了Java编译器,使用关键字aspect来实现织入;
  2. 类加载器:在目标类被装载到JVM时,通过一个特殊的类加载器,对目标类的字节码重新“增强”;
  3. 运行期:目标对象和切面都是普通Java类,通过JVM的动态代理功能或者第三方库实现运行期动态织入。

最简单的方式是第三种,Spring的AOP实现就是基于JVM的动态代理。由于JVM的动态代理要求必须实现接口,如果一个普通类没有业务接口,就需要通过CGLIB或者Javassist这些第三方库实现。

AOP技术看上去比较神秘,但实际上,它本质就是一个动态代理,让我们把一些常用功能如权限检查、日志、事务等,从每个业务方法中剥离出来。

需要特别指出的是,AOP对于解决特定问题,例如事务管理非常有用,这是因为分散在各处的事务代码几乎是完全相同的,并且它们需要的参数(JDBC的Connection)也是固定的。另一些特定问题,如日志,就不那么容易实现,因为日志虽然简单,但打印日志的时候,经常需要捕获局部变量,如果使用AOP实现日志,我们只能输出固定格式的日志,因此,使用AOP时,必须适合特定的场景。

Spring对接口类型使用JDK动态代理,对普通类使用CGLIB创建子类。如果一个Bean的class是final,Spring将无法为其创建子类。

虽然Spring容器内部实现AOP的逻辑比较复杂(需要使用AspectJ解析注解,并通过CGLIB实现代理类),但我们使用AOP非常简单,一共需要三步:

  1. 定义执行方法,并在方法上通过AspectJ的注解告诉Spring应该在何处调用此方法;
  2. 标记@Component@Aspect
  3. @Configuration类上标注@EnableAspectJAutoProxy

Spring也提供其他方法来装配AOP,但都没有使用AspectJ注解的方式来得简洁明了。

代理模式

java中的代理模式有两种,一种为静态代理,一种为动态代理

静态代理要求委托人,和代理具备相同能力(实现相同接口)

动态代理,不要求代理和委托人实现相同的接口,委托人必须有实现过接口(必须有类型扩展)

  1. 代理实现 InvocationHandler
  2. 代理对象 Proxy

使用声明式事务

使用Spring操作JDBC虽然方便,但是我们在前面讨论JDBC的时候,讲到过JDBC事务,如果要在Spring中操作事务,没必要手写JDBC事务,可以使用Spring提供的高级接口来操作事务。

Spring提供了一个PlatformTransactionManager来表示事务管理器,所有的事务都由它负责管理。而事务由TransactionStatus表示。

Spring为啥要抽象出PlatformTransactionManagerTransactionStatus?原因是JavaEE除了提供JDBC事务外,它还支持分布式事务JTA(Java Transaction API)。分布式事务是指多个数据源(比如多个数据库,多个消息系统)要在分布式环境下实现事务的时候,应该怎么实现。分布式事务实现起来非常复杂,简单地说就是通过一个分布式事务管理器实现两阶段提交,但本身数据库事务就不快,基于数据库事务实现的分布式事务就慢得难以忍受,所以使用率不高。

Spring为了同时支持JDBC和JTA两种事务模型,就抽象出PlatformTransactionManager。因为我们的代码只需要JDBC事务,因此,在AppConfig中,需要再定义一个PlatformTransactionManager对应的Bean,它的实际类型是DataSourceTransactionManager

@Configuration
@ComponentScan
@PropertySource("jdbc.properties")
public class AppConfig {
    ...
    @Bean
    PlatformTransactionManager createTxManager(@Autowired DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

使用编程的方式使用Spring事务仍然比较繁琐,更好的方式是通过声明式事务来实现。使用声明式事务非常简单,除了在AppConfig中追加一个上述定义的PlatformTransactionManager外,再加一个@EnableTransactionManagement就可以启用声明式事务:

@Configuration
@ComponentScan
@EnableTransactionManagement // 启用声明式
@PropertySource("jdbc.properties")
public class AppConfig {
    ...
}

然后,对需要事务支持的方法,加一个@Transactional注解:

@Component
public class UserService {
    // 此public方法自动具有事务支持:
    @Transactional
    public User register(String email, String password, String name) {
       ...
    }
}

或者更简单一点,直接在Bean的class处加上,表示所有public方法都具有事务支持:

@Component
@Transactional
public class UserService {
    ...
}

Spring对一个声明式事务的方法,如何开启事务支持?原理仍然是AOP代理,即通过自动创建Bean的Proxy实现:

public class UserService$$EnhancerBySpringCGLIB extends UserService {
    UserService target = ...
    PlatformTransactionManager txManager = ...

    public User register(String email, String password, String name) {
        TransactionStatus tx = null;
        try {
            tx = txManager.getTransaction(new DefaultTransactionDefinition());
            target.register(email, password, name);
            txManager.commit(tx);
        } catch (RuntimeException e) {
            txManager.rollback(tx);
            throw e;
        }
    }
    ...
}

注意:声明了@EnableTransactionManagement后,不必额外添加@EnableAspectJAutoProxy

BonusService.addBonus()方法必须要有@Transactional`,否则,登录后积分就无法添加了。

默认的事务传播级别是REQUIRED,它满足绝大部分的需求。还有一些其他的传播级别:

REQUIRED:默认,如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行

SUPPORTS:表示如果有事务,就加入到当前事务,如果没有,那也不开启事务执行。这种传播级别可用于查询方法,因为SELECT语句既可以在事务内执行,也可以不需要事务;

MANDATORY:表示必须要存在当前事务并加入执行,否则将抛出异常。这种传播级别可用于核心更新逻辑,比如用户余额变更,它总是被其他事务方法调用,不能直接由非事务方法调用;

REQUIRES_NEW:表示不管当前有没有事务,都必须开启一个新的事务执行。如果当前已经有事务,那么当前事务会挂起,等新事务完成后,再恢复执行;

NOT_SUPPORTED:表示不支持事务,如果当前有事务,那么当前事务会挂起,等这个方法执行完成后,再恢复执行;

NEVER:和NOT_SUPPORTED相比,它不但不支持事务,而且在监测到当前有事务时,会抛出异常拒绝执行;

NESTED:表示如果当前有事务,则开启一个嵌套级别事务,如果当前没有事务,则开启一个新事务。

MyBatis

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射(ORM)。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

使用Mybatis

  • 引入相关的pom支持
  • 在Configuration(AppConfig.java)中配置SqlSessionFactory到容器,该对象需要传入一个DataSource数据源
  • 编写Mapper接口,定义对应的方法。
  • 添加注解,写入SQL语句
  • 在Configuration类中添加注解@MapperScan去扫描mybatis的mapper
  • 运行
@Configuration
@ComponentScan
@PropertySource("jdbc.properties")
@MapperScan("com.softeem.mapper")
public class AppConfig {

配置文件方式

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">
<mapper namespace="com.softeem.mapper.BookMapper">
  <resultMap id="BaseResultMap" type="com.softeem.entity.Book">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="cover" jdbcType="VARCHAR" property="cover" />
    <result column="title" jdbcType="VARCHAR" property="title" />
    <result column="author" jdbcType="VARCHAR" property="author" />
    <result column="date" jdbcType="VARCHAR" property="date" />
    <result column="press" jdbcType="VARCHAR" property="press" />
    <result column="abs" jdbcType="VARCHAR" property="abs" />
    <result column="cid" jdbcType="INTEGER" property="cid" />
  </resultMap>
  <sql id="Base_Column_List">
    id, cover, title, author, `date`, press, `abs`, cid
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List" />
    from book
    where id = #{id,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    delete from book
    where id = #{id,jdbcType=INTEGER}
  </delete>
  <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.softeem.entity.Book" useGeneratedKeys="true">
    insert into book (cover, title, author,
      `date`, press, `abs`, cid
      )
    values (#{cover,jdbcType=VARCHAR}, #{title,jdbcType=VARCHAR}, #{author,jdbcType=VARCHAR},
      #{date,jdbcType=VARCHAR}, #{press,jdbcType=VARCHAR}, #{abs,jdbcType=VARCHAR}, #{cid,jdbcType=INTEGER}
      )
  </insert>
  <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.softeem.entity.Book" useGeneratedKeys="true">
    insert into book
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="cover != null">
        cover,
      </if>
      <if test="title != null">
        title,
      </if>
      <if test="author != null">
        author,
      </if>
      <if test="date != null">
        `date`,
      </if>
      <if test="press != null">
        press,
      </if>
      <if test="abs != null">
        `abs`,
      </if>
      <if test="cid != null">
        cid,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="cover != null">
        #{cover,jdbcType=VARCHAR},
      </if>
      <if test="title != null">
        #{title,jdbcType=VARCHAR},
      </if>
      <if test="author != null">
        #{author,jdbcType=VARCHAR},
      </if>
      <if test="date != null">
        #{date,jdbcType=VARCHAR},
      </if>
      <if test="press != null">
        #{press,jdbcType=VARCHAR},
      </if>
      <if test="abs != null">
        #{abs,jdbcType=VARCHAR},
      </if>
      <if test="cid != null">
        #{cid,jdbcType=INTEGER},
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKey" parameterType="com.softeem.entity.Book">
    update book
    set cover = #{cover,jdbcType=VARCHAR},
      title = #{title,jdbcType=VARCHAR},
      author = #{author,jdbcType=VARCHAR},
      `date` = #{date,jdbcType=VARCHAR},
      press = #{press,jdbcType=VARCHAR},
      `abs` = #{abs,jdbcType=VARCHAR},
      cid = #{cid,jdbcType=INTEGER}
    where id = #{id,jdbcType=INTEGER}
  </update>
</mapper>