• Spring总结六:AOP(面向切面编程)


    概述:

      AOP(Aspect-Oriented Programming,面向切面的编程),它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。它是一种新的方法论,它是对传统OOP编程的一种补充。AOP是希望能够将通用需求功能从不相关的类当中分离出来,能够使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只需要修改这个行为即可。

      AOP就是将公用功能提取出来,如果以后公用功能的需求发生变化,只需要改动公用的模块的代码即可,多个调用的地方则不需要改动。所谓面向切面,就是只关注通用功能,而不关注业务逻辑。实现方式一般是通过拦截。一般应用在日志记录,事务管理,权限验证,性能监测(统计方法运行时间),异常拦截等。

      1,Aspect(切面):通常是一个类,里面可以定义切入点和通知
      2,JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用
      3,Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
      4,Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
      5,AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类,原理我们后面有时间再写一篇博客。AOP动态代理的实现

    我们先通过例子来了解一下基于xml配置的AOP:

    简单的实现转账功能,刘德华给张学友转账10000元:

    首先我们把pom.xml中需要包引进去(我是通过父工程管理的jar包版本,所以没有写版本信息):

    <dependencies>
        <!--spring需要的包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
    </dependencies>

    我们的dao和service代码:

    public class AccountDao {
        /**
         * 转入方法
         */
        public void shiftto(){
            System.out.println("张学友的账户转入了 10000...");
        }
    
        /**
         * 转出方法
         */
        public void rollout(){
            System.out.println("刘德华的账户转出了 10000...");
        }
    }
    
    public class AccountService {
    
        private AccountDao accountDao;
    
        /**
         * 基于xml配置 提供一个set方法
         */
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        /**
         * 转账方法
         */
        public String transfer() {
            //转出
            accountDao.rollout();
            //转入
            accountDao.shiftto();
            return "我是AccountService类的transfer方法的返回值";
        }
    }

    然后配置spring配置文件中对应的bean:

        <!--目标类-->
        <bean id="accountDao" class="com.zy.dao.AccountDao"></bean>
        <bean id="accountService" class="com.zy.service.AccountService">
            <property name="accountDao" ref="accountDao"></property>
        </bean>

    如果只有上面这些的话(不使用AOP的情况),我们可以通过测试代码:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:applicationContext.xml")
    public class AccountServiceTest {
        @Value("#{accountService}")
        private AccountService service;
    
        @Test
        public void transfer() throws Exception {
            service.transfer();
        }
    
    }

    得出的结果如下:

    那么如果现在我们要把转账的功能放在事务里的话,正常情况下需要在实现的service中加入事务的代码,但是使用AOP的话我们可以这样做:

    新建自己的Aspect类:

    public class MyAspect {
        /**
         * before 前置通知方法
         */
        public void before(JoinPoint joinPoint) {
            System.out.println("---------------由" + joinPoint.getTarget().getClass().getSimpleName() + "类开启事务");
        }
    
        /**
         * afterReturning 后置通知方法
         */
        public void afterReturning(JoinPoint joinPoint, Object returnVal) {
            System.out.println("---------------提交事务,返回值为:" + returnVal);
        }
    
        /**
         * after 最终通知方法
         */
        public void after(JoinPoint joinPoint) {
            System.out.println("---------------释放资源");
        }
    
        /**
         * afterThrowing 后置异常通知方法
         */
        public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
            System.out.println("---------------事务回滚,程序异常信息:" + ex.getMessage());
        }
    }

    这个时候 我们需要在spring配置文件applicationContext.xml中这样配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           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 id="accountDao" class="com.zy.dao.AccountDao"></bean>
        <bean id="accountService" class="com.zy.service.AccountService">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
        <!--切面类:里面可以有多个增强目标类的方法-->
        <bean id="myAspect" class="com.zy.aspect.MyAspect"></bean>
    
        <!--配置目标类和切面类的关系-->
        <aop:config proxy-target-class="false">
            <!--配置切面:指定增强方法的位置-->
            <aop:aspect ref="myAspect">
                <!--配置切入点-->
                <aop:pointcut id="myPointcut" expression="bean(*Service)"></aop:pointcut>
                <!--配置切入点和增强方法的联系-->
                <!--before 前置通知-->
                <aop:before method="before" pointcut-ref="myPointcut"></aop:before>
                <!--afterReturning 后置通知 可能有返回值 returnVal要和方法的形参名称一致-->
                <aop:after-returning method="afterReturning" pointcut-ref="myPointcut"
                                     returning="returnVal"></aop:after-returning>
                <!--after 最终通知-->
                <aop:after method="after" pointcut-ref="myPointcut"></aop:after>
                <!--后置异常通知-->
                <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="ex"></aop:after-throwing>
            </aop:aspect>
        </aop:config>
    </beans>

    上面的配置的切入点语法需要解释一下:

    Spring 只支持AspectJ的部分切入点语法 
    
    4.3.1 语法一: execution(修饰符? 返回值 方法名(参数) 异常?)
    execution(* *(..))  匹配所有spring管理对象所有方法, 第一个*任意返回值 ,第二个*任意方法, .. 任意参数 
    execution(* com.zy.service.*..*(..)) 匹配com.zy.service包中所有对象所有方法
    execution(* com.zy.service.AccountService.s*(..)) 匹配com.zy.service.AccountService中s开头方法 
    
    4.3.2 语法二:bean(beanName) 匹配目标Bean所有方法
    bean(*Service) 匹配所有以Service结尾BeanName 的对象 
     
    4.3.3 语法三:within(包.*) 匹配包下所有类的所有方法
    within(com.zy.service..*) 匹配spring包及其子包中类所有方法 
     
    注意: 一个.代表子目录; 两个点.. 表示后代目录 

    最后再运行我们的测试代码会得出以下结果:

    这样我们就把事务加进去了,这样做最大的好处就是可以随时把事务去掉或者是修改其实现代码。

    但是如果感觉这样在xml文件中配置比较麻烦的话,这里讲述一个稍微简单的方式,就是我们文章开始标红的通知类型 around(环绕通知):

    修改MyAspect,加入around方法:

        /**
         * around 环绕通知
         */
        public void around(ProceedingJoinPoint joinPoint) throws Throwable {
            Object returnVal = null;
            try {
                //执行before
                System.out.println("---------------由" + joinPoint.getTarget().getClass().getSimpleName() + "类开启事务");
                //执行具体业务
                returnVal = joinPoint.proceed();
                //执行afterReturning
                System.out.println("---------------提交事务,返回值为:" + returnVal);
            } catch (Exception ex) {
                //执行afterThrowing
                System.out.println("---------------事务回滚,程序异常信息:" + ex.getMessage());
    
            } finally {
                //执行after
                System.out.println("---------------释放资源");
            }
        }

    修改xml配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           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 id="accountDao" class="com.zy.dao.AccountDao"></bean>
        <bean id="accountService" class="com.zy.service.AccountService">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
        <!--切面类:里面可以有多个增强目标类的方法-->
        <bean id="myAspect" class="com.zy.aspect.MyAspect"></bean>
    
        <!--配置目标类和切面类的关系-->
        <aop:config proxy-target-class="false">
            <!--配置切面:指定增强方法的位置-->
            <aop:aspect ref="myAspect">
                <!--配置切入点-->
                <aop:pointcut id="myPointcut" expression="bean(*Service)"></aop:pointcut>
                <!--环绕通知-->
                <aop:around method="around" pointcut-ref="myPointcut"></aop:around>
            </aop:aspect>
        </aop:config>
    </beans>

    可以看出 一行around通知的配置代替了之前的 before afterReturning afterThrowing after,但是效果是一样的。

    下面我们来说一下基于注解的方式:

    首先我们的spring配置文件改写成如下这样:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd
    ">
        <!--context 开启注解扫描-->
        <context:component-scan base-package="com.zy"></context:component-scan>
        <!--aop 开启自动代理-->
        <aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
    </beans>

    给我们的dao和service添加注解:

    @Repository("accountDao")
    public class AccountDao {
        /**
         * 转入方法
         */
        public void shiftto(){
            System.out.println("张学友的账户转入了 10000...");
        }
    
        /**
         * 转出方法
         */
        public void rollout(){
            System.out.println("刘德华的账户转出了 10000...");
        }
    }
    
    @Service("accountService")
    public class AccountService {
        @Value("#{accountDao}")
        private AccountDao accountDao;
    
        /**
         * 转账方法
         */
        public String transfer() {
            //转出
            accountDao.rollout();
            //转入
            accountDao.shiftto();
            return "我是AccountService类的transfer方法的返回值";
        }
    }

    给我们的MyAspect类添加注解:

    @Component("myAspect")
    @Aspect //通知这个类是个切面
    public class MyAspect {
    
        /**
         * 提出来公用的切入点
         */
        @Pointcut("bean(*Service)")
        private void myPointcut() {
        }
    
        /**
         * before 前置通知方法
         */
        @Before("myPointcut()")
        public void before(JoinPoint joinPoint) {
            System.out.println("---------------由" + joinPoint.getTarget().getClass().getSimpleName() + "类开启事务");
        }
    
        /**
         * afterReturning 后置通知方法
         */
        @AfterReturning(pointcut = "myPointcut()", returning = "returnVal")
        public void afterReturning(JoinPoint joinPoint, Object returnVal) {
            System.out.println("---------------提交事务,返回值为:" + returnVal);
        }
    
        /**
         * after 最终通知方法
         */
        @After("myPointcut()")
        public void after(JoinPoint joinPoint) {
            System.out.println("---------------释放资源");
        }
    
        /**
         * afterThrowing 后置异常通知方法
         */
        @AfterThrowing(pointcut = "myPointcut()", throwing = "ex")
        public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
            System.out.println("---------------事务回滚,程序异常信息:" + ex.getMessage());
        }
    }

    运行测试代码得到如下结果:

    到此 完全ojbk。

  • 相关阅读:
    asp.net core 参数使用
    dotnetcore EF 表达式
    dotnetcore EF 查询筛选
    flowable流程中心设计之子流程(九)
    Spring Boot源码阅读如何加载环境变量(二)
    flowable流程中心设计之网关(八)
    idea阅读源码无download source解决方式
    Spring Boot源码阅读如何启动内置web容器(三)
    InheritableThreadLocal源码阅读
    Spring Boot源码阅读启动主流程(一)
  • 原文地址:https://www.cnblogs.com/blazeZzz/p/9310859.html
Copyright © 2020-2023  润新知