前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >解释一下Spring框架AOP(面向切面编程)

解释一下Spring框架AOP(面向切面编程)

作者头像
Java编程指南
发布2019-08-02 11:44:21
5290
发布2019-08-02 11:44:21
举报
文章被收录于专栏:Java编程指南Java编程指南

文章开头我先把上一期spring事物这篇文章(spring事物管理)最后的问题解答一下:

上面转账的两步操作中间发生了异常,但是第一步依然在数据库中进行了增加操作。实际应用中不会允许这样的情况发生,所以我们这里用事务来进行管理。

  Dao 层不变,我们在 Service 层注入 TransactionTemplate 模板,因为是用模板来管理事务,所以模板需要注入事务管理器 DataSourceTransactionManager 。而事务管理器说到底还是用底层的JDBC在管理,所以我们需要在事务管理器中注入 DataSource。这几个步骤分别如下:

 AccountServiceImpl 接口:

代码语言:javascript
复制
package com.ys.service.impl;
 
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
 
import com.ys.dao.AccountDao;
import com.ys.service.AccountService;
 
public class AccountServiceImpl implements AccountService{
 
    private AccountDao accountDao;
    private TransactionTemplate transactionTemplate;
     
    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
    @Override
    public void transfer(final String outer,final String inner,final int money) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus arg0) {
                accountDao.out(outer, money);
                //int i = 1/0;
                accountDao.in(inner, money);
            }
        });
    }
 
}

  Spring 全局配置文件 applicationContext.xml:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">   
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
     
    <bean id="accountDao" class="com.ys.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
     
    <bean id="accountService" class="com.ys.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
        <property name="transactionTemplate" ref="transactionTemplate"></property>
    </bean>
     
    <!-- 创建模板 -->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="txManager"></property>
    </bean>
     
    <!-- 配置事务管理器 ,管理器需要事务,事务从Connection获得,连接从连接池DataSource获得 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

  测试文件保持不变,可以分两次测试,第一次两次操作没有发生异常,然后数据库正常改变了。第二次操作中间发生了异常,发现数据库内容没变。

  如果大家有兴趣也可以试试底层的PlatformTransactionManager来进行事务管理,我这里给出主要代码:

代码语言:javascript
复制
//定义一个某个框架平台的TransactionManager,如JDBC、Hibernate
        DataSourceTransactionManager dataSourceTransactionManager =
                new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 设置数据源
     
        DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定义事务属性
        transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 设置传播行为属性
        TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 获得事务状态
        try {
            // 数据库操作
            accountDao.out(outer, money);
            int i = 1/0;
            accountDao.in(inner, money);
             
            dataSourceTransactionManager.commit(status);// 提交
        } catch (Exception e) {
            dataSourceTransactionManager.rollback(status);// 回滚
        }

AOP概念理解:

其实是OOP(Object-Oriented Programing) 思想的补充和完善。我们知道,OOP引进"抽象"、"封装"、"继承"、"多态"等概念,对万事万物进行抽象和封装,来建立一种对象的层次结构,它强调了

一种完整事物的自上而下的关系。但是具体细粒度到每个事物内部的情况,OOP就显得无能为力了。比如日志功能。日志代码往往水平地散布在所有对象层次当 中,却与它所散布到的对象的核心功能毫无关系。对于其他很多类似功能,如事务管理、权限控制等也是如此。这导致了大量代码的重复,而不利于各个模块的重 用。 而AOP技 术则恰恰相反,它利用一种称为"横切"的技术,能够剖解开封装的对象内部,并将那些影响了多个类并且与具体业务无关的公共行为 封装成一个独立的模块(称 为切面)。更重要的是,它又能以巧夺天功的妙手将这些剖开的切面复原,不留痕迹的融入核心业务逻辑中。这样,对于日后横切功能的编辑和重用都能够带来极大 的方便。 AOP技术的具体实现,无非也就是通过动态代理技术或者是在程序编译期间进行静态的"织入"方式。下面是这方面技术的几个基本术语:

1、join point(连接点):是程序执行中的一个精确执行点,例如类中的一个方法。它是一个抽象的概念,在实现AOP时,并不需要去定义一个join point。

2、point cut(切入点):本质上是一个捕获连接点的结构。在AOP中,可以定义一个point cut,来捕获相关方法的调用。

3、advice(通知):是point cut的执行代码,是执行“方面”的具体逻辑。

4、aspect(切面):point cut和advice结合起来就是aspect,它类似于OOP中定义的一个类,但它代表的更多是对象间横向的关系。

说了这么多,可能我们还是对AOP有点不知所措,不知道是干什么的,那么我们就以一个例子作为讲解,来理解这个抽象的概念

我们有一个简易的计算器,进行加减乘除的操作,有一个需求,1.需要在进行算法之前和之后进行输出一句话

那么对于以上操作我们可能最容易想到的就是用一个实现类实现这个接口。然后在接口调用方法前后输出一句话

这样确实能实现这个需求,但是可能有的同学就想到了,是不是重复代码了呢?如果我有上千个方法呢?是不是还得在每个方法里增加几行代码?我们能不能在不改变原来方法的结构上

也能实现相同的需求呢?这个时候AOP就能帮我们实现了。下面我们详细的讲解下如何使用注解的方式来实现AOP

还是同样的接口和实现类,只是这时候实现类中没有了输出语句,如图

上图就是最原始的方法了,也就是说我们在这个方法里面只需要关注我们方法执行的内容,并不需要关注一些方法之外的东西,比如说记录日志,方法前输出语句等等。。

那么,既然这个方法什么都不关注的话,那我们的输出语句又在哪儿写呢?这个时候我们就定义一个专门的类,用它来作为切面,代码如下所示

代码语言:javascript
复制
package advice;

import java.util.Arrays;
import java.util.List;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;


@Aspect    //声明注解
public class CalculationAnnotation {
    
    /**
     * 定义前置通知
     * execution(* biz.UserBiz.*(..)) 表示  所有修饰符的所有返回值类型  biz.UserBiz 包下的所有方法
     * 在方法执行之前执行
     * */
    @Before("execution(* biz.CalculationImpl.*(..))")
    public void before(JoinPoint join){
        //获取方法名
        String mathName=join.getSignature().getName();
        //获取参数列表
        List<Object> args = Arrays.asList(join.getArgs());
        
        System.out.println("前置通知---->before   方法名是:"+mathName+"\t参数列表是:"+args);
    }
    
    /**
     * 后置通知
     * 在方法返回后执行,无论是否发生异常
     * 不能访问到返回值
     * 
     * */
    @After("execution(* biz.CalculationImpl.*(..))")
    public void after(){
        System.out.println("后置通知---->after....");
    }
}

@Aspect ----->表示声明这个类是一个切面

这样呢,咱们这个切面就声明完毕了,那么,我们可以想到,这个时候我们只是声明了一个切面而已,并没有在那个地方用到这个切面对不对?也就是说我们配置的切面还跟我们程序还没有任何的关联关系

这样的话呢,就引出了我们的配置文件了也就是我们Spring的配置文件applicationContext.xml,配置如下

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<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"
   
    xmlns:context="http://www.springframework.org/schema/context"
    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-3.0.xsd
        ">
        
     
     
      <!-- 配置Bean -->
     <bean id ="CalculationImpl" class="biz.CalculationImpl"></bean>
     
     
     <!-- 将切面类交与Spring容器管理 -->
     <bean class="advice.CalculationAnnotation"></bean>


      <!-- 使用注解自动生成代理对象 -->
      <aop:aspectj-autoproxy/>
       
       
</beans>

这个时候我们可以看看配置文件里到底写了什么,写这些是干啥的,有什么用。

这个大家肯定都懂是吧,这没话说,也就是将CalculationImpl类交给Spring容器来管理,如果有不懂的童鞋可以看看我的另一篇关于Spring IOC 的文章

那么这行代码呢?这行代码的意思就是将我们的CalculationAnnotation类交给Spring容器管理,因为我们在CalculationAnnotation类中不是声明了一个@Aspect切面注解吗对不对

当Spring容器初始化的时候它会找有没有

这个节点,如果有的话呢,容器就会根据你的Bean配置,找看那个类中配置了@Aspect切面注解

如果找到了的话那么就根据你的注解来执行相应的代码,什么意思呢?比如说如图所示

好,那么我们就来看看执行之后结果会是怎样的呢?

这样我们是不是就完成了之前的需求呢?在执行代码前输出一行语句,如果我们想要做到日志的记录的话,是不是只需要把输出语句修改为记录日志的代码就可以了呢。而且我还没有影响任何的功能性代码

也就是对源代码并没有做任何的修改,那么既然有前置增强的话肯定也有后置增强和其他增强操作下面我就讲讲后置增强,

其实对于其他的增强类型的话呢,既然知道前置增强是怎么一回事了,那么其他四种就轻而易举了

后置增强,其实我们只需要在切面类也就是我们写前置增强的类中直接添加后置 增强代码即可,如图

只是将注解标签给进行了一道修改,其他的任何操作我们都不需要在进行修改,示例结果如图所示

这样是不是就完成了在方法前后执行与方法无关的代码呢?可能有些童鞋有疑问,为什么输出语句是在最后输出的,不应该是夹在中间吗?但是我们看测试代码,我是执行了add方法,接收了一个返回值,然后在方法的外面输出的我接收的返回值变量,那么这样的话,可不就是我们看到的结果嘛

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-03-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java编程指南 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档