Spring2

AOP

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

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

AOP入门案例

【第一步】导入aop相关坐标

 1 <dependencies>
 2     <!--spring核心依赖,会将spring-aop传递进来-->
 3     <dependency>
 4         <groupId>org.springframework</groupId>
 5         <artifactId>spring-context</artifactId>
 6         <version>5.2.10.RELEASE</version>
 7     </dependency>
 8     <!--切入点表达式依赖,目的是找到切入点方法,也就是找到要增强的方法-->
 9     <dependency>
10         <groupId>org.aspectj</groupId>
11         <artifactId>aspectjweaver</artifactId>
12         <version>1.9.4</version>
13     </dependency>
14 </dependencies>

【第二步】定义dao接口与实现类

 1 public interface BookDao {
 2     public void save();
 3     public void update();
 4 }
 5 
 6 @Repository
 7 public class BookDaoImpl implements BookDao {
 8     public void save() {
 9         System.out.println(System.currentTimeMillis());
10         System.out.println("book dao save ...");
11     }
12     public void update(){
13         System.out.println("book dao update ...");
14     }
15 }

【第三步】定义通知类,制作通知方法

1 //通知类必须配置成Spring管理的bean
2 @Component
3 public class MyAdvice {
4     public void method(){
5         System.out.println(System.currentTimeMillis());
6     }
7 }

【第四步】定义切入点表达式、配置切面(绑定切入点与通知关系)

 1 //通知类必须配置成Spring管理的bean
 2 @Component
 3 //设置当前类为切面类类
 4 @Aspect
 5 public class MyAdvice {
 6     //设置切入点,@Pointcut注解要求配置在方法上方
 7     @Pointcut("execution(void com.test.dao.BookDao.update())")
 8     private void pt(){}
 9 
10     //设置在切入点pt()的前面运行当前操作(前置通知)
11     @Before("pt()")
12     public void method(){
13         System.out.println(System.currentTimeMillis());
14     }
15 }

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

【第五步】在配置类中进行Spring注解包扫描和开启AOP功能

1 @Configuration
2 @ComponentScan("com.test")
3 //开启注解开发AOP功能
4 @EnableAspectJAutoProxy
5 public class SpringConfig {
6 }

AOP工作流程【理解】

AOP工作流程

1. Spring容器启动
2. 读取所有切面配置中的切入点
3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
  匹配失败,创建原始对象
  匹配成功,创建原始对象(目标对象)的代理对象
4. 获取bean执行方法
  获取的bean是原始对象时,调用方法并执行,完成操作
  获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

AOP核心概念

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

AOP切入点表达式

语法格式

切入点:要进行增强的方法
切入点表达式:要进行增强的方法的描述方式
  描述方式一:执行com.test.dao包下的BookDao接口中的无参数update方法
    execution(void com.test.dao.BookDao.update())
  描述方式二:执行com.test.dao.impl包下的BookDaoImpl类中的无参数update方法
    execution(void com.test.dao.impl.BookDaoImpl.update())
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
  execution(public User com.test.service.UserService.findById(int))
    动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
    访问修饰符:public,private等,可以省略
    返回值:写返回值类型
    包名
    类/接口名
    方法名
    参数:直接写参数的类型,多个类型用逗号隔开
    异常名:方法定义中抛出指定异常,可以省略

通配符

可以使用通配符描述切入点,快速描述。
  *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
    匹配com.test包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
      execution(public * com.test.*.UserService.find*(*))
  ..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
    匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
      execution(public User com..UserService.findById(..))
  +:专用于匹配子类类型
    execution(* *..*Service+.*(..))

AOP通知类型【重点】

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

AOP通知详解

前置通知

名称:@Before
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
范例:

1 @Before("pt()")
2 public void before() {
3     System.out.println("before advice ...");
4 }

后置通知

名称:@After
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
范例:

1 @After("pt()")
2 public void after() {
3     System.out.println("after advice ...");
4 }

环绕通知

名称:@Around(重点,常用)
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
范例:

1 @Around("pt()")
2 public Object around(ProceedingJoinPoint pjp) throws Throwable {
3     System.out.println("around before advice ...");
4     Object ret = pjp.proceed();
5     System.out.println("around after advice ...");
6     return ret;
7 }

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

返回后通知

名称:@AfterReturning(了解)
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行完毕后运行
范例:

1 @AfterReturning("pt()")
2 public void afterReturning() {
3     System.out.println("afterReturning advice ...");
4 }

抛出异常后通知

名称:@AfterThrowing(了解)
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
范例:

1 @AfterThrowing("pt()")
2 public void afterThrowing() {
3     System.out.println("afterThrowing advice ...");
4 }

环绕通知获取接口/类全限定名、方法名

 1 @Component
 2 @Aspect
 3 public class ProjectAdvice {
 4     //匹配业务层的所有方法
 5     @Pointcut("execution(* com.test.service.*Service.*(..))")
 6     private void servicePt(){}
 7 
 8     //设置环绕通知,在原始操作的运行前后记录执行时间
 9     @Around("ProjectAdvice.servicePt()") //本类类名可以省略不写
10     public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
11         //获取执行的签名对象
12         Signature signature = pjp.getSignature();
13         //获取接口/类全限定名
14         String className = signature.getDeclaringTypeName();
15         //获取方法名
16         String methodName = signature.getName();
17         //记录开始时间
18         long start = System.currentTimeMillis();
19         //执行万次操作
20         for (int i = 0; i < 10000; i++) {
21            pjp.proceed();
22         }
23         //记录结束时间
24         long end = System.currentTimeMillis();
25         //打印执行结果
26         System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms");
27     }
28 }

AOP切入点数据获取

获取参数

JoinPoint对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数(适用于前置、后置、返回后、抛出异常后通知

1 @Before("pt()")
2 public void before(JoinPoint jp) {
3     Object[] args = jp.getArgs(); //获取连接点方法的参数们
4     System.out.println(Arrays.toString(args));
5 }

ProceedJointPoint是JoinPoint的子类(适用于环绕通知

1 @Around("pt()")
2 public Object around(ProceedingJoinPoint pjp) throws Throwable {
3     Object[] args = pjp.getArgs(); //获取连接点方法的参数们
4     System.out.println(Arrays.toString(args));
5     args[0]=666;
6     //Object ret = pjp.proceed();
7     Object ret = pjp.proceed(args); //可通过该方式对数据进行处理再返回
8     return ret;
9 }

获取返回值

返回后通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象

1 @AfterReturning(value = "pt()",returning = "ret")
2 public void afterReturning(Object ret) { //变量名要和returning="ret"的属性值一致
3     System.out.println("afterReturning advice ..."+ret);
4 }

  注:如参数JoinPoint与返回值ret都存在,JoinPoint一定要在前,否则报错。如存在参数JoinPoint或ProceedingJoinPoint必须在第一位。

1 @AfterReturning(value = "pt()",returning = "ret")
2 public void afterReturning(JoinPoint jp,Object ret) { 
3     System.out.println("afterReturning advice ..."+ret);
4 }

环绕通知中可以手工书写对原始方法的调用,得到的结果即为原始方法的返回值

1 @Around("pt()")
2 public Object around(ProceedingJoinPoint pjp) throws Throwable {
3     // 手动调用连接点方法,返回值就是连接点方法的返回值
4     Object ret = pjp.proceed();
5     return ret;
6 }

获取异常

抛出异常后通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象

1 @AfterThrowing(value = "pt()",throwing = "t")
2 public void afterThrowing(Throwable t) {//变量名要和throwing = "t"的属性值一致
3     System.out.println("afterThrowing advice ..."+ t);
4 }

环绕通知可以获取切入点方法运行的异常信息,使用形参可以接收运行时抛出的异常对象

 1 @Around("pt()")
 2 public Object around(ProceedingJoinPoint pjp)  {
 3     Object ret = null;
 4     //此处需要try...catch处理,catch中捕获到的异常就是连接点方法中抛出的异常
 5     try {
 6         ret = pjp.proceed();
 7     } catch (Throwable t) {
 8         t.printStackTrace();
 9     }
10     return ret;
11 }

Spring事务管理

Spring事务简介【重点】

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

代码实现

前置工作】环境准备

 1 public interface AccountDao {
 2     @Update("update tbl_account set money = money + #{money} where name = #{name}")
 3     void inMoney(@Param("name") String name, @Param("money") Double money);
 4 
 5     @Update("update tbl_account set money = money - #{money} where name = #{name}")
 6     void outMoney(@Param("name") String name, @Param("money") Double money);
 7 }
 8 
 9 public interface AccountService {
10     /**
11      * 转账操作
12      * @param out 传出方
13      * @param in 转入方
14      * @param money 金额
15      */
16     public void transfer(String out,String in ,Double money) ;
17 }
18 
19 @Service
20 public class AccountServiceImpl implements AccountService {
21     @Autowired
22     private AccountDao accountDao;
23 
24     public void transfer(String out,String in ,Double money) {
25         accountDao.outMoney(out,money);
26         int i = 1/0;
27         accountDao.inMoney(in,money);
28     }
29 }

【第一步】在业务层接口上添加Spring事务管理

1 public interface AccountService {
2     //配置当前接口方法具有事务
3     @Transactional
4     public void transfer(String out,String in ,Double money) ;
5 }

注:
1. Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合。
2. 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务。

【第二步】在JdbcConfig中设置事务管理器(将事务管理器添加到IOC容器中)

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

注:
1. 事务管理器要根据实现技术进行选择
2. MyBatis框架使用的是JDBC事务

【第三步】开启注解式事务驱动

1 @Configuration
2 @ComponentScan("com.test")
3 @PropertySource("classpath:jdbc.properties")
4 @Import({JdbcConfig.class,MybatisConfig.class})
5 //开启注解式事务驱动
6 @EnableTransactionManagement
7 public class SpringConfig {
8 }

【第四步】运行测试类,查看结果

 1 @RunWith(SpringJUnit4ClassRunner.class)
 2 @ContextConfiguration(classes = SpringConfig.class)
 3 public class AccountServiceTest {
 4     @Autowired
 5     private AccountService accountService;
 6 
 7     @Test
 8     public void testTransfer() throws IOException {
 9         accountService.transfer("Tom","Jerry",100D);
10     }
11 }

Spring事务角色

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

Spring事务相关配置

事务配置

1 public interface AccountService{
2     @Transactional(readOnly = true,timeout = -1)
3     public void tranfer(String out,String in,Double money) throws IOException;
4 }

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

1 public interface AccountService{
2     @Transactional(rollbackFor = {IOException.class})
3     public void tranfer(String out,String in,Double money) throws IOException;
4 }

事务传播行为

 事务传播行为:事务协调员对事务管理员所携带事务的处理态度。

在业务层接口上添加Spring事务,设置事务传播行为REQUIRES_NEW (需要新事务):

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

posted @ 2023-07-24 00:10  溯鸣  阅读(18)  评论(0编辑  收藏  举报