Spring学习笔记2

1. 代理

1.1 JDK 动态代理

JDK 动态代理是 Java 标准库自带的一种代理机制,基于 接口 实现。它的核心是 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口。

特点

  • 基于接口:JDK 动态代理只能代理实现了接口的目标对象。
  • 性能较低:相较于 CGLIB,JDK 动态代理在运行时的性能稍低。
  • 灵活性高:可以在运行时动态生成代理类,适用于需要动态增强接口方法的场景。

实现步骤

  1. 定义接口:目标对象需要实现一个接口。
  2. 实现 InvocationHandler:用于处理代理类的方法调用。
  3. 生成代理对象:通过 Proxy.newProxyInstance() 方法生成代理对象。

示例代码

  1. 定义接口
public interface BankService {
    void transferMoney(Long fromAccountId, Long toAccountId, double amount);
}
  1. 实现目标类
public class BankServiceImpl implements BankService {
    @Override
    public void transferMoney(Long fromAccountId, Long toAccountId, double amount) {
        System.out.println("Transferring money from account " + fromAccountId + " to account " + toAccountId);
    }
}
  1. 实现 InvocationHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class TransactionHandler implements InvocationHandler {

    private Object target; // 目标对象

    public TransactionHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 事务控制逻辑
        System.out.println("Transaction started...");
        try {
            Object result = method.invoke(target, args); // 调用目标方法
            System.out.println("Transaction committed.");
            return result;
        } catch (Exception e) {
            System.out.println("Transaction rolled back.");
            throw e;
        }
    }
}
  1. 生成代理对象
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        // 目标对象
        BankService target = new BankServiceImpl();

        // 创建 InvocationHandler
        TransactionHandler handler = new TransactionHandler(target);

        // 生成代理对象
        BankService proxy = (BankService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(), // 类加载器
            target.getClass().getInterfaces(), // 目标类实现的接口
            handler // InvocationHandler
        );

        // 调用代理对象的方法
        proxy.transferMoney(1L, 2L, 100.0);
    }
}

输出结果

Transaction started...
Transferring money from account 1 to account 2
Transaction committed.

1.2 CGLIB 代理

CGLIB(Code Generation Library)是一种基于 字节码生成 的代理技术,允许代理 没有实现接口的类。它的核心是 net.sf.cglib.proxy.Enhancer 类和 net.sf.cglib.proxy.MethodInterceptor 接口。

特点

  • 基于类:CGLIB 可以代理没有实现接口的类。
  • 性能较高:CGLIB 生成的代理类直接基于目标类的字节码,性能通常优于 JDK 动态代理。
  • 动态生成子类:CGLIB 通过生成目标类的子类来实现代理。
    实现步骤
  1. 定义目标类:目标类不需要实现接口。
  2. 实现 MethodInterceptor:用于处理代理类的方法调用。
  3. 生成代理对象:通过 Enhancer 类生成代理对象。

示例代码

  1. 定义目标类
public class BankService {
    public void transferMoney(Long fromAccountId, Long toAccountId, double amount) {
        System.out.println("Transferring money from account " + fromAccountId + " to account " + toAccountId);
    }
}
  1. 实现 MethodInterceptor
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class TransactionInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 事务控制逻辑
        System.out.println("Transaction started...");
        try {
            Object result = proxy.invokeSuper(obj, args); // 调用目标方法
            System.out.println("Transaction committed.");
            return result;
        } catch (Exception e) {
            System.out.println("Transaction rolled back.");
            throw e;
        }
    }
}
  1. 生成代理对象
import net.sf.cglib.proxy.Enhancer;

public class Main {
    public static void main(String[] args) {
        // 创建 Enhancer 对象
        Enhancer enhancer = new Enhancer();

        // 设置目标类的父类
        enhancer.setSuperclass(BankService.class);

        // 设置 MethodInterceptor
        enhancer.setCallback(new TransactionInterceptor());

        // 生成代理对象
        BankService proxy = (BankService) enhancer.create();

        // 调用代理对象的方法
        proxy.transferMoney(1L, 2L, 100.0);
    }
}

输出结果

Transaction started...
Transferring money from account 1 to account 2
Transaction committed.

1.3 JDK 动态代理与 CGLIB 的对比

特性JDK 动态代理CGLIB 代理
基于接口
性能较低(基于反射)较高(基于字节码生成)
目标对象必须实现接口可以是任意类
代理对象代理接口生成目标类的子类
适用场景需要代理接口方法需要代理没有接口的类
依赖JDK 自带需要额外依赖 CGLIB 库

2. AOP思想

2.1 基本概念

AOP(Aspect-Oriented Programming) 是一种编程范式,旨在通过将横切关注点(Cross-Cutting Concerns)从核心业务逻辑中分离出来,提高代码的模块化程度。横切关注点是指那些在多个模块中都会用到的功能,例如日志记录、事务管理、安全性检查等。AOP 的核心思想是将这些横切关注点从核心业务逻辑中解耦,封装成独立的模块(切面),并通过配置或注解的方式,将它们动态地应用到需要增强的类或方法上。

AOP 的关键术语

  1. 切面(Aspect):模块化的横切关注点,例如日志记录、事务管理等。
  2. 连接点(Join Point):程序执行过程中的某个点,例如方法调用、异常抛出等。
  3. 通知(Advice):切面在特定的连接点上执行的动作,例如在方法调用前后执行的日志记录。
  4. 切点(Pointcut):定义了通知应该应用到哪些连接点的规则,例如所有以 transferMoney 开头的方法。
  5. 引入(Introduction):允许向现有的类添加新方法或属性。
  6. 织入(Weaving):将切面应用到目标对象的过程,可以在编译时、类加载时或运行时进行。

2.2 AspectJ 技术

AspectJ 是一个成熟的 AOP 框架,提供了丰富的语法和工具,支持在 Java 应用中实现 AOP。AspectJ 可以在编译时或运行时将切面织入到目标类中。

AspectJ 的主要特点

  1. 强大的定义切点的能力:AspectJ 提供了灵活的切点表达式,可以精确地指定通知应该应用到哪些方法或代码块。
  2. 多种通知类型:支持多种通知类型,包括前置通知(Before)、后置通知(After)、环绕通知(Around)等。
  3. 编译时织入:可以在编译时将切面织入到目标类中,提高运行时性能。
  4. 运行时织入:也可以在运行时通过代理或字节码操作将切面织入到目标类中。
  5. 丰富的注解和 XML 配置:支持通过注解或 XML 配置来定义切面和切点。

2.3 AspectJ 使用 XML 配置

添加 AspectJ 依赖
如果使用 Maven,在 pom.xml 中添加 AspectJ 的依赖。

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.7</version>
</dependency>

实例代码

1. 切面类

public class LoggingAspect {

    // 前置通知
    public void logBefore() {
        System.out.println("Logging before method execution");
    }
}

2. 目标类

package com.example.service;

public class BankService {

    public void transferMoney(Long fromAccountId, Long toAccountId, double amount) {
        System.out.println("Transferring money from account " + fromAccountId + " to account " + toAccountId);
    }
}

3. XML 配置文件

在 XML 配置文件中定义切面、切点和通知:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 定义目标 bean -->
    <bean id="bankService" class="com.example.service.BankService" />

    <!-- 定义切面 bean -->
    <bean id="loggingAspect" class="com.example.aspect.LoggingAspect" />

    <!-- 定义切面 -->
    <aop:config>
        <!-- 定义切点 -->
        <aop:pointcut id="serviceMethods" expression="execution(* com.example.service.*.*(..))"/>

        <!-- 定义切面和通知 -->
        <aop:aspect ref="loggingAspect">
            <aop:before pointcut-ref="serviceMethods" method="logBefore"/>
        </aop:aspect>
    </aop:config>
</beans>

4. 测试代码

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

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        BankService bankService = context.getBean(BankService.class);
        bankService.transferMoney(1L, 2L, 100.0);
    }
}

输出结果

Logging before method execution
Transferring money from account 1 to account 2

2.4 AspectJ 使用注解配置

添加 AspectJ 依赖
如果使用 Maven,在 pom.xml 中添加 AspectJ 的依赖。

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.7</version>
</dependency>

实例代码

1. 切面类

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect  // 标记这是一个切面
public class LoggingAspect {

    // 定义切点
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}

    // 定义前置通知
    @Before("serviceMethods()")
    public void logBefore() {
        System.out.println("Logging before method execution");
    }
}

2. 目标类

package com.example.service;

public class BankService {

    public void transferMoney(Long fromAccountId, Long toAccountId, double amount) {
        System.out.println("Transferring money from account " + fromAccountId + " to account " + toAccountId);
    }
}

3. Spring 配置文件

如果使用 Spring,需要在 Spring 的 XML 配置文件中启用注解支持:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 启用 AspectJ 注解支持 -->
    <aop:aspectj-autoproxy/>

    <!-- 定义切面 bean -->
    <bean id="loggingAspect" class="com.example.aspect.LoggingAspect" />

    <!-- 定义目标 bean -->
    <bean id="bankService" class="com.example.service.BankService" />
</beans>

4. 测试代码

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

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        BankService bankService = context.getBean(BankService.class);
        bankService.transferMoney(1L, 2L, 100.0);
    }
}

输出结果

Logging before method execution
Transferring money from account 1 to account 2

2.5 通知

AspectJ 提供了多种通知(Advice)类型,每种通知类型在不同的连接点(Join Point)上执行不同的逻辑。以下是 AspectJ 中常见的通知类型及其作用和语法。

1. Before 通知(前置通知)

  • 作用:在目标方法执行之前执行。
  • 语法
@Aspect
public class BeforeAspect {

    @Pointcut("execution(* com.example.service.*.*(..))")  // 定义切点
    public void serviceMethods() {}

    @Before("serviceMethods()")  // 前置通知
    public void beforeAdvice() {
        System.out.println("Before advice executed");
    }
}

2. After 通知(后置通知)

  • 作用:在目标方法执行之后执行(无论方法是否抛出异常)。
  • 语法
@Aspect
public class AfterAspect {

    @Pointcut("execution(* com.example.service.*.*(..))")  // 定义切点
    public void serviceMethods() {}

    @After("serviceMethods()")  // 后置通知
    public void afterAdvice() {
        System.out.println("After advice executed");
    }
}

3. AfterReturning 通知(返回通知)

  • 作用:在目标方法成功返回且没有抛出异常时执行。
  • 语法
@Aspect
public class AfterReturningAspect {

    @Pointcut("execution(* com.example.service.*.*(..))")  // 定义切点
    public void serviceMethods() {}

    @AfterReturning(pointcut = "serviceMethods()", returning = "result")  // 返回通知
    public void afterReturningAdvice(Object result) {
        System.out.println("AfterReturning advice executed. Result: " + result);
    }
}
  • 参数说明
    • returning = "result":将方法返回值绑定到通知方法的参数 result

4. AfterThrowing 通知(异常通知)

  • 作用:在目标方法抛出异常时执行。
  • 语法
@Aspect
public class AfterThrowingAspect {

    @Pointcut("execution(* com.example.service.*.*(..))")  // 定义切点
    public void serviceMethods() {}

    @AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")  // 异常通知
    public void afterThrowingAdvice(Exception ex) {
        System.out.println("AfterThrowing advice executed. Exception: " + ex.getMessage());
    }
}
  • 参数说明
    • throwing = "ex":将异常对象绑定到通知方法的参数 ex

5. Around 通知(环绕通知)

  • 作用:在目标方法执行前后执行,并且可以完全控制方法的执行流程。
  • 语法
@Aspect
public class AroundAspect {

    @Pointcut("execution(* com.example.service.*.*(..))")  // 定义切点
    public void serviceMethods() {}

    @Around("serviceMethods()")  // 环绕通知
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Around advice: Before method execution");

        // 调用目标方法
        Object result = joinPoint.proceed();

        System.out.println("Around advice: After method execution");
        return result;
    }
}
  • 关键点
    • ProceedingJoinPoint 是关键对象,调用其 proceed() 方法以执行目标方法。
    • 可以修改方法的返回值,或完全阻止方法的执行。

3. JdbcTemplate

JdbcTemplate 是 Spring 框架提供的一个核心工具类,用于简化 JDBC 操作。它封装了 JDBC 的繁琐操作(如连接管理、异常处理、资源释放等),使开发者能够更专注于 SQL 语句和业务逻辑的实现。

引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
    </dependency>
</dependencies>

3.1 主要特点

  1. 简化 JDBC 操作

    • 自动管理数据库连接、语句和结果集的创建与释放。
    • 减少样板代码,提高开发效率。
  2. 统一的异常处理

    • 将 JDBC 的 SQLException 转换为 Spring 的 DataAccessException,提供更清晰的异常层次结构。
  3. 支持多种操作

    • 支持查询、更新、批量操作、存储过程调用等。
  4. 与 Spring 集成

    • 与 Spring 的事务管理、数据源等无缝集成。

3.2 核心方法

1. 更新操作(增删改)

  • update():用于执行 INSERT、UPDATE、DELETE 等 SQL 语句。

    int update(String sql, Object... args);
    

    示例:

    String sql = "UPDATE users SET name = ? WHERE id = ?";
    int rows = jdbcTemplate.update(sql, "John", 1);
    

2. 查询操作

  • queryForObject():查询单行数据并映射为对象。

    <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args);
    

    示例:

    String sql = "SELECT * FROM users WHERE id = ?";
    User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 1);
    
  • query():查询多行数据并映射为对象列表。

    <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args);
    

    示例:

    String sql = "SELECT * FROM users";
    List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
    

3. 执行任意 SQL

  • execute():用于执行任意 SQL 语句(如 DDL 语句)。

    void execute(String sql);
    

    示例:

    jdbcTemplate.execute("CREATE TABLE users (id INT, name VARCHAR(100))");
    

3.3 使用步骤

  1. 配置数据源

    • 在 Spring 配置文件中配置数据源(如 DataSource)。

    • 示例:

      <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
          <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
          <property name="url" value="jdbc:mysql://localhost:3306/test"/>
          <property name="username" value="root"/>
          <property name="password" value="password"/>
      </bean>
      
  2. 创建 JdbcTemplate

    • 将数据源注入到 JdbcTemplate 中。

    • 示例:

      <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
          <property name="dataSource" ref="dataSource"/>
      </bean>
      
  3. 在代码中使用

    • 通过依赖注入获取 JdbcTemplate 实例并调用其方法。

    • 示例:

      @Autowired
      private JdbcTemplate jdbcTemplate;
      
      public void addUser(User user) {
          String sql = "INSERT INTO users (name, age) VALUES (?, ?)";
          jdbcTemplate.update(sql, user.getName(), user.getAge());
      }
      

3.4 优点

  1. 简化代码
    • 减少 JDBC 样板代码,提高开发效率。
  2. 异常处理
    • 提供统一的异常处理机制,避免繁琐的 try-catch 块。
  3. 与 Spring 集成
    • 与 Spring 的事务管理、数据源等无缝集成。
  4. 灵活性
    • 支持自定义 RowMapperResultSetExtractor,满足复杂需求。

4. 配置平台事务管理器和事务管理模板

在 Spring 框架中,事务管理是一个非常重要的功能,用于确保数据库操作的完整性。Spring 提供了多种事务管理方式,其中最常见的两种是声明式事务管理和编程式事务管理。

4.1 配置平台事务管理器

平台事务管理器是 Spring 事务管理的核心接口,它负责事务的开始、提交和回滚。最常见的实现是 DataSourceTransactionManager,用于管理 JDBC 数据源的事务。

  1. 配置数据源

首先,需要配置一个数据源。这里我们使用 DriverManagerDataSource 作为示例数据源:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/test"/>
    <property name="username" value="root"/>
    <property name="password" value="password"/>
</bean>
  1. 配置平台事务管理器

接下来,配置 DataSourceTransactionManager,将数据源注入到事务管理器中:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

4.2 配置事务管理模板

事务管理模板(TransactionTemplate)是一个辅助类,用于简化事务管理的编程式操作。它可以自动管理事务的开始、提交和回滚。

配置事务管理模板

在 Spring 配置文件中配置 TransactionTemplate,并将平台事务管理器注入到模板中:

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="transactionManager"/>
</bean>

4.2.1 编程式事务管理

使用 TransactionTemplate 进行编程式事务管理,可以通过 lambda 表达式或 TransactionCallback 接口来实现。

示例代码

@Autowired
private TransactionTemplate transactionTemplate;

@Autowired
private JdbcTemplate jdbcTemplate;

public void addUser(User user) {
    transactionTemplate.execute(status -> {
        try {
            String sql = "INSERT INTO users (name, age) VALUES (?, ?)";
            jdbcTemplate.update(sql, user.getName(), user.getAge());
            // 模拟异常
            // int i = 1 / 0;
            return null;
        } catch (Exception e) {
            // 回滚事务
            status.setRollbackOnly();
            throw e;
        }
    });
}

4.2.2 声明式事务管理

声明式事务管理通过在 Spring 配置文件中使用 AOP 配置事务,实现更简洁的事务管理。

  1. 配置事务管理器

确保已经配置了 DataSourceTransactionManager

  1. 配置事务管理器的 AOP 支持

使用 tx:annotation-driven 开启基于注解的事务管理:

<tx:annotation-driven transaction-manager="transactionManager"/>
  1. 使用 @Transactional 注解

在需要事务管理的类或方法上使用 @Transactional 注解:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void addUser(User user) {
        String sql = "INSERT INTO users (name, age) VALUES (?, ?)";
        jdbcTemplate.update(sql, user.getName(), user.getAge());
        // 模拟异常
        // int i = 1 / 0;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值