Spring学习笔记(下)

222 阅读10分钟

Spring Frameworkd中文文档

3. AOP

3.1 入门

  1. What is AOP

面向切面编程(Aspect Oriented Programming)

AOP是OOP的延续。它可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。

人话:不通过修改源代码方式,在主干功能里添加新的功能。

例如:一个登录功能——

原始基本功能:输入用户名密码--查询数据库--判断--成功则跳转页面,失败则返回再次输入。

现在我们希望在此之上添加新的功能,权限管理。如果按照原始方式,我们需要对源代码进行修改来实现。而AOP的作用就在这里,它可以将这个新功能做成一个独立的模块,需要的话就加进去生效,不需要的话也可以完全剔除而不影响原始功能。

3.2 底层原理

  1. AOP底层使用动态代理

    1. 动态代理有两种情况。

    case1,有接口情况,使用JDK动态代理(spring已经封装好)

    创建接口实现类的代理对象,增强类的方法(增加功能)。

    case2,无接口情况,使用CGLIB动态代理

    创建当前类子类的代理对象。

3.3 JDK动态代理

3.3.1 简述

使用Proxy类里面的方法创建代理对象

java.lang.reflect里有一个类

Class Proxy

java.lang.reflect.Proxy

类里的方法:newProxyInstance

三个参数:

  • ClassLoader,类加载器
  • Interface,增强方法所在的类,这个类实现的接口。支持多个接口。
  • InvocationHandler,实现这个接口,创建代理对象,写增强的方法。

3.3.2 代码

  1. 创建接口,定义方法

  2. 创建类,实现方法

  3. 使用Proxy类创建接口的代理对象(增强过程)

注意,这里是底层实现原理。在spring中我们已经有封装好的可以用。

// JDKProxy.java
package com.phsite;

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

public class JDKProxy {

    public static void main(String[] args) {
        // 创建[接口实现类]代理对象
        Class[] interfaces = {UserDao.class};
//        Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
//            @Override
//            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//                return null;
//            }
//        });
        UserDaoImpl userDao = new UserDaoImpl();
        UserDao dao =
                (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
        int result = dao.add(1, 2);
        System.out.println("result:" + result);
    }
}


// 创建代理对象代码
class UserDaoProxy implements InvocationHandler {

    // 1. 创建的是谁的代理对象,就把谁传递过来。
    // 有参数构造传递
    private Object obj;
    public UserDaoProxy(Object obj) {
        this.obj = obj;
    }


    // 增强的逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 方法之前做处理
        System.out.println("在方法之前执行。。。" + method.getName() + " :传递的参数。。" + Arrays.toString(args));
        // 被增强的方法执行
        Object res = method.invoke(obj, args);
        // 方法之后被处理
        System.out.println("方法之后执行" + obj);

        // 如何判断增强什么方法?对不同的方法add和update做不同的处理。
        // 细节:注意method.getName可以得到方法名。可以依此进行判断。
        return res;
    }
}
// userDaoImpl.java
package com.phsite;

public class UserDaoImpl implements UserDao{

    @Override
    public int add(int a, int b) {
        System.out.println("add方法执行了");
        return a + b;
    }

    @Override
    public String update(String id) {
        System.out.println("update方法执行了");
        return id;
    }
}

3.4 AOP术语

class User {
    add();
    update();
    select();
    delete();
}
  1. 连接点

    1. 类里面哪些方法可以被增强,那么这些方法就成为连接点。
  2. 切入点

    1. 实际被真正增强的方法,就成为切入点。
  3. 通知(增强)

    1. 实际增强的逻辑部分,称为通知(增强)。比如权限管理的判断。
    2. 通知有多种类型。(执行的时机位置)
      • 前置通知
      • 后置通知
      • 环绕通知
      • 异常通知
      • 最终通知
  4. 切面

    1. 是动作。把通知应用到切入点的过程。

3.5 AOP操作

准备工作:

  1. Spring框架中,一般基于基于AspectJ来实现AOP操作。

what is AspectJ?

  • 它并不是Spring组成部分,是一个独立的AOP框架。一般把AspectJ和Spring框架一起使用,进行AOP操作。
  1. 基于AspectJ实现AOP操作

    1. 基于XML配置文件实现
    2. 基于注解方式实现(一般用这种)
  2. 在项目工程引入AOP相关依赖

spring-aop

spring-aspects等包。

去maven仓库里找,然后把dependency复制过来,用maven下载

  1. 切入点表达式
  • 切入点表达式作用:知道对哪个类里面的哪个方法进行增强

  • 语法结构:

execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))

例子1. 对com.phsite.dao.BookDao里面的add进行增强

execution(* com.phsite.dao.BookDao.add(..))

*表示所有,返回类型可以不写

例子2. 对应~里所有方法进行增强

execution(* com.phsite.dao.BookDao.*(..))

例子3. 对com.phsite.dao包里所有类的所有方法进行增强。

execution(* com.phsite.dao.*.*(..))

注:这里参数列表的两个点,意味着它的参数???

我们要做的,就是配置上面提到的五种通知类型。

3.5.1 AspectJ注解

  1. 创建类,在类里面定义方法

  2. 创建增强类(编写增强逻辑)

    • 在增强类里创建方法,让不同的方法代表不同的通知类型
  3. 进行通知的配置

    • 在spring配置文件中,开启注解扫描
    • 使用注解创建User和UserProxy对象
    • 在增强类上面添加注解@Aspect
    • 在spring配置文件中开启生成代理对象
  4. 配置不同类型的通知

在增强类的里面,在座位通知方法上面添加通知类型注解,使用切入点表达式配置

// 增强的类
@Component
@Aspect
public class UserProxy {

    // 前置通知
    // Before注解表示作为前置通知
    @Before(value = "execution(* com.phsite.aopanno.User.add(..))")
    public void before() {
        System.out.println("before");
    }
}

问题:proceedingJoinPoint找不到。明明我已经导入了相关依赖包,版本也和视频里的一样。

  1. 相同的切入点抽取后版本
// 增强的类
@Component
@Aspect
public class UserProxy {

    // 相同切入点抽取
    @Pointcut(value = "execution(* com.phsite.aopanno.User.add(..))")
    public void pointDemo() {
    }

    // 前置通知
    // Before注解表示作为前置通知
    @Before(value = "pointDemo()")
    public void before() {
        System.out.println("before");
    }

    @After(value = "pointDemo()")
    public void after() {
        System.out.println("after");
    }

    // 异常通知
    @AfterThrowing(value = "pointDemo()")
    public void afterThrowing() {
        System.out.println("afterThrowing");
    }

    @AfterReturning(value = "pointDemo()")
    public void afterReturn() {
        System.out.println("afterreturing");
    }

//    @Around(value = "pointDemo()")
//    public void around() {
//        System.out.println("环绕之前");
//        // 被增强的方法执行
//        proceedingJoinPoint.proceed();
//        System.out.println("环绕之后");
//    }
}

  1. 有多个增强类对同一个方法进行增强,设置增强类优先级

在增强类上面添加注解@Order(数值),数字类型值越小,优先级越高。

3.5.2 AspectJ配置文件方式

(仅作了解)

4. JDBCTemplate

4.1 入门

  1. what is JdbcTemplate?

Spring框架对JDBC进行了封装,使用JDBCTemplate可以很方便地对数据库操作。

  1. 准备工作
  • 引入相关jar包。

spring-jdbc

spring-orm

spring-tx

mysql-connector-java

druid

  • 在spring配置文件中配置连接池

  • 配置JdbcTemplate对象,注入DataSource

  • 创建service类,创建dao类,在dao注入jdbcTemplate对象

// service里注入dao
// dao里注入jdbcTemplate
@Service
public class BookService {

    // 注入Dao
    @Autowired
    private BookDao bookDao;
}

@Repository
public class BookDaoImpl implements BookDao {

    // 注入jdbctemplate
    @Autowired
    private JdbcTemplate jdbcTemplate;
}
<!--&lt;!&ndash;    开启注解扫描&ndash;&gt;-->
    <context:component-scan base-package="com.phsite"></context:component-scan>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="url" value="jdbc:mysql:///user_db" />
        <property name="username" value="root" />
        <property name="password" value="root" />
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    </bean>

<!--    创建JdbcTemplate对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--        需要注入dataSource-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
  1. 操作数据库

创建实体类

编写service和dao

在dao进行数据库添加操作

调用jdbcTemplate的update方法,实现添加操作

两个参数:

  • 第一个参数:sql语句
  • 第二个参数:可变参数(sql语句中的值)
@Repository
public class BookDaoImpl implements BookDao {

    // 注入jdbctemplate
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void add(Book book) {
        // 创建sql
        String sql = "insert into t_book values(?,?,?)";

        // 数组形式的可变参数。
        Object[] args = {book.getBookId(), book.getBookname(), book.getBookstatus()};
        int update =jdbcTemplate.update(sql, args);
        System.out.println(update);
    }
}

public class TestBook {

    @Test
    public void testJdbcTemplate() {
        ApplicationContext context =
                new ClassPathXmlApplicationContext("bean1.xml");
        BookService bookService = context.getBean("bookService", BookService.class);
        // 为了临时测试,这里book暂时还用老办法new
//        Book book = new Book();
//        book.setBookId("1");
//        book.setBookname("java");
//        book.setBookstatus("a");

        // 修改操作
//        Book book = new Book();
//        book.setBookId("1");
//        book.setBookname("javaupup");
//        book.setBookstatus("atph");

        // 删除
        bookService.deleteBook("1");
    }
}

查询的方法

jdbcTemplate.queryForObject(sql, class)

返回值。

jdbcTemplate.queryForObject(sql, RowMapper, ...args);

返回对象

第二个参数:RowMapper,是接口,针对你返回的不同类型数据,使用这个接口里的实现类可以完成数据的封装,封装成java里对象实例。

jdbcTemplate.query(sql, RowMapper, ..args);

返回集合。

批量操作:

操作表里的多条记录。

  1. 实现批量添加
  2. 实现批量修改
  3. 实现批量删除

可以体会jdbc的语法糖。

考虑之后mybatis的使用,这里使用原生sql的写法可以看phSpringStart5项目。

5. 事务

什么是事务?

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

事务四个特性ACID

原子性、一致性、隔离性、持久性

5.1 Spring事务管理简介

  1. 事务一般添加到JavaEE三层结构里面的Service业务逻辑层。

  2. Spring进行事务管理两种方式:

  • 编程式事务管理
  • 声明式事务管理
    // 转账方法
    public void accountMoney() {
        try {
            // 1. 开启事务

            // 2. 进行业务操作

            // lucy少100
            userDao.reduceMoney();

            // 模拟异常,出了异常,lucy少了但mary没多。
            int i = 10 / 0;

            // mary多100
            userDao.addMoney();

            // 3. 没有异常,提交事务
        } catch (Exception e) {
            // 4. 出现异常,事务回滚

        }
    }

这个就是编程式。不方便。

一般用声明式事务管理。

  1. 声明式事务管理两种方式:
  • 基于注解方式
  • 基于xml配置文件方式
  1. 在Spring进行声明式事务管理,底层使用AOP

  2. Spring事务管理API

一个接口,代表事务管理器,该接口针对不同的框架提供了不同的实现类。(这里的不同框架:jdbcTemplate、Hibernate、myBatis)

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

  1. 配置事务管理器
<!--    创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--        注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
  1. 开启事务注解
  • 引入名称空间tx

  • 开启事务注解

xmlns:aop="http://www.springframework.org/schema/aop"


<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
  1. 在Service类上面添加事务注解

@Transactional,可以加类上面,也可以加方法上面。如果加在类上面,就是类里所有方法都添加事务。如果加在方法上面,则仅为这个方法添加事务。

5.3 参数配置

  • propagation:事务传播行为

多事务方法之间进行调用,这个过程中事务是如何进行管理的。

事务方法:对数据库表数据进行变化的操作。

Spring框架事务传播行为有7种。

REQUIRED:如果有事务在运行,该方法就在事务内运行;否则就启动一个新的事务,并在自己的事务内运行。

REQUIRED_NEW:该方法必须启动新事务,并在自己的事务内运行。如果有事务正在运行则将其挂起。

……

  • isolation:事务隔离级别

事务有特性称为隔离性,多事务操作之间不会产生影响。

三个读问题:脏读、不可重复读、虚(幻)读

  1. 脏读:一个未提交的事务读取到另一个未提交事务的数据。
  2. 不可重复读:一个未提交事务读取到另一提交事务修改数据。
  3. 虚读:一个未提交的事务读到另一个提交事务添加数据。

通过设置事务隔离级别,解决读问题

4个:

脏读不可重复读幻读
READ UNCOMMITED读未提交111
READ COMMITED读已提交011
REPEATABLE READ可重复读001
SERIALIZABLE串行化000

mysql默认是可重复读。

  • timeout:超时时间

事务需要在一定时长内进行提交,如果不提交就回滚。

默认值是-1(表示无超时限制),设置数字单位是秒。

  • readOnly: 是否只读

读:查询

写:添加修改删除

默认值false,表示可以查询,也可以添加修改删除

设置为true的话,只能查询。

  • rollbackFor: 回滚

设置出现哪些异常进行事务回滚

  • noRollbackFor:不回滚

设置出现哪些异常不进行事务回滚

5.4 完全注解声明式

@Configuration
@ComponentScan(basePackages = "com.phsite")
@EnableTransactionManagement
public class TxConfig {
    // 创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://49.235.49.250:3306/user_db");
        dataSource.setUsername("phuser");
        dataSource.setPassword("Ph@092700");
        return dataSource;
    }

    // jdbcTemplate对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        // 到ioc容器种根据类型找到dataSource
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        // 注入datasource
        jdbcTemplate.setDataSource(dataSource);

        return jdbcTemplate;
    }

    // 事务管理器对象
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}