Spring03-AOP(动态代理模式,环绕通知,增强方法)、事务

文章目录

今日目标

  • 能够理解AOP的作用
  • 能够完成AOP的入门案例
  • 能够理解AOP的工作流程
  • 能够说出AOP的五种通知类型
  • 能够完成"测量业务层接口万次执行效率"案例
  • 能够掌握Spring事务配置

一、AOP

1 AOP简介

问题导入

问题1:AOP的作用是什么?

问题2:连接点和切入点有什么区别,二者谁的范围大?

问题3:请描述什么是切面?

1.1 AOP简介和作用【理解】
  • AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构
    • OOP(Object Oriented Programming)面向对象编程
  • 作用:在不惊动原始设计的基础上为其进行功能增强。简单的说就是在不改变方法源代码的基础上对方法进行功能增强。
  • Spring理念:无入侵式/无侵入式
1.2 AOP中的核心概念【理解】

在这里插入图片描述

  • 连接点(JoinPoint):正在执行的方法,例如:update()、delete()、select()等都是连接点。
  • 切入点(Pointcut):进行功能增强了的方法,例如:update()、delete()方法,select()方法没有被增强所以不是切入点,但是是连接点。
    • 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
      • 一个具体方法:cn.whu.dao包下的BookDao接口中的无形参无返回值的save方法
      • 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
  • 通知(Advice):在切入点前后执行的操作,也就是增强的共性功能
    • 在SpringAOP中,功能最终以方法的形式呈现
  • 通知类:通知方法所在的类叫做通知类
  • 切面(Aspect):描述通知与切入点的对应关系,也就是哪些通知方法对应哪些切入点方法。

2 AOP入门案例【重点】

问题导入

问题1:在通知方法中如何定义切入点表达式?

问题2:如何配置切面?

问题3:在配置类上如何开启AOP注解功能?

2.1 AOP入门案例思路分析
  • 案例设定:测定接口执行效率
  • 简化设定:在接口执行前输出当前系统时间
  • 开发模式:XML or 注解
  • 思路分析:
    1. 导入坐标(pom.xml)
    2. 制作连接点方法(原始操作,dao接口与实现类)
    3. 制作共性功能(通知类与通知)
    4. 定义切入点
    5. 绑定切入点与通知关系(切面)
2.2 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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>cn.whu</groupId>
  <artifactId>spring_18_aop_quickstart</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <!--spring核心依赖,会将spring-aop传递进来-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <!--切入点表达式依赖,目的是找到切入点方法,也就是找到要增强的方法-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.4</version>
    </dependency>
  </dependencies>

</project>

基础代码:

@Configuration
@ComponentScan("cn.whu")
public class SpringConfig {
}

public interface BookDao {
    public void save();
    public void update();
}

@Repository
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println(LocalDateTime.now());
        System.out.println("book dao save");
    }

    public void update() {
        System.out.println("book dao update ...");
    }
}

//!!!导入一个包 三个注解 Spring就用起来了 !!!
public class App {
    public static void main(String[] args) {
        ApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class);

        BookDao bookDao = ioc.getBean(BookDao.class);

        bookDao.save();
        System.out.println("--------------");
        bookDao.update();
    }
}

在这里插入图片描述


下面再从AOP角度梳理步骤:前两步环境里已经有了 不需要再做了

【第一步】导入aop相关坐标
<dependencies>
    <!--spring核心依赖,会将spring-aop传递进来-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    <!--切入点表达式依赖,目的是找到切入点方法,也就是找到要增强的方法-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>
</dependencies>

在这里插入图片描述

【第二步】定义dao接口与实现类 (以及核心配置类)
public interface BookDao {
    public void save();
    public void update();
}

@Repository
public class BookDaoImpl implements BookDao {

    public void save() {
        System.out.println(LocalDateTime.now());
        System.out.println("book dao save ...");
    }
    public void update(){
        System.out.println("book dao update ...");
    }
}

@Configuration
@ComponentScan("cn.whu")
public class SpringConfig {
}
【第三步】定义通知类,制作通知方法
//通知类必须配置成Spring管理的bean
@Component //肯定给SpringIoC管理呀
public class MyAdvice {
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}
【第四步】定义切入点表达式、配置切面(绑定切入点与通知关系)

切入点定义依托一个不具有实际意义的方法进行,即无参数,无返回值,方法体无实际逻辑

@Component //给SpringIoC当做Bean管理 [专业术语: 通知类必须配置成Spring管理的bean]
@Aspect //告诉Spring这是做AOP的 [专业术语: 设置当前类为通知类]
public class MyAdvice {

    //设置切入点: 也就是定义在谁那里执行
    //@Pointcut注解要求配置在方法上方
    @Pointcut("execution(void cn.whu.dao.BookDao.update())")//切入点定义在哪执行
    private void pt(){}

    //设置在切入点pt()的前面运行当前操作(前置通知)
    @Before("pt()")//共性功能(通知)和在哪执行(切入点)绑定起来
    //定义共性功能
    public void method(){//方法名任意
        System.out.println(LocalDateTime.now());
    }
}

在这里插入图片描述

【第五步】在配置类中进行Spring注解包扫描和开启AOP功能
@Configuration
@ComponentScan("cn.whu")
//开启注解开发AOP功能 (因为不确定是xml还是注解方式 所以要注明下)
@EnableAspectJAutoProxy
public class SpringConfig {
}
测试类和运行结果
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        bookDao.update();
    }
}

在这里插入图片描述
update方法里没有打印时间的代码,是通知方法中的打印时间放执行了,说明对原始方法进行了增强,AOP编程成功
(也就多写了一个类和5个注解)

3 AOP工作流程【理解】

问题导入

什么是目标对象?什么是代理对象?

3.1 AOP工作流程
  1. Spring容器启动
  2. 读取所有切面配置中的切入点 (@Pointcut定义了但没有@Before(“pt()”)配置的切入点不读取)
  3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
    • 匹配失败,创建原始对象
    • 匹配成功,创建原始对象(目标对象)的代理对象
  4. 获取bean执行方法
    • 获取的bean是原始对象时,调用方法并执行,完成操作
    • 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
3.2 AOP核心概念

目标对象(Target):被代理的对象,也叫原始对象,该对象中的方法没有任何功能增强。
代理对象(Proxy):代理后生成的对象,由Spring帮我们创建代理对象。

3.3 在测试类中验证代理对象
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        bookDao.update();
		//打印对象的类名
        System.out.println(bookDao.getClass());
    }
}

在这里插入图片描述

参考:https://blog.csdn.net/m0_52012606/article/details/122585571
静态代理,实现该类的顶层接口,导入该类对象为属性,重新实现对应的接口方法,在调用方法执行执行前后添加一些操作,就实现了对方法的增强,但是这增强一个方法就得写一个类实现"重写"一次,太麻烦了吧。
景泰代理:传入类全路径.方法,通过反射增强,就简单方便多了

4 AOP切入点表达式

问题导入

在切入点表达式中如何简化包名和参数类型书写?

4.1 语法格式
  • 切入点:要进行增强的方法

  • 切入点表达式:要进行增强的方法的描述方式

    • 描述方式一:执行com.itheima.dao包下的BookDao接口中的无参数update方法
    execution(void com.itheima.dao.BookDao.update())
    
    • 描述方式二:执行com.itheima.dao.impl包下的BookDaoImpl类中的无参数update方法
    execution(void com.itheima.dao.impl.BookDaoImpl.update())
    

    也就是说切入点描述的是实现类或者接口都ok

  • 切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)

    execution(public User com.itheima.service.UserService.findById(int))
    
    • 动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
    • 访问修饰符:public,private等,可以省略
    • 返回值:写返回值类型
    • 包名:多级包使用点连接
    • 类/接口名:
    • 方法名:
    • 参数:直接写参数的类型,多个类型用逗号隔开
    • 异常名:方法定义中抛出指定异常,可以省略

一个一个写也累 所以肯定有通配符匹配的写法

4.2 通配符

目的:可以使用通配符描述切入点,快速描述。

  • :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有(任意类型)一个参数的方法

execution(public * com.itheima.*.UserService.find*(*))
  • … :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法

execution(public User com..UserService.findById(..))
  • +:专用于匹配子类类型
execution(* *..*Service+.*(..))

*表示1个
..表示任意

从后往前看,()内肯定是参数,倒数第一个肯定是方法名,倒数第二个肯定类或接口名,再往回倒就不确定了
execution(* *..*.*(..)) 任意包下任意方法 可太疯狂了 可不能这么配
execution(* *..u*(..)) 任意u开头的方法
execution(* *..*e(..)) 任意e结尾的方法
execution(* *…Service+.(…)) 所有Service类或者接口的子类
execution(* cn.whu.*.*Service.find*(..)) 所有业务层查询方法加AOP

4.3 书写技巧
  • 所有代码按照标准规范开发,否则以下技巧全部失效
  • 描述切入点通常描述接口,而不描述实现类 (为了解耦)
  • 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述
  • 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
  • 包名书写尽量不使用…匹配,效率过低,常用*做单个包描述匹配,或精准匹配
  • 接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名
  • 方法名书写以动词进行精准匹配,名词采用*匹配,例如getById书写成getBy*,selectAll书写成select*
  • 参数规则较为复杂,根据业务方法灵活调整
  • 通常 不使用异常作为匹配规则

5 AOP通知类型【重点】

问题导入

请描述一下如何定义环绕通知方法?

5.1 AOP通知分类
  • AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
  • AOP通知共分为5种类型
    • 前置通知:在切入点方法执行之前执行
    • 后置通知:在切入点方法执行之后执行,无论切入点方法内部是否出现异常,后置通知都会执行
    • 环绕通知(重点):手动调用切入点方法并对其进行增强的通知方式。
    • 返回后通知(了解):在切入点方法执行之后执行,如果切入点方法内部出现异常将不会执行。
    • 抛出异常后通知(了解):在切入点方法执行之后执行,只有当切入点方法内部出现异常之后才执行。
5.2 AOP通知详解

环境准备,复制一份上面的module,修改如下:
在这里插入图片描述

public interface BookDao {
    public void update();
    public int select();
}
@Repository
public class BookDaoImpl implements BookDao {

    @Override
    public void update() {
        System.out.println("book dao update is running ...");
    }

    @Override
    public int select() {
        System.out.println("book dao select is running ...");
        return 100;
    }
}



@Configuration
@ComponentScan("cn.whu")
@EnableAspectJAutoProxy
public class SpringConfig {

}

public class App {
    public static void main(String[] args) {
        ApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ioc.getBean(BookDao.class);
        bookDao.update();
    }
}
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void cn.whu.dao.BookDao.update())")
    private void pt(){}

    public void before(){
        System.out.println("before advice ...");
    }
    public void after(){
        System.out.println("after advice ...");
    }
    public void around(){
        System.out.println("around before advice ...");
        System.out.println("around after advice ...");
    }
    public void afterReturning(){
        System.out.println("afterReturning advice ...");
    }
    public void afterThrowing(){
        System.out.println("afterThrowing advice ...");
    }
}
5.2.1 前置通知
  • 名称:@Before
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
  • 范例:
@Before("pt()")
public void before() {
    System.out.println("before advice ...");
}

调用上面的测试方法执行update方法,会在方法体执行前执行before()进行打印

5.2.2 后置通知
  • 名称:@After
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行(抛异常了也会执行)
  • 范例:
@After("pt()")
public void after() {
    System.out.println("after advice ...");
}

调用上面的测试方法执行update方法,会在方法体执行后执行after()进行打印

5.2.3 返回后通知
  • 名称:@AfterReturning(了解)
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行完毕后运行(抛异常了就不会执行)
  • 范例:
@AfterReturning("pt()")
public void afterReturning() {
    System.out.println("afterReturning advice ...");
}
5.2.4 抛出异常后通知
  • 名称:@AfterThrowing(了解)
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
  • 范例:
@AfterThrowing("pt()")
public void afterThrowing() {
    System.out.println("afterThrowing advice ...");
}
5.2.5 环绕通知 (★)
  • 名称:@Around(重点,常用)
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
  • 范例::
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("around before advice ...");
    Object ret = pjp.proceed();//环绕通知有返回值 必须接收下来并原路返回
    System.out.println("around after advice ...");
    return ret;
}

调用上面的测试方法执行update方法,会在方法体执行前执行"around before advice ..."打印,执行后执行System.out.println("around after advice ...");打印

环绕通知注意事项

  1. 环绕通知方法形参必须是ProceedingJoinPoint,表示正在执行的连接点,使用该对象的proceed()方法表示对原始对象方法进行调用,返回值为原始对象方法的返回值。
  2. 环绕通知方法的返回值建议写成Object类型,用于将原始对象方法的返回值进行返回,哪里使用代理对象就返回到哪里。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最终aop.MyAdvice.java

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void cn.whu.dao.BookDao.update())")
    private void pt(){}

    @Pointcut("execution(int cn.whu.dao.BookDao.select())")
    private void pt2(){}

    //@Before("pt()")
    public void before(){
        System.out.println("before advice ...");
    }
    //After("pt2()")
    public void after(){
        System.out.println("after advice ...");
    }

    //@Around("pt()") //最重要最常用
    public void around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice ...");
        //环绕肯定要手动对原始操作进行调用,格式也是固定的
        pjp.proceed();
        System.out.println("around after advice ...");
    }

    //@Around("pt2()")
    //原来函数有返回值 环绕通知这里得返回Object类型,接收调用得返回值并返回
    public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice ...");
        //环绕肯定要手动对原始操作进行调用,格式也是固定的
        Object ret = pjp.proceed();
        System.out.println("around after advice ...");
        return ret;
    }

    //@AfterReturning("pt2()")
    public void afterReturning(){
        System.out.println("afterReturning advice ...");
    }

    @AfterThrowing("pt2()")
    public void afterThrowing(){
        System.out.println("afterThrowing advice ...");
    }
}

二、AOP案例

1 案例-测量业务层接口万次执行效率

问题导入

能不能描述一下环绕通知里面的实现步骤?

1.1 需求和分析

需求:任意业务层接口执行均可显示其执行效率(执行时长)

分析:

​ ①:业务功能:业务层接口执行前后分别记录时间,求差值得到执行效率
​ ②:通知类型选择前后均可以增强的类型——环绕通知

1.2 代码实现
【前置工作】环境准备
  1. Spring整合mybatis对spring_db数据库中的Account进行CRUD操作

  2. Spring整合Junit测试CRUD是否OK。

  3. 在pom.xml中添加aspectjweaver切入点表达式依赖

  4. … …

环境准备:就是前面spring整合junit(mybatis也整合了)的module(spring_16_spring_junit)复制一份,就是本案例的初始环境
在这里插入图片描述
注意加入aspectj依赖

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.4</version>
</dependency>

【第一步】编写通知类
@Component
@Aspect
public class ProjectAdvice {

    @Pointcut("execution(* cn.whu.service.*Service.*(..))")//service层的所有方法 (最后的.*方法名匹配别忘了)
    private void servicePt(){}//切入点方法 private权限

    //@Around("servicePt()") //环绕通知 别写错了
    @Around("ProjectAdvice.servicePt()") //这么写也行 "类名.方法名" 这么写可以引入别的类里面定义的切入点
    public Object runSpeed(ProceedingJoinPoint pjp) throws Throwable {
        Signature signature = pjp.getSignature(); //获取执行的签名对象
        //cn.whu.service.AccountService
        String className = signature.getDeclaringTypeName().toString(); //获取接口/类全限定名
        //findById
        String methodName = signature.getName(); //获取方法名
        //cn.whu.service.AccountService.findById
        String classMethodName = className+"."+methodName; //完整名称
        //System.out.println(classMethodName);

        //执行万次 并统计时间
        long start = System.currentTimeMillis();
        Object ret=null;
        for(int i=0;i<10000;i++){
            ret = pjp.proceed();
        }
        long end = System.currentTimeMillis();
        
        System.out.println(classMethodName +" 的万次运行时间: "+(end-start)+" ms");
        return ret;
    }
}
【第二步】在SpringConfig配置类上开启AOP注解功能
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
@EnableAspectJAutoProxy //开启AOP注解功能
public class SpringConfig {
}
【第三步】运行测试类,查看结果
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTestCase {
    @Autowired
    private AccountService accountService;
    @Test
    public void testFindById(){
        Account account = accountService.findById(1);
    }
    @Test
    public void testFindAll(){
        List<Account> list = accountService.findAll();
    }
}

在这里插入图片描述

在这里插入图片描述
findAll快点,findById慢点,因为findById还要在findAll的基础上进行一次筛选

2 AOP切入点数据获取

问题导入

在环绕通知中可以获取到哪些数据?


环境准备:将之前的spring_20_aop_advice_type复制一份,然后修改一下即可。本博客跨天了,我打算重新创建一份
在这里插入图片描述
创建好目录结构和文件
在这里插入图片描述

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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>cn.whu</groupId>
  <artifactId>spring_22_aop_advice_data</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.4</version>
    </dependency>
  </dependencies>

</project>

写完记得刷新一下

public interface BookDao {
    public String findName(int id);
}
@Repository
public class BookDaoImpl implements BookDao {
    public String findName(int id) {
        System.out.println("id: " + id);
        return "whu";
    }
}

@Configuration
@ComponentScan("cn.whu")
@EnableAspectJAutoProxy //注解做AOP才需要 提前开启一下
public class SpringConfig {

}

  • 直接开测
//测试
public class AOP {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ioc.getBean(BookDao.class);
        String name = bookDao.findName(1);
        System.out.println(name);
    }
}

在这里插入图片描述

最最后AOP类也准备下

@Component //给spring管理Bean
@Aspect // 告诉spring这是AOP
public class MyAdvice {
    @Pointcut("execution(* cn.whu.dao.BookDao.findName(..)))") //注意表达式得写到方法层次
    private void pt(){}

}

环境准备完毕
下面开始获取切入点数据


2.1 获取参数

说明:在前置通知和环绕通知中都可以获取到连接点方法的参数们

  • JoinPoint对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数
@Before("pt()")
public void before(JoinPoint jp){//主要就是记住这个类啦
    Object[] args = jp.getArgs();//获取连接点(原始方法)参数
    System.out.println("方法参数: "+Arrays.toString(args));
    System.out.println("before advice ...");
}

// 参数嘛 所有的通知类型都能获取的 不用怀疑 比如下面的after 剩下的就不再演示了
@After("pt()")
public void after(JoinPoint jp){
    Object[] args = jp.getArgs();
    System.out.println("方法参数: "+Arrays.toString(args));
    System.out.println("after advice ...");
}
  • ProccedJointPoint是JoinPoint的子类
    • 所有通知类型都能获取返回值。around用ProceedingJoinPoint,其他的都用JoinPoint。

around需要用子接口(子接口方法更多,可以调用切入点,around也需要调用切入点)

// 获取参数
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    //获取方法参数
    Object[] args = pjp.getArgs();
    System.out.println("方法参数: "+Arrays.toString(args));
    //执行方法
    Object ret = pjp.proceed();
    //提示这是around
    System.out.println("around after advice ...");
    //返回值要给人家啊
    return ret;
}
//修改参数
@Around("pt()")
public Object around2(ProceedingJoinPoint pjp) throws Throwable {
    Object[] args = pjp.getArgs();
    System.out.println("方法参数: "+Arrays.toString(args));
    args[0] = 123456;//可以修改调用参数的
    Object ret = pjp.proceed(args);//想让修改的参数生效,这里调用时得将参数传进去才行  
    System.out.println("around after advice ...");
    return ret;
}
2.2 获取返回值

说明:在返回后通知和环绕通知中都可以获取到连接点方法的返回值

  • 返回后通知执行时肯定有返回值了,必然可以获取返回值,但是得注明返回值属性名
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(String ret) { //变量名要和returning="ret"的属性值一致
    System.out.println("afterReturning advice ..."+ret);
}

所有的通知都可以获取参数,afterReturning必然也可以,但是注意参数顺序:JoinPoint必须是第一个参数
给个形参接收返回值

@AfterReturning(value = "pt()", returning = "ret")
public void afterReturning2(JoinPoint jp, String ret){
    System.out.println("afterReturning advice ... || "+ ret+ " || "+ Arrays.toString(jp.getArgs()));
}
  • 环绕通知中可以手工书写对原始方法的调用,得到的结果即为原始方法的返回值
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    // 手动调用连接点方法,返回值就是连接点方法的返回值
    Object ret = pjp.proceed();
    return ret; //当然这里可以修改返回值哒 eg: return ret+"666";
}

// BookDaoImpl里加一行 int i = 1/0; 造一个异常试试

2.3 获取异常 (了解)

说明:在抛出异常后通知和环绕通知中都可以获取到连接点方法中出现的异常

  • 抛出异常后通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象
    给个形参接受异常对象
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {//变量名要和throwing = "t"的属性值一致
    System.out.println("afterThrowing advice ..."+ t);
}
  • 抛出异常后通知可以获取切入点方法运行的异常信息,使用形参可以接收运行时抛出的异常对象
@Around("pt()")
public Object around(ProceedingJoinPoint pjp)  {
    Object ret = null;
    //此处需要try...catch处理,catch中捕获到的异常就是连接点方法中抛出的异常
    try {
        ret = pjp.proceed();
    } catch (Throwable t) {
        t.printStackTrace();
    }
    return ret;
}

3 案例-百度网盘密码数据兼容处理

问题导入

请说出我们该使用什么类型的通知来完成这个需求?

3.1 需求和分析

需求:对百度网盘分享链接输入密码时尾部多输入的空格做兼容处理

在这里插入图片描述

分析:
①:在业务方法执行之前对所有的输入参数进行格式处理——trim()
②:使用处理后的参数调用原始方法——环绕通知中存在对原始方法的调用

仔细想想,环绕通知多好啊,这种烦人的需求,竟然写一份代码就够了

3.2 代码实现
【前置工作】环境准备

前面的项目spring_22_aop_advice_data复制一份修改下即可
在这里插入图片描述

@Configuration
@ComponentScan("cn.whu")
@EnableAspectJAutoProxy
public class SpringConfig {

}

//-------------service层代码-----------------------
public interface ResourcesService {
    public boolean openURL(String url ,String password);
}
@Service
public class ResourcesServiceImpl implements ResourcesService {
    @Autowired
    private ResourcesDao resourcesDao;

    public boolean openURL(String url, String password) {
        return resourcesDao.readResources(url,password);
    }
}
//-------------dao层代码-----------------------
public interface ResourcesDao {
    boolean readResources(String url, String password);
}
@Repository
public class ResourcesDaoImpl implements ResourcesDao {
    public boolean readResources(String url, String password) {
        System.out.println(password.length());
        //模拟校验
        return password.equals("root");//正规做法应该key密钥对url进行解密 好处,不用读数据库了
    }
}

先测试下环境有无问题

public class AOP {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class);
        ResourcesService service = ioc.getBean(ResourcesService.class);
        boolean flag = service.openURL("http://pan.baidu.com/hah", "root");//true
        boolean flag = service.openURL("http://pan.baidu.com/hah", "root ");//false
        System.out.println(flag);
    }
}
【第一步】编写通知类
@Component //作为Bean被Spring管理
@Aspect //告诉Spring 我不是普通Bean,我是要做AOP的
public class DataAdvice {

    // 给所有Service层方法 但凡参数有空格的,都帮你trim一下
    @Pointcut("execution(boolean cn.whu.service.*Service.*(..))")
    private void servicePt(){}

    @Around("servicePt()")
    public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {

        Object[] args = pjp.getArgs();
        //for (Object arg : args) { //增强for只能对数组做读,无法修改到数组
        // 必须用fori循环
        for (int i = 0; i < args.length; i++) {
            if(args[i] instanceof String){
                args[i] = ((String) args[i]).trim();
            }
            // 上下两种写法都行
            /*if(args[i].getClass().equals(String.class)){
                args[i] = args[i].toString().trim();
            }*/
        }

        //修改完参数 千万别忘记要传递回来!!!☆
        Object ret = pjp.proceed(args);
        return ret;
    }
}
【第二步】在SpringConfig配置类上开启AOP注解功能
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy // 开启AOP注解 复制的应该直接就做好了
public class SpringConfig {
}
【第三步】运行测试类,查看结果
public class AOP {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class);
        ResourcesService service = ioc.getBean(ResourcesService.class);
        boolean flag = service.openURL("http://pan.baidu.com/hah", "root ");
        System.out.println(flag);
    }
}

在这里插入图片描述

个人感想: AOP真的是共性功能的不二选择啊

4 AOP开发总结

4.1 AOP的核心概念
  • 概念:AOP(Aspect Oriented Programming)面向切面编程,一种编程范式
  • 作用:在不惊动原始设计的基础上为方法进行功能增强
  • 核心概念
    • 代理(Proxy):SpringAOP的核心本质是采用代理模式实现的
    • 连接点(JoinPoint): 在SpringAOP中,理解为任意方法的执行
    • 切入点(Pointcut):匹配连接点的式子,也是具有共性功能的方法描述
    • 通知(Advice):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
    • 切面(Aspect):描述通知与切入点的对应关系 (绑定切入点和切面的 @Before @After… )
    • 目标对象(Target):被代理的原始对象成为目标对象
4.2 切入点表达式语法
  • 切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)

    • execution(* com.itheima.service.Service.(…))
  • 切入点表达式描述通配符:

    • 作用:用于快速描述,范围描述
    • *:匹配任意符号(常用)
    • … :匹配多个连续的任意符号(常用)
    • +:匹配子类类型
  • 切入点表达式书写技巧

    1.按标准规范开发
    2.查询操作的返回值建议使用*匹配
    3.减少使用…的形式描述包
    4.对接口进行描述,使用*表示模块名,例如UserService的匹配描述为*Service
    5.方法名书写保留动词,例如get,使用*表示名词,例如getById匹配描述为getBy*
    6.参数根据实际情况灵活调整

4.3 五种通知类型
  • 前置通知
  • 后置通知
    • 环绕通知(重点)
    • 环绕通知依赖形参ProceedingJoinPoint才能实现对原始方法的调用
    • 环绕通知可以隔离原始方法的调用执行
    • 环绕通知返回值设置为Object类型
    • 环绕通知中可以对原始方法调用过程中出现的异常进行处理
  • 返回后通知
  • 抛出异常后通知

三、Spring事务管理

1 Spring事务简介【重点】

问题导入

Spring提供的事务管理是数据层的事务还是业务层的事务?

1.1 Spring事务作用
  • 事务作用:在数据层保障一系列的数据库操作同成功同失败
  • Spring事务作用:在数据层或**业务层**保障一系列的数据库操作同成功同失败

在这里插入图片描述

1.2 需求和分析
  • 需求:实现任意两个账户间转账操作
  • 需求微缩:A账户减钱,B账户加钱
  • 分析:
    ①:数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)
    ②:业务层提供转账操作(transfer),调用减钱与加钱的操作
    ③:提供2个账号和操作金额执行转账操作
    ④:基于Spring整合MyBatis环境搭建上述操作
  • 结果分析:
    ①:程序正常执行时,账户金额A减B加,没有问题
    ②:程序出现异常后,转账失败,但是异常之前操作成功,异常之后操作失败,整体业务失败
1.3 代码实现

从0开始初始化环境
在这里插入图片描述
创建目录和文件
在这里插入图片描述
JdbcConfig

public class JdbcConfig {

    //这里可以加载jdbc.properties里的变量
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    //1.定义一个方法获得要管理的对象
    //2.添加@Bean,表示当前方法的返回值是一个bean
    @Bean
    public DataSource dataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}

MybatisConfig

public class MybatisConfig {

    @Bean //IoC容器中有SqlSessionFactory了
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {//@Bean注入引用类型 特别方便 提供参数即可
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        //框架的好处,大部分东西都是默认的,不用设计,那些动态变化设置一下就ok了
        ssfb.setTypeAliasesPackage("cn.whu.domain"); //<typeAliases>
        ssfb.setDataSource(dataSource);//<dataSource>
        return ssfb;
    }

    //帮你造mapper 也就是Dao实现类的 (Service里的Autowired有东西自动注入了)
    @Bean //IoC容器中有 所有DaoImpl实现类了
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("cn.whu.dao");//<mappers>
        return msc;
    }
}

SpringConfig

@Configuration
@ComponentScan("cn.whu") //扫描到@service自动帮你创建并管理bean了
//@PropertySource:加载类路径jdbc.properties文件
@PropertySource({"classpath:jdbc.properties"}) //加上classpath:就可以ctrl+鼠标跳转到文件了
@Import({JdbcConfig.class, MybatisConfig.class})
public class SpringConfig {

}

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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>cn.whu</groupId>
  <artifactId>spring_24_case_transfer</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <!-- spring -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <!-- druid连接池 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.16</version>
    </dependency>
    <!-- mybatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>
    <!-- mysql -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>
    <!-- spring-jdbc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <!-- mybatis-spring -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.0</version>
    </dependency>
    <!-- junit 测试还是要有的 -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!-- spring-test和junit得结合着用 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.26</version>
    </dependency>

    <!--竟然不需要AOP aspectjweaver 的包 不是AOP实现的事务吗?-->
  </dependencies>
</project>

竟然不需要AOP aspectjweaver 的包 不是AOP实现的事务吗?

【前置工作】环境准备

Spring整合Mybatis相关代码(依赖、JdbcConfig、MybatisConfig、SpringConfig)省略。

public interface AccountDao {

    @Update("update tbl_account set money = money + #{money} where name = #{name}")
    void inMoney(@Param("name") String name, @Param("money") Double money);

    @Update("update tbl_account set money = money - #{money} where name = #{name}")
    void outMoney(@Param("name") String name, @Param("money") Double money);
}

public interface AccountService {
    /**
     * 转账操作
     * @param out 传出方
     * @param in 转入方
     * @param money 金额
     */
    public void transfer(String out,String in ,Double money) ;
}

@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        int i = 1/0;
        accountDao.inMoney(in,money);
    }
}

环境准备完毕,先浅浅测试一下

@RunWith(SpringJUnit4ClassRunner.class)//Spring整合Junit专用的类加载器
//下面这行配置帮你加载SpringConfig,启动Spring
@ContextConfiguration(classes = {SpringConfig.class})//加载spring核心配置类 把Spring用起来呀
public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() {
        accountService.transfer("Tom","Jerry",100d);
    }
}

运行完毕查看数据库,分别注释和不注释int i = 1/0;执行,没问题,环境就搭建成功了

【第一步】在业务层接口上添加Spring事务管理
public interface AccountService {
    //配置当前接口方法具有事务
    @Transactional
    public void transfer(String out,String in ,Double money) ;
}

注意事项

  1. Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合
  2. 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务
【第二步】设置事务管理器(将事务管理器添加到IOC容器中)

说明:可以在JdbcConfig中配置事务管理器

//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
    DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
    transactionManager.setDataSource(dataSource);
    return transactionManager;
}

注意事项

  1. 事务管理器要根据实现技术进行选择
  2. MyBatis框架使用的是JDBC事务
【第三步】开启注解式事务驱动
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
【第四步】运行测试类,查看结果
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() throws IOException {
        accountService.transfer("Tom","Jerry",100D);//现在就不会出现一方减钱一方不加钱的错误了
    }
}

真的就三步(测试不算),就做好了事务处理,spring确实牛~

2 Spring事务角色【理解】

问题导入

什么是事务管理员,什么是事务协调员?

2.1 Spring事务角色
  • 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
  • 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法

本来outMoney和inMoney两个update操作在dao层都有自己各自的事务,分别为T1,T2。本来一次操作,却有2个事务,肯定出问题。T1不会回滚T2的

在这里插入图片描述

然后业务层transfer开启了事务T(Spring事务), T1、T2都加入T成为了一个事务。(这就OK了)

在这里插入图片描述

为什么T1、T2的事务可以加入T?
答: 因为虽然T是spring事务,T1/T2是mybatis管理数据库的事务,但是他们用的都是一个数据源(连接池)
Spring事务管理器: PlatformTransactionManager(dataSource)
Mybatis工场: SqlSessionFactoryBean(dataSource)
两个dataSource是同一个

3 Spring事务相关配置

问题导入

什么样的异常,Spring事务默认是不进行回滚的?

3.1 事务配置

在这里插入图片描述
在这里写:
在这里插入图片描述


说明:对于RuntimeException类型异常或者Error错误,Spring事务能够进行回滚操作。但是对于编译器异常,Spring事务是不进行回滚的,所以需要使用rollbackFor来设置要回滚的异常。

修改抛出的异常种类:
AccountServiceImpl

public void transfer(String out, String in, Double money) throws IOException {
    accountDao.outMoney(out,money);
    //int i = 1/0;
    if(true) throw new IOException();
    accountDao.inMoney(in,money);
}

再测试:
AccountServiceTest

 @Test
 public void testTransfer() throws IOException {
     accountService.transfer("Tom","Jerry",100d);
 }

发现事务不起作用了 (1000,1000)->(900,1000)。因为IOException既不是错误(Error)也不是运行时异常(RuntimeException)

这是还想回滚的话,得手动配置下,这些异常也要回滚 {里面可以写多种异常,逗号隔开,其实就是数组}
在这里插入图片描述

3.2 案例:转账业务追加日志
需求和分析
  • 需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕
  • 需求微缩:A账户减钱,B账户加钱,数据库记录日志
  • 分析:
    ①:基于转账操作案例添加日志模块,实现数据库中记录日志
    ②:业务层转账操作(transfer),调用减钱、加钱与记录日志功能
  • 实现效果预期:
    无论转账操作是否成功,均进行转账操作的日志留痕
  • 存在的问题:
    日志的记录与转账操作隶属同一个事务,同成功同失败 (都加入一个事务,必然导致这样)
  • 实现效果预期改进:
    无论转账操作是否成功,日志必须保留
  • 事务传播行为:事务协调员对事务管理员所携带事务的处理态度(我加入你,还是不加入,还是开启一个新的,很明显,日志处理service需要一个新的事务)

在这里插入图片描述

【准备工作】环境整备

上面的moudle: spring_24_case_transfer复制一份为spring_24_case_transfer_log即可,并把service层所有的异常删除

USE spring_db;
CREATE TABLE tbl_log(
	id INT PRIMARY KEY AUTO_INCREMENT,
	info VARCHAR(255),
	createDate DATE
);

然后新建LogService.java、LogServiceImpl.java、LogDao 专门的日志模块

public interface LogService {
    //propagation设置事务属性:传播行为设置为当前操作需要新事务
    @Transactional
    void log(String out, String in, Double money);
}

@Service
public class LogServiceImpl implements LogService {

    @Autowired
    private LogDao logDao;
    public void log(String out,String in,Double money ) {
        logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
    }
}

public interface LogDao {
    @Insert("insert into tbl_log (info,createDate) values(#{info},now())")
    void log(String info);
}
【第一步】在AccountServiceImpl中调用logService中添加日志的方法
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Autowired
    private LogService logService;

    public void transfer(String out,String in ,Double money) {
        try{
            accountDao.outMoney(out,money);
            int i = 1/0;
            accountDao.inMoney(in,money);
        }finally {
        	//这样保证 日志一定会运行
            logService.log(out,in,money);
        }
    }
}
【第二步】在LogService的log()方法上设置事务的传播行为

我LogService不加入你们的事务,自己单独开一个事务(不然你抛出异常回滚,把我本来要记录的日志也回滚没了)

public interface LogService {
    //propagation设置事务属性:传播行为设置为当前操作需要新事务
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    void log(String out, String in, Double money);
}
【第三步】运行测试类,查看结果
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() throws IOException {
        accountService.transfer("Tom","Jerry",50D);
    }
}
3.3 事务传播行为

在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring IOC 控制反转:把创建对象的权利交给Spring 创建对象 1.无参构造<bean class=""> 2.静态工厂<bean class="" factory-method=""> 3.实例工厂 <bean bean-factory="" factory-method=""> 管理对象 对象关系DI 构造器注入<construct-arg> set注入<property> 生命周期 scope:prototype/singleton init-method destroy-method API BeanFactory:使用这个工厂创建对象的方式都是懒加载,在调用的时候再创建 ClassPathXmlApplicationContext:使用这个工厂创建对象,他会根据scope智能判断是否懒加载,如果是单例则创建容器时就会创建里面bean的实例,如果是多例在获取使用时才会创建bean实例 FileSystemXmlApplicationContext磁盘路径 AnnotationConfigApplicationContext注解 WebApplicationContext:web环境使用的容器 注解 创建对象 Component:不分层的注解 Controller:web层 Service:service层 Repository:dao层 管理对象 注入 AutoWired Qualifier Resource Value 声明周期 Scope PostConstruct PreDestroy 新注解 Bean:写方法上,将方法的返回值 Configuration:标记配置类 ComponentScan包扫描 PropertySource:加载配置文件 Import:导入其他配置类 AOP 概念:面向切面编程,在不改变源码的情况下对方法进行增强,抽取横切关注点(日志处理,事务管理,安全检查,性能测试等等),使用AOP进行增强,使程序员只需要关注与业务逻辑编写. 专业术语 目标Target:需要增强的类 连接点JoinPoint:目标中可被增强方法 切入点PointCut:被增强方法 增强Advice:增强代码 切面Aspect:切点加通知 织入weaving:讲切面加载进内存形成代理对象的过程 代理Proxy 底层实现 JDK动态代理(默认) 基于接口:代理对象与目标对象是兄弟关系,目标类必须实现接口 CGLIB动态代理 基于父类:代理对象与目标对象是父子关系.目标不能被final修饰 修改默认代理方法:<aop:aspectj-autoproxy proxy-target-class="true"/> 增强种类 前置通知 后置通知 异常通知 最终通知 环绕通知 注意:使用注解的方式,最终通知和后置通知顺序换了,建议使用环绕通知 注解 配置 声明式事务管理 PlatFormTransactionManager:平台事务管理器:定义了commit/rollback Mybatis/jdbc:DataSourceTransactionManager Hibernater:HibernaterTransactionManager TransactionManagerDifinition 传播行为:A-->B,在B上声明是否一定需要事务管理 requerd:必须的(默认),如果A有事务那么就加入A的事务,如果A没有事务那么单独创建一个事务 supports,如果A有事务则加入,如果没有就算了 隔离级别 default:使用数据库默认的隔离级别(mysql:可重复读,oracle:读已提交) readuncommited:读未提交,不可以解决任何问题 readcommited:读已提交,可以解决脏读问题 repeatableRead:可重复读,可以解决脏读,不可重复读问题 Serializbler:串行化,可以解决所有问题 超时时间: 默认-1(永不超时),事务一直不提交也不回滚的时间 是否只读: 默认false TransactionManagerStatus: 事务的一些状态 整合 Spring整合Junit 1.导入依赖spring-test 2.加注解:RunWith、ContextConfiguration 3.注入对象进行测试 Spring整合web 1.导入依赖spring-web 2.配置ContextLoadListener 3.配置 <!--全局初始化参数--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> 4.在Servlet中使用WebApplicationContextUtils获取容器对象 5.使用容器对象去获取Service对象
1. 简介 1.1. 概览 1.2. 使用场景 2. Spring 2.0 的新特性 2.1. 简介 2.2. 控制反转(IoC)容器 2.2.1. 更简单的XML配置 2.2.2. 新的bean作用域 2.2.3. 可扩展的XML编写 2.3. 面向切面编程(AOP) 2.3.1. 更加简单的AOP XML配置 2.3.2. 对@AspectJ 切面的支持 2.4. 中间层 2.4.1. 在XML里更为简单的声明性事务配置 2.4.2. JPA 2.4.3. 异步的JMS 2.4.4. JDBC 2.5. Web层 2.5.1. Spring MVC的表单标签库 2.5.2. Spring MVC合理的默认值 2.5.3. Portlet 框架 2.6. 其他特性 2.6.1. 动态语言支持 2.6.2. JMX 2.6 .3. 任务规划 2.6.4. 对Java 5(Tiger)的支持 2.7. 移植到Spring 2.0 2.7.1. 一些变化 2.7.1.1. Jar包 2.7.1.2. XML配置 2.7.1.3. Deprecated的类和方法 2.7.1.4. Apache OJB 2.7.1.5. iBatis 2.8. 更新的样例应用 2.9. 改进的文档 I. 核心技术 3. 控制反转容器 3.1. 简介 3.2. 容器和bean的基本原理 3.2.1. 容器 3.2.1.1. 配置元数据 3.2.2. 实例化容器 3.2.2.1. 组成基于XML配置元数据 3.2.3. 多种bean 3.2.3.1. 命名bean 3.2.3.2. 实例化bean 3.2.4. 使用容器 3.3. 依赖 3.3.1. 注入依赖 3.3.1.1. Setter注入 3.3.1.2. 构造器注入 3.3.1.3. 一些例子 3.3.2. 构造器参数的解析 3.3.2.1. 构造器参数类型匹配 3.3.2.2. 构造器参数的索引 3.3.3. bean属性及构造器参数详解 3.3.3.1. 直接量(基本类型、Strings类型等。) 3.3.3.2. 引用其它的bean(协作者) 3.3.3.3. 内部bean 3.3.3.4. 集合 3.3.3.5. Nulls 3.3.3.6. XML-based configuration metadata shortcuts 3.3.3.7. 组合属性名称 3.3.4. 使用depends-on 3.3.5. 延迟初始化bean 3.3.6. 自动装配(autowire)协作者 3.3.6.1. 设置Bean使自动装配失效 3.3.7. 依赖检查 3.3.8. 方法注入 3.3.8.1. Lookup方法注入 3.3.8.2. 自定义方法的替代方案 3.4. bean的作用域 3.4.1. Singleton作用域 3.4.2. Prototype作用域 3.4.3. 其他作用域 3.4.3.1. 初始化web配置 3.4.3.2. Request作用域 3.4.3.3. Session作用域 3.4.3.4. global session作用域 3.4.3.5. 作用域bean与依赖 3.4.4. 自定义作用域 3.5. 定制bean特性 3.5.1. Lifecycle接口 3.5.1.1. 初始化回调 3.5.1.2. 析构回调 3.5.2. 了解自己 3.5.2.1. BeanFactoryAware 3.5.2.2. BeanNameAware 3.6. bean定义的继承 3.7. 容器扩展点 3.7.1. 用BeanPostProcessor定制bean 3.7.1.1. 使用BeanPostProcessor的Hello World示例 3.7.1.2. RequiredAnnotationBeanPostProcessor示例 3.7.2. 用BeanFactoryPostProcessor定制配置元数据 3.7.2.1. PropertyPlaceholderConfigurer示例 3.7.2.2. PropertyOverrideConfigurer示例 3.7.3. 使用FactoryBean定制实例化逻辑 3.8. ApplicationContext 3.8.1. 利用MessageSource实现国际化 3.8.2. 事件 3.8.3. 底层资源的访问 3.8.4. ApplicationContext在WEB应用中的实例化 3.9. 粘合代码和可怕的singleton 3.9.1. 使用Singleton-helper类 4. 资源 4.1. 简介 4.2. Resource 接口 4.3. 内置 Resource 实现 4.3.1. UrlResource 4.3.2. ClassPathResource 4.3.3. FileSystemResource 4.3.4. ServletContextResource 4.3.5. InputStreamResource 4.3.6. ByteArrayResource 4.4. ResourceLoader 4.5. ResourceLoaderAware 接口 4.6. 把Resource作为属性来配置 4.7. Application context 和Resource 路径 4.7.1. 构造application context 4.7.1.1. 创建 ClassPathXmlApplicationContext 实例 - 简介 4.7.2. Application context构造器中资源路径的通配符 4.7.2.1. Ant风格的pattern 4.7.2.2. classpath*: 前缀 4.7.2.3. 其他关于通配符的说明 4.7.3. FileSystemResource 提示 5. 校验,数据绑定,BeanWrapper,与属性编辑器 5.1. 简介 5.2. 使用Spring的Validator接口进行校验 5.3. 从错误代码到错误信息 5.4. Bean处理和BeanWrapper 5.4.1. 设置和获取属性值以及嵌套属性 5.4.2. 内建的PropertyEditor实现 5.4.2.1. 注册用户自定义的PropertyEditor 6. 使用Spring进行面向切面编程(AOP) 6.1. 简介 6.1.1. AOP概念 6.1.2. Spring AOP的功能和目标 6.1.3. SpringAOP代理 6.2. @AspectJ支持 6.2.1. 启用@AspectJ支持 6.2.2. 声明一个切面 6.2.3. 声明一个切入点(pointcut) 6.2.3.1. 切入点指定者的支持 6.2.3.2. 合并切入点表达式 6.2.3.3. 共享常见的切入点(pointcut)定义 6.2.3.4. 示例 6.2.4. 声明通知 6.2.4.1. 前置通知(Before advice) 6.2.4.2. 返回后通知(After returning advice) 6.2.4.3. 抛出后通知(After throwing advice) 6.2.4.4. 后通知(After (finally) advice) 6.2.4.5. 环绕通知(Around Advice) 6.2.4.6. 通知参数(Advice parameters) 6.2.4.7. 通知(Advice)顺序 6.2.5. 引入(Introductions) 6.2.6. 切面实例化模型 6.2.7. 例子 6.3. Schema-based AOP support 6.3.1. 声明一个切面 6.3.2. 声明一个切入点 6.3.3. 声明通知 6.3.3.1. 通知(Advice) 6.3.3.2. 返回后通知(After returning advice) 6.3.3.3. 抛出异常后通知(After throwing advice) 6.3.3.4. 后通知(After (finally) advice) 6.3.3.5. 通知 6.3.3.6. 通知参数 6.3.3.7. 通知顺序 6.3.4. 引入 6.3.5. 切面实例化模型 6.3.6. Advisors 6.3.7. 例子 6.4. AOP声明风格的选择 6.4.1. Spring AOP还是完全用AspectJ? 6.4.2. Spring AOP中使用@AspectJ还是XML? 6.5. 混合切面类型 6.6. 代理机制 6.7. 编程方式创建@AspectJ代理 6.8. 在Spring应用中使用AspectJ 6.8.1. 在Spring中使用AspectJ来为domain object进行依赖注入 6.8.1.1. @Configurable object的单元测试 6.8.1.2. 多application context情况下的处理 6.8.2. Spring中其他的AspectJ切面 6.8.3. 使用Spring IoC来配置AspectJ的切面 6.8.4. 在Spring应用中使用AspectJ Load-time weaving(LTW) 6.9. 其它资源 7. Spring AOP APIs 7.1. 简介 7.2. Spring中的切入点API 7.2.1. 概念 7.2.2. 切入点实施 7.2.3. AspectJ切入点表达式 7.2.4. 便利的切入点实现 7.2.4.1. 静态切入点 7.2.4.2. 动态切入点 7.2.5. 切入点的基类 7.2.6. 自定义切入点 7.3. Spring通知API 7.3.1. 通知的生命周期 7.3.2. Spring里的通知类型 7.3.2.1. 拦截around通知 7.3.2.2. 前置通知 7.3.2.3. 异常通知 7.3.2.4. 后置通知 7.3.2.5. 引入通知 7.4. Spring里的advisor(Advisor) API 7.5. 使用ProxyFactoryBean创建AOP代理 7.5.1. 基础 7.5.2. JavaBean属性 7.5.3. 基于JDK和CGLIB的代理 7.5.4. 对接口进行代理 7.5.5. 对类进行代理 7.5.6. 使用“全局”advisor 7.6. 简化代理定义 7.7. 使用ProxyFactory通过编程创建AOP代理 7.8. 操作被通知对象 7.9. 使用“自动代理(autoproxy)”功能 7.9.1. 自动代理bean定义 7.9.1.1. BeanNameAutoProxyCreator 7.9.1.2. DefaultAdvisorAutoProxyCreator 7.9.1.3. AbstractAdvisorAutoProxyCreator 7.9.2. 使用元数据驱动的自动代理 7.10. 使用TargetSources 7.10.1. 热交换目标源 7.10.2. 池化目标源 7.10.3. 原型目标源 7.10.4. ThreadLocal目标源 7.11. 定义新的通知类型 7.12. 更多资源 8. 测试 8.1. 简介 8.2. 单元测试 8.3. 集成测试 8.3.1. Context管理和缓存 8.3.2. 测试fixture的依赖注入 8.3.3. 事务管理 8.3.4. 方便的变量 8.3.5. 示例 8.3.6. 运行集成测试 8.4. 更多资源 II. 中间层数据访问 9. 事务管理 9.1. 简介 9.2. 动机 9.3. 关键抽象 9.4. 使用资源同步的事务 9.4.1. 高层次方案 9.4.2. 低层次方案 9.4.3. TransactionAwareDataSourceProxy 9.5. 声明式事务管理 9.5.1. 理解Spring的声明式事务管理实现 9.5.2. 第一个例子 9.5.3. 回滚 9.5.4. 为不同的bean配置不同的事务语义 9.5.5. <tx:advice/> 有关的设置 9.5.6. 使用 @Transactional 9.5.6.1. @Transactional 有关的设置 9.5.7. 插入事务操作 9.5.8. 结合AspectJ使用 @Transactional 9.6. 编程式事务管理 9.6.1. 使用 TransactionTemplate 9.6.2. 使用 PlatformTransactionManager 9.7. 选择编程式事务管理还是声明式事务管理 9.8. 与特定应用服务器集成 9.8.1. BEA WebLogic 9.8.2. IBM WebSphere 9.9. 公共问题的解决方案 9.9.1. 对一个特定的 DataSource 使用错误的事务管理器 9.10. 更多的资源 10. DAO支持 10.1. 简介 10.2. 一致的异常层次 10.3. 一致的DAO支持抽象类 11. 使用JDBC进行数据访问 11.1. 简介 11.1.1. Spring JDBC包结构 11.2. 利用JDBC核心类实现JDBC的基本操作和错误处理 11.2.1. JdbcTemplate类 11.2.2. NamedParameterJdbcTemplate类 11.2.3. SimpleJdbcTemplate类 11.2.4. DataSource接口 11.2.5. SQLExceptionTranslator接口 11.2.6. 执行SQL语句 11.2.7. 执行查询 11.2.8. 更新数据库 11.3. 控制数据库连接 11.3.1. DataSourceUtils类 11.3.2. SmartDataSource接口 11.3.3. AbstractDataSource类 11.3.4. SingleConnectionDataSource类 11.3.5. DriverManagerDataSource类 11.3.6. TransactionAwareDataSourceProxy类 11.3.7. DataSourceTransactionManager类 11.4. 用Java对象来表达JDBC操作 11.4.1. SqlQuery类 11.4.2. MappingSqlQuery类 11.4.3. SqlUpdate类 11.4.4. StoredProcedure类 11.4.5. SqlFunction类 12. 使用ORM工具进行数据访问 12.1. 简介 12.2. Hibernate 12.2.1. 资源管理 12.2.2. 在Spring的application context中创建 SessionFactory 12.2.3. HibernateTemplate 12.2.4. 不使用回调的基于Spring的DAO实现 12.2.5. 基于Hibernate3的原生API实现DAO 12.2.6. 编程式的事务划分 12.2.7. 声明式的事务划分 12.2.8. 事务管理策略 12.2.9. 容器资源 vs 本地资源 12.2.10. 在应用服务器中使用Hibernate的注意点 12.3. JDO 12.3.1. 建立PersistenceManagerFactory 12.3.2. JdoTemplate和JdoDaoSupport 12.3.3. 基于原生的JDO API实现DAO 12.3.4. 事务管理 12.3.5. JdoDialect 12.4. Oracle TopLink 12.4.1. SessionFactory 抽象层 12.4.2. TopLinkTemplate 和 TopLinkDaoSupport 12.4.3. 基于原生的TopLink API的DAO实现 12.4.4. 事务管理 12.5. iBATIS SQL Maps 12.5.1. iBATIS 1.x和2.x的概览与区别 12.5.2. iBATIS SQL Maps 1.x 12.5.2.1. 创建SqlMap 12.5.2.2. 使用 SqlMapTemplate 和 SqlMapDaoSupport 12.5.3. iBATIS SQL Maps 2.x 12.5.3.1. 创建SqlMapClient 12.5.3.2. 使用 SqlMapClientTemplate 和 SqlMapClientDaoSupport 12.5.3.3. 基于原生的iBATIS API的DAO实现 12.6. JPA 12.6.1. 在Spring环境中建立JPA 12.6.1.1. LocalEntityManagerFactoryBean 12.6.1.2. LocalContainerEntityManagerFactoryBean 12.6.1.3. 处理多个持久化单元 12.6.2. JpaTemplate 和 JpaDaoSupport 12.6.3. 基于原生的JPA实现DAO 12.6.4. 异常转化 12.6.5. 事务管理 12.6.6. JpaDialect III. Web 13. Web框架 13.1. 介绍 13.1.1. 与其他web框架的集成 13.1.2. Spring Web MVC框架的特点 13.2. DispatcherServlet 13.3. 控制器 13.3.1. AbstractController 和 WebContentGenerator 13.3.2. 其它的简单控制器 13.3.3. MultiActionController 13.3.4. 命令控制器 13.4. 处理器映射(handler mapping) 13.4.1. BeanNameUrlHandlerMapping 13.4.2. SimpleUrlHandlerMapping 13.4.3. 拦截器(HandlerInterceptor) 13.5. 视图与视图解析 13.5.1. 视图解析器 13.5.2. 视图解析链 13.5.3. 重定向(Rediret)到另一个视图 13.5.3.1. RedirectView 13.5.3.2. redirect:前缀 13.5.3.3. forward:前缀 13.6. 本地化解析器 13.6.1. AcceptHeaderLocaleResolver 13.6.2. CookieLocaleResolver 13.6.3. SessionLocaleResolver 13.6.4. LocaleChangeInterceptor 13.7. 使用主题 13.7.1. 简介 13.7.2. 如何定义主题 13.7.3. 主题解析器 13.8. Spring对分段文件上传(multipart file upload)的支持 13.8.1. 介绍 13.8.2. 使用MultipartResolver 13.8.3. 在表单中处理分段文件上传 13.9. 使用Spring的表单标签库 13.9.1. 配置标签库 13.9.2. form标签 13.9.3. input标签 13.9.4. checkbox标签 13.9.5. radiobutton标签 13.9.6. password标签 13.9.7. select标签 13.9.8. option标签 13.9.9. options标签 13.9.10. textarea标签 13.9.11. hidden标签 13.9.12. errors标签 13.10. 处理异常 13.11. 惯例优先原则(convention over configuration) 13.11.1. 对控制器的支持: ControllerClassNameHandlerMapping 13.11.2. 对模型的支持:ModelMap (ModelAndView) 13.11.3. 对视图的支持: RequestToViewNameTranslator 13.12. 其它资源 14. 集成视图技术 14.1. 简介 14.2. JSP和JSTL 14.2.1. 视图解析器 14.2.2. 'Plain-old' JSPs versus JSTL 'Plain-old' JSP与JSTL 14.2.3. 帮助简化开发的额外的标签 14.3. Tiles 14.3.1. 需要的资源 14.3.2. 如何集成Tiles 14.3.2.1. InternalResourceViewResolver 14.3.2.2. ResourceBundleViewResolver 14.4. Velocity和FreeMarker 14.4.1. 需要的资源 14.4.2. Context 配置 14.4.3. 创建模板 14.4.4. 高级配置 14.4.4.1. velocity.properties 14.4.4.2. FreeMarker 14.4.5. 绑定支持和表单处理 14.4.5.1. 用于绑定的宏 14.4.5.2. 简单绑定 14.4.5.3. 表单输入生成宏 14.4.5.4. 重载HTML转码行为并使你的标签符合XHTML 14.5. XSLT 14.5.1. 写在段首 14.5.1.1. Bean 定义 14.5.1.2. 标准MVC控制器代码 14.5.1.3. 把模型数据转化为XML 14.5.1.4. 定义视图属性 14.5.1.5. 文档转换 14.5.2. 小结 14.6. 文档视图(PDF/Excel) 14.6.1. 简介 14.6.2. 配置和安装 14.6.2.1. 文档视图定义 14.6.2.2. Controller 代码 14.6.2.3. Excel视图子类 14.6.2.4. PDF视图子类 14.7. JasperReports 14.7.1. 依赖的资源 14.7.2. 配置 14.7.2.1. 配置ViewResolver 14.7.2.2. 配置View 14.7.2.3. 关于报表文件 14.7.2.4. 使用 JasperReportsMultiFormatView 14.7.3. 构造ModelAndView 14.7.4. 使用子报表 14.7.4.1. 配置子报表文件 14.7.4.2. 配置子报表数据源 14.7.5. 配置Exporter的参数 15. 集成其它Web框架 15.1. 简介 15.2. 通用配置 15.3. JavaServer Faces 15.3.1. DelegatingVariableResolver 15.3.2. FacesContextUtils 15.4. Struts 15.4.1. ContextLoaderPlugin 15.4.1.1. DelegatingRequestProcessor 15.4.1.2. DelegatingActionProxy 15.4.2. ActionSupport 类 15.5. Tapestry 15.5.1. 注入 Spring 托管的 beans 15.5.1.1. 将 Spring Beans 注入到 Tapestry 页面中 15.5.1.2. 组件定义文件 15.5.1.3. 添加抽象访问方法 15.5.1.4. 将 Spring Beans 注入到 Tapestry 页面中 - Tapestry 4.0+ 风格 15.6. WebWork 15.7. 更多资源 16. Portlet MVC框架 16.1. 介绍 16.1.1. 控制器 - MVC中的C 16.1.2. 视图 - MVC中的V 16.1.3. Web作用范围的Bean 16.2. DispatcherPortlet 16.3. ViewRendererServlet 16.4. 控制器 16.4.1. AbstractController和PortletContentGenerator 16.4.2. 其它简单的控制器 16.4.3. Command控制器 16.4.4. PortletWrappingController 16.5. 处理器映射 16.5.1. PortletModeHandlerMapping 16.5.2. ParameterHandlerMapping 16.5.3. PortletModeParameterHandlerMapping 16.5.4. 增加 HandlerInterceptor 16.5.5. HandlerInterceptorAdapter 16.5.6. ParameterMappingInterceptor 16.6. 视图和它们的解析 16.7. Multipart文件上传支持 16.7.1. 使用PortletMultipartResolver 16.7.2. 处理表单里的文件上传 16.8. 异常处理 16.9. Portlet应用的部署 IV. 整合 17. 使用Spring进行远程访问与Web服务 17.1. 简介 17.2. 使用RMI暴露服务 17.2.1. 使用 RmiServiceExporter 暴露服务 17.2.2. 在客户端链接服务 17.3. 使用Hessian或者Burlap通过HTTP远程调用服务 17.3.1. 为Hessian配置DispatcherServlet 17.3.2. 使用HessianServiceExporter暴露你的bean 17.3.3. 客户端连接服务 17.3.4. 使用Burlap 17.3.5. 对通过Hessian或Burlap暴露的服务使用HTTP基础认证 17.4. 使用HTTP调用器暴露服务 17.4.1. 暴露服务对象 17.4.2. 在客户端连接服务 17.5. Web服务 17.5.1. 使用JAXI-RPC暴露服务 17.5.2. 访问Web服务 17.5.3. 注册bean映射 17.5.4. 注册自己的处理方法 17.5.5. 使用XFire来暴露Web服务 17.6. 对远程接口不提供自动探测 17.7. 在选择这些技术时的一些考虑 18. Enterprise Java Bean(EJB)集成 18.1. 简介 18.2. 访问EJB 18.2.1. 概念 18.2.2. 访问本地的无状态Session Bean(SLSB) 18.2.3. 访问远程SLSB 18.3. 使用Spring提供的辅助类实现EJB组件 19. JMS 19.1. 简介 19.2. 使用Spring JMS 19.2.1. JmsTemplate 19.2.2. 连接工厂 19.2.3. (消息)目的地管理 19.2.4. 消息侦听容器 19.2.4.1. SimpleMessageListenerContainer 19.2.4.2. DefaultMessageListenerContainer 19.2.4.3. ServerSessionMessageListenerContainer 19.2.5. 事务管理 19.3. 发送一条消息 19.3.1. 使用消息转换器 19.3.2. SessionCallback 和ProducerCallback 19.4. 接收消息 19.4.1. 同步接收 19.4.2. 异步接收 - 消息驱动的POJOs 19.4.3. SessionAwareMessageListener 接口 19.4.4. MessageListenerAdapter 19.4.5. 事务中的多方参与 20. JMX 20.1. 介绍 20.2. 输出bean到JMX 20.2.1. 创建一个MBeanServer 20.2.2. 复用现有的MBeanServer 20.2.3. MBean的惰性初始化 20.2.4. MBean的自动注册 20.2.5. 控制注册行为 20.3. 控制bean的管理接口 20.3.1. MBeanInfoAssembler 接口 20.3.2. 使用源码级元数据 20.3.3. 使用JDK 5.0注解 20.3.4. 源代码级的元数据类型 20.3.5. 接口AutodetectCapableMBeanInfoAssembler 20.3.6. 用Java接口定义管理接口 20.3.7. 使用MethodNameBasedMBeanInfoAssembler 20.4. 控制bean的 ObjectName 20.4.1. 从Properties中读取ObjectName 20.4.2. 使用 MetadataNamingStrategy 20.5. JSR-160连接器 20.5.1. 服务器端连接器 20.5.2. 客户端连接器 20.5.3. 基于Burlap/Hessian/SOAP的JMX 20.6. 通过代理访问MBeans 20.7. 通知 20.7.1. 为通知注册监听器 20.7.2. 发布通知 20.8. 更多资源 21. JCA CCI 21.1. 介绍 21.2. 配置CCI 21.2.1. 连接器配置 21.2.2. 在Spring中配置ConnectionFactory 21.2.3. 配置CCI连接 21.2.4. 使用一个 CCI 单连接 21.3. 使用Spring的 CCI访问支持 21.3.1. 记录转换 21.3.2. CciTemplate 类 21.3.3. DAO支持 21.3.4. 自动输出记录生成 21.3.5. 总结 21.3.6. 直接使用一个 CCI Connection 接口和Interaction接口 21.3.7. CciTemplate 使用示例 21.4. 建模CCI访问为操作对象 21.4.1. MappingRecordOperation 21.4.2. MappingCommAreaOperation 21.4.3. 自动输出记录生成 21.4.4. 总结 21.4.5. MappingRecordOperation 使用示例 21.4.6. MappingCommAreaOperation 使用示例 21.5. 事务 22. Spring邮件抽象层 22.1. 简介 22.2. Spring邮件抽象结构 22.3. 使用Spring邮件抽象 22.3.1. 可插拔的MailSender实现 22.4. 使用 JavaMail MimeMessageHelper 22.4.1. 创建一条简单的MimeMessage,并且发送出去 22.4.2. 发送附件和嵌入式资源(inline resources) 23. Spring中的定时调度(Scheduling)和线程池(Thread Pooling) 23.1. 简介 23.2. 使用OpenSymphony Quartz 调度器 23.2.1. 使用JobDetailBean 23.2.2. 使用 MethodInvokingJobDetailFactoryBean 23.2.3. 使用triggers和SchedulerFactoryBean来包装任务 23.3. 使用JDK Timer支持类 23.3.1. 创建定制的timers 23.3.2. 使用 MethodInvokingTimerTaskFactoryBean类 23.3.3. 打包:使用TimerFactoryBean来设置任务 23.4. SpringTaskExecutor抽象 23.4.1. TaskExecutor接口 23.4.2. 何时使用TaskExecutor接口 23.4.3. TaskExecutor类型 23.4.4. 使用TaskExecutor接口 24. 动态语言支持 24.1. 介绍 24.2. 第一个例子 24.3. 定义动态语言支持的bean 24.3.1. 公共概念 24.3.1.1. <lang:language/> 元素 24.3.1.2. Refreshable bean 24.3.1.3. 内置动态语言源文件 24.3.1.4. 理解dynamic-language-backed bean context的构造器注入 24.3.2. JRuby beans 24.3.3. Groovy beans 24.3.4. BeanShell beans 24.4. 场景 24.4.1. Spring MVC控制器脚本化 24.4.2. Validator脚本化 24.5. 更多的资源 25. 注解和源代码级的元数据支持 25.1. 简介 25.2. Spring的元数据支持 25.3. 注解 25.3.1. @Required 25.3.2. Spring中的其它@Annotations 25.4. 集成Jakarta Commons Attributes 25.5. 元数据和Spring AOP自动代理 25.5.1. 基本原理 25.5.2. 声明式事务管理 25.5.3. 缓冲 25.5.4. 自定义元数据 25.6. 使用属性来减少MVC web层配置 25.7. 元数据属性的其它用法 25.8. 增加对额外元数据API的支持 A. XML Schema-based configuration A.1. Introduction A.2. XML Schema-based configuration A.2.1. Referencing the schemas A.2.2. The util schema A.2.2.1. <util:constant/> A.2.2.2. <util:property-path/> A.2.2.3. <util:properties/> A.2.2.4. <util:list/> A.2.2.5. <util:map/> A.2.2.6. <util:set/> A.2.3. The jee schema A.2.3.1. <jee:jndi-lookup/> (simple) A.2.3.2. <jee:jndi-lookup/> (with single JNDI environment setting) A.2.3.3. <jee:jndi-lookup/> (with multiple JNDI environment settings) A.2.3.4. <jee:jndi-lookup/> (complex) A.2.3.5. <jee:local-slsb/> (simple) A.2.3.6. <jee:local-slsb/> (complex) A.2.3.7. <jee:remote-slsb/> A.2.4. The lang schema A.2.5. The tx (transaction) schema A.2.6. The aop schema A.2.7. The tool schema A.2.8. The beans schema A.3. Setting up your IDE A.3.1. Setting up Eclipse A.3.2. Setting up IntelliJ IDEA A.3.3. Integration issues A.3.3.1. XML parsing errors in the Resin v.3 application server B. Extensible XML authoring B.1. Introduction B.2. Authoring the schema B.3. Coding a NamespaceHandler B.4. Coding a BeanDefinitionParser B.5. Registering the handler and the schema B.5.1. META-INF/spring.handlers B.5.2. META-INF/spring.schemas C. spring-beans-2.0.dtd D. spring.tld D.1. Introduction D.2. The bind tag D.3. The escapeBody tag D.4. The hasBindErrors tag D.5. The htmlEscape tag D.6. The message tag D.7. The nestedPath tag D.8. The theme tag D.9. The transform tag E. spring-form.tld E.1. Introduction E.2. The checkbox tag E.3. The errors tag E.4. The form tag E.5. The hidden tag E.6. The input tag E.7. The label tag E.8. The option tag E.9. The options tag E.10. The password tag E.11. The radiobutton tag E.12. The select tag E.13. The textarea tag F. Spring 2.0 开发手册中文化项目 F.1. 声明 F.2. 致谢 F.3. 参与人员及任务分配 F.4. Spring 2.0 正式版开发手册翻译说明 F.5. 项目历程 F.5.1. Spring 2.0 RC2 开发手册翻译项目 F.5.2. Spring 2.0 正式版开发手册翻译项目
Spring Boot中,使用AOP环绕通知可以通过配置pom文件和编写测试类来实现。首先,在pom文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` 这个依赖会自动开启自动代理,相当于使用了`@EnableAspectJAutoProxy`注解,因此不需要再手动添加这个注解。\[1\]\[2\] 接下来,可以编写一个使用Spring Boot单元测试的测试类。首先,导入需要的类: ``` import com.ljs.springbootplace.service.MyService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; ``` 然后,在测试类上添加注解`@RunWith(SpringJUnit4ClassRunner.class)`和`@SpringBootTest(classes = SpringbootplaceApplication.class)`,指定运行的JUnit运行器和Spring Boot的启动类。同时,注入需要测试的服务类: ``` @Autowired private MyService myService; ``` 最后,编写测试方法,调用需要测试的方法: ``` @Test public void test() { String userNameById = myService.getUserNameById_testAspect(1);//测试aop System.out.println(userNameById); } ``` 这样就可以使用Spring Boot的AOP功能进行环绕通知的测试了。\[3\] #### 引用[.reference_title] - *1* [SpringBoot-AOP环绕通知记录日志/鉴权](https://blog.csdn.net/wjg8209/article/details/119675560)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [springboot实现aop环绕通知](https://blog.csdn.net/weixin_43421537/article/details/104778208)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值