Spring新手指南(下)

Spring新手指南(下)

AOP:

概念:(Aspect Oriented Programing)指在程序运行期间,将某段代码动态的切入指定方法指定位置进行运行的编程方式

动态代理

注意点: 被代理的对象必须实现某个接口(即代理对象与被代理对象均实现了同一个接口)

专业术语

在这里插入图片描述

初体验

使用步骤:

  • 导包
    基础包+增强包
  • 写配置
    1. 将目标类和切面类(封装了通知方法)加入到容器中
    2. 使用@Aspect注解告诉Spring这个一个切面类
    3. 告诉Spring,切面类里面的每一个方法,都是何时何地的运行
    package pers.lele.utils;
    
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    /**
    * @Before: 前置通知, 在方法执行之前执行
    * @After: 后置通知, 在方法执行之后执行
    * @AfterRunning:返回通知, 在方法成功执行返回结果之后执行
    * @AfterThrowing: 异常通知, 在方法抛出异常之后
    * @Around: 环绕通知, 围绕着方法执行
    */
    
    //标识该类是一个切面类
    @Component
    @Aspect
    public class LogUtils {
    
      //定义通知方法
    
      @Before("execution(public int pers.lele.service.MyMathCalculator.*(int, int))")
      public void LogBefore() {
          System.out.println("LogBefore方法被执行了...");
      }
    
      @After("execution(public int pers.lele.service.MyMathCalculator.*(int, int))")
      public void LogAfter() {
          System.out.println("LogAfter方法被执行了...");
      }
    
      @AfterReturning("execution(public int pers.lele.service.MyMathCalculator.*(int, int))")
      public void LogAfterReturning() {
          System.out.println("LogAfterReturning方法被执行了...");
      }
    
      @After("execution(public int pers.lele.service.MyMathCalculator.*(int, int))")
      public void LogAfterThrowing() {
          System.out.println("LogAfterThrowing方法被执行了...");
      }
    }
    
    1. 开启基于注解的AOP功能
      <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
  • 测试
    @SpringJUnitConfig(locations = "classpath:applicationContext.xml")
    @RunWith(SpringJUnit4ClassRunner.class)
    public class CalculatorTest {
    
        @Autowired
        Calculator calculator;
    //    MyMathCalculator myMathCalculator;
    
        @Test
        public void test01() {
            /**
             * org.springframework.beans.factory.UnsatisfiedDependencyException:
             * Error creating bean with name 'CalculatorTest':
             * Unsatisfied dependency expressed through field 'myMathCalculator';
             * nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException:
             * Bean named 'myMathCalculator' is expected to be of type 'pers.lele.service.MyMathCalculator'
             * but was actually of type 'com.sun.proxy.$Proxy23'
             */
            /*只能通过接口来获取bean*/
    //        int result = myMathCalculator.add(1, 2);
    //        System.out.println(result);
        }
    
        @Test
        public void test02() {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            Calculator bean = context.getBean(Calculator.class);
    //        System.out.println(bean.getClass());
    //        int result = bean.add(1, 2);
    //        System.out.println(result);
        }
    }
    

细节

  • 若目标类实现了接口,则若从容器中通过类型来拿到目标对象,要用接口类型来获取,若从容器中通过id来拿到对象,要用接口来接受该对象

  • AOP的底层是动态代理,容器中保存的组件是它的代理对象

  • 即使目标类不实现接口,也可以获取到代理对象(通过cglib),用目标类接收就可以

  • 切入点表达式:execution(访问权限符 返回值类型 方法全类名(参数表))
    通配符* :
    1. 匹配一个或多个字符
    2. 匹配任意一个参数
    3. 只能匹配一层路径
    4. 权限位置*不能表示任意,当不写时表示任意,
    通配符有…:
    1. 匹配任意多个参数
    2. 匹配任意多层路径

    最模糊的 execution(* .(…))

    &&:切入的位置同时满足
    || :
    !:

  • 当程序正常运行时,执行顺序 before after afterReturning,当程序异常运行时,执行顺序 before after adterthrowing

      try{
          @before
          div();
          @AfterReturning
      }catch(){
          @AfterThrowing
      }final {
          @After
      }
    
  • 在通知方法的参数列表上写一个参数 JoinPoint 来获取方法的详细信息(获取连接点的详细信息)

```java
      @Before("execution(public int pers.lele.service.MyMathCalculator.*(int, int))")
      public void LogBefore(JoinPoint joinPoint) {
          Signature signature = joinPoint.getSignature();
          String name = signature.getName();
          System.out.println(name+" : "+ Arrays.asList(joinPoint.getArgs()));
          System.out.println("LogBefore方法被执行了...");
      }
```
  • 在标有注解@AfterReturning的方法上,通过returning来指定接收方法的返回值

     @AfterReturning(value = "execution(public int pers.lele.service.MyMathCalculator.*(int, int))",
             returning = "result")
     public void LogAfterReturning(Object result) {
         System.out.println("结果是:" + result);
         System.out.println("LogAfterReturning方法被执行了...");
     }
    
  • 在标有注解@AfterThrowing,通过throwing来指定接收方法的返回值

   @AfterThrowing(value = "execution(public int pers.lele.service.MyMathCalculator.*(int, int))",
           throwing = "result")
   public void LogAfterThrowing(Throwable result) {
       System.out.println("错误信息:" + result);
       System.out.println("LogAfterThrowing方法被执行了...");
   }
  • (Spring对通知方法的约束)通知方法的参数不能随便写,必须要Spring清楚
     //因为方法参数的返回值是doubel类型,而真正的返回值是int类型,当类型不匹配时就不会执行响应的通知方法
   @AfterReturning(value = "execution(public int pers.lele.service.MyMathCalculator.*(int, int))",
           returning = "result")
   public void LogAfterReturning(double result) {
       System.out.println("结果是:" + result);
       System.out.println("LogAfterReturning方法被执行了...");
   }
  • 抽取可重用的切入点表达式

    1. 随便声明一个没有实现的返回void的空方法
    2. 给方法上标注@Pointcut注解
          @Pointcut("execution(public int pers.lele.service.MyMathCalculator.*(int, int))")
          public void myPointCut(){
      
          }
          //定义通知方法
      
          @Before(value = "myPointCut()")
          public void LogBefore(JoinPoint joinPoint) {
              Signature signature = joinPoint.getSignature();
              String name = signature.getName();
              System.out.println(name + " : " + Arrays.asList(joinPoint.getArgs()));
              System.out.println("LogBefore方法被执行了...");
          }
    
  • 环绕通知:@Around:本身就是一个动态代理

      //引用可重用切入点
       @Around("myPointCut()")
          public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
              Object result = null;
              //获取参数
              Object[] args = pjp.getArgs();
              //执行方法
              try {
                  System.out.println("前置通知");
                  result = pjp.proceed(args);
                  System.out.println("返回通知");
              } catch (Exception e) {
                  e.printStackTrace();
                  System.out.println("异常通知");
              } finally {
                  System.out.println("后置通知");
                  return result;
              }
          }
        //顺序 前置通知 返回通知 后置通知
    
  • 环绕通知和普通通知一起作用的执行顺序

    • 正常情况下:环绕前置 普通前置 方法执行 环绕返回 环绕后置 普通后置 普通返回
    • 异常情况下:环绕前置 普通前置 方法执行 环绕异常 环绕后置 普通后置 普通返回
      try{
        普通通知:LogBefore方法被执行了...
            {
                //环绕通知
               try{
                    环绕通知:前置通知
                    方法执行
                    环绕通知:返回通知   
               }catch(){
                    环绕通知:异常通知   
               }final{
                    环绕通知:后置通知
               }
            }
        普通通知:LogAfterReturning方法被执行了...
      }catch(Exception e){
        普通通知:LogAfterThrowing方法被执行了...
      }final {
        普通通知:LogAfter方法被执行了...
      }
    
    • 为了使得普通的异常能捕获到环绕异常,则需要在环绕异常内部进行抛出异常
      @Around("myPointCut()")
      public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
          Object result = null;
          //获取参数
          Object[] args = pjp.getArgs();
          //执行方法
          try {
              System.out.println("环绕通知:前置通知");
              result = pjp.proceed(args);
              System.out.println("环绕通知:返回通知");
          } catch (Exception e) {
              System.out.println("环绕通知:异常通知");
              throw new RuntimeException(e);
          } finally {
              System.out.println("环绕通知:后置通知");
          }
          return result;
      }
    
    • 多切面运行顺序
      • 多切面的运行顺序
      • 对于多个切面而言,执行顺序根据字母的大小进行排序
    • AOP使用场景:
      1. AOP做日志保存到数据库
      2. 权限验证
      3. AOP做安全检查
      4. 事务控制

在这里插入图片描述

基于注解的AOP配置步骤

  1. 将目标类和切面类都加入到ioc容器中
  2. 告诉Spring哪个是切面类@Aspect
  3. 在切面类中使用注解来配置切面类中哪些方法何时何地执行
  4. 开启注解的AOP功能

基于配置文件的AOP配置步骤

  <!--1.将目标类与切面类都加入到ioc容器中-->
  <bean id="userServlet" class="pers.lele.servlet.UserServlet"></bean>
  <bean id="logUtils" class="pers.lele.utils.LogUtils"></bean>
  <bean id="timeUtils" class="pers.lele.utils.TimeUtils"></bean>
  <!--2.告诉Spring哪个是切面类-->
  <aop:config>
      <!--切入点-->
      <aop:pointcut id="deleteUserById"
                    expression="execution(public * pers.lele.servlet.UserServlet.deleteUserById(Integer))"/>
      <!--2.告诉Spring哪些是切面类-->
      <aop:aspect ref="logUtils">
          <!--3.告诉Spring通知方法何地运行-->
          <aop:before method="LogBefore" pointcut-ref="deleteUserById"/>
          <aop:after-returning method="LogAfterReturning" pointcut-ref="deleteUserById" returning="result"/>
          <aop:after-throwing method="LogAfterThrowing" pointcut-ref="deleteUserById" throwing="e"/>
          <aop:after method="LogAfter" pointcut-ref="deleteUserById"/>
          <aop:around method="LogAround" pointcut-ref="deleteUserById"/>
      </aop:aspect>
      <aop:aspect ref="timeUtils">
          <aop:before method="LogBefore" pointcut-ref="deleteUserById"/>
          <aop:after-returning method="LogAfterReturning" pointcut-ref="deleteUserById" returning="result"/>
          <aop:after-throwing method="LogAfterThrowing" pointcut-ref="deleteUserById" throwing="e"/>
          <aop:after method="LogAfter" pointcut-ref="deleteUserById"/>
      </aop:aspect>
  </aop:config>

注解与配置文件比较

重要的用配置,不重要的用注解

声明式事务

事务的特征:

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

Spring事务的使用

  1. 配置事务管理器,让其进行事务控制
      <!--1.配置事务管理器,让其进行事务控制-->
         <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
             <!--控制住数据源-->
             <property name="dataSource" ref="dataSource"></property>
         </bean>
  1. 开启基于注解的事务控制模式,依赖tx名称空间 需要aop包
  <!--2.开启基于注解的事务控制模式,依赖tx名称空间 需要aop包-->
  <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
  1. 给事务方法加注解即可
  @Transactional
  public void checkOut(String username, String ISBN) {
      //根据SBN查出价格
      Float price = bookMapper.queryPriceByISBN(ISBN);
      Account account = accountMapper.queryAccountByUsername(username);
      account.setBalance(account.getBalance() - price);
      //修改用户价格
      accountMapper.updateBalance(account);
      //修改库存
      BookStock bookStock = bookStockMapper.queryBookStockByISBN(ISBN);
      bookStock.setStock(bookStock.getStock() - 1);
      bookStockMapper.updateBookStock(bookStock);
  }

ApplicationContext与BeanFactory的区别

ApplicationContext是BeanFactory的子接口,
BeanFactory:bean的工厂接口,负责创建bean实例,保存所有单例的bean其实是一个map 最底层的接口
ApplicationContext是容器接口,更多负责容器功能的实现,留给程序员使用的

事务细节

  • timeout() 超出指定时长后自动终止并回滚,以秒为单位,这里的超时指的是方法的内sql语句在指定的时间内全部执行完才
    超时,如一下情况不会超时

          @Transactional(timeout = 3)
          public void updateBalance(Float price) {
              Account account = new Account("Jerry", price);
              accountMapper.updateBalance(account);
              try {
                  Thread.sleep(3000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      //      accountMapper.updateBalance(account);
      //      int i = 1 / 0;
          }
    
  • readOnly() 设置事务为只读事务,加快查询速度

  • 异常

    • 运行时异常(非检查异常):可以不用处理,默认都回滚
    • 编译时异常(检查异常):可以进行try-catch或抛出,默认不回滚
  • Class<? extends Throwable>[] noRollbackFor() default {};哪些异常事务可以不回滚

        @Transactional(noRollbackFor = {ArithmeticException.class})
        public void updateBalance(Float price) {
            Account account = new Account("Jerry", price);
            accountMapper.updateBalance(account);
            int i = 1 / 0;
        }
    
  • Class<? extends Throwable>[] RollbackFor() default {};哪些异常事务可以回滚

          @Transactional(rollbackFor = FileNotFoundException.class)
          public void updateBalance(Float price) throws FileNotFoundException {
              Account account = new Account("Jerry", price);
              accountMapper.updateBalance(account);
              //对于事务中的编译时异常,不要进行try catch
              new FileInputStream("123");
          }
    
  • 事务的隔离级别 transaction isolation level:数据库系统必须具有隔离并发运行各个事务的能力,使得他们不会相互影响,避免各种并发问题

    • 读未提交 (出现脏读)
    • 读已提交
    • 可重复读
    • 串行化

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

  • 事务的传播行为(事务的传播+事务的行为)
    如果有多个事务进行嵌套,子事务是否要和大事务一起回滚,当子事务不设置传播行为时,默认应该是REQUIRED的
      @Transactional
      public void mulOperator(Account account, Book book) {
          accountService.updateBalance(account);
          bookService.updatePrice(book);
          System.out.println("exe...");
          int i = 1 / 0;
      }
      //与大事务同一车 使用之前的connection
      @Transactional(propagation = Propagation.REQUIRED)
      public void updateBalance(Account account){
          accountMapper.updateBalance(account);
      }  
      //自己开自己的车,不与大事务开一条车 新创建一个connection
      @Transactional( propagation = Propagation.REQUIRES_NEW)
      public void updatePrice(Book book) {
          bookMapper.updatePriceByISBN(book);
      }
    
  • REQUIRED事务属性继承于大事务,而propagation-Propagation.REQUESTS_NEW可以调整
  • 若上面的mulOperator与(updateBalance或updatePrice)在同一个类中,
    当大事务出错时会直接回滚,无论只事务是否是EQUIRES_NEW(本类方法的嵌套调用就只是一个事务)
    在这里插入图片描述

基于xml配置事务

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <tx:advice id="myAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!--name:方法名-->
            <tx:method name="" propagation=""/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <!--配置哪些方法是切面方法-->
        <aop:pointcut id="pointcut" expression=" execution(*.*())"/>
        <aop:advisor advice-ref="myAdvice" pointcut-ref="pointcut"></aop:advisor>
    </aop:config>
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只学弱狗!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值