zoukankan      html  css  js  c++  java
  • Spring系列之AOP实现的两种方式

    Spring只支持XML方式而没有实现注解的方式(也叫AspectJ方式)的AOP,所以要使用@Aspect注解,只能引入AspectJ相关的 jar 包:

    aopalliance-1.0.jar 和 aspectjweaver.jar

    Spring的配置文件 applicationContext.xml 中引入context、aop对应的命名空间;

    配置自动扫描的包,同时使切面类中相关方法中的注解生效,需自动地为匹配到的方法所在的类生成代理对象。

    复制代码
    <?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-4.0.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
        <!-- 配置自动扫描的包 -->
        <context:component-scan base-package="com.qcc.beans.aop"></context:component-scan>
        <!-- 自动为切面方法中匹配的方法所在的类生成代理对象。 -->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    </beans>
    复制代码

    AOP常用的实现方式有两种,

    1、采用声明的方式来实现(基于XML),

    2、是采用注解的方式来实现(基于AspectJ)。

    AOP中一些比较重要的概念

    Joinpoint(连接点):程序执行时的某个特定的点,在Spring中就是某一个方法的执行 。

    Pointcut(切点):说的通俗点,spring中AOP的切点就是指一些方法的集合,而这些方法是需要被增强、被代理的。一般都是按照一定的约定规则来表示的,如正则表达式等。切点是由一类连接点组成。 

    Advice(通知):还是说的通俗点,就是在指定切点上要干些什么。 

    Advisor(通知器):其实就是切点和通知的结合 。

    注意:

    1、环绕方法通知,环绕方法通知要注意必须给出调用之后的返回值,否则被代理的方法会停止调用并返回null,除非你真的打算这么做。           

    2、只有环绕通知才可以使用JoinPoint的子类ProceedingJoinPoint,各连接点类型可以调用代理的方法,并获取、改变返回值。

    1、<aop:pointcut>如果位于<aop:aspect>元素中,则命名切点只能被当前<aop:aspect>内定义的元素访问到。为了能被整个<aop:config>元素中定义的所有增强访问,则必须在<aop:config>下定义切点。

    2、如果在<aop:config>元素下直接定义<aop:pointcut>,必须保证<aop:pointcut>在<aop:aspect>之前定义。<aop:config>下还可以定义<aop:advisor>,三者在<aop:config>中的配置有先后顺序的要求:首先必须是<aop:pointcut>,然后是<aop:advisor>,最后是<aop:aspect>。而在<aop:aspect>中定义的<aop:pointcut>则没有先后顺序的要求,可以在任何位置定义。<aop:pointcut>:用来定义切入点,该切入点可以重用;<aop:advisor>:用来定义只有一个通知和一个切入点的切面;<aop:aspect>:用来定义切面,该切面可以包含多个切入点和通知,而且标签内部的通知和切入点定义是无序的;和advisor的区别就在此,advisor只包含一个通知和一个切入点。

    3、在使用spring框架配置AOP的时候,不管是通过XML配置文件还是注解的方式都需要定义pointcut"切入点"。

    例如定义切入点表达式 execution(* com.sample.service.impl..*.*(..))

    execution()是最常用的切点函数,其语法如下所示:

    整个表达式可以分为五个部分:

    (1)、execution(): 表达式主体。

    (2)、第一个*号:表示返回类型,*号表示所有的类型。

    (3)、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。

    (4)、第二个*号:表示类名,*号表示所有的类。

    (5)、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

    一、基于XML配置的Spring AOP

    采用声明的方式实现(在XML文件中配置),大致步骤为:配置文件中配置pointcut, 在java中用编写实际的aspect 类, 针对对切入点进行相关的业务处理。

    业务接口:

    复制代码
    package com.spring.service;
    public interface IUserManagerService {
        //查找用户
        public String findUser();
        //添加用户
        public void addUser();
    }
    复制代码

    业务实现:

    复制代码
    package com.spring.service.impl;
    import com.spring.service.IUserManagerService;
    public class UserManagerServiceImpl implements IUserManagerService{
    private String name;
        public void setName(String name){
            this.name=name;
        }
        public String getName(){
            return this.name;
        }
        public String findUser(){
            System.out.println("============执行业务方法findUser,查找的用户是:"+name+"=============");
            return name;
        }
        public void addUser(){
            System.out.println("============执行业务方法addUser=============");
            //throw new RuntimeException();
        }
    }
    复制代码

    切面类:

    复制代码
    package com.spring.aop;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    public class AopAspect {
        /**
         * 前置通知:目标方法调用之前执行的代码
          * @param jp
         */
        public void doBefore(JoinPoint jp){
            System.out.println("===========执行前置通知============");
        }
        /**
         * 后置返回通知:目标方法正常结束后执行的代码
          * 返回通知是可以访问到目标方法的返回值的
          * @param jp
         * @param result
         */
        public void doAfterReturning(JoinPoint jp,String result){
            System.out.println("===========执行后置通知============");
            System.out.println("返回值result==================="+result);
        }
        /**
         * 最终通知:目标方法调用之后执行的代码(无论目标方法是否出现异常均执行)
          * 因为方法可能会出现异常,所以不能返回方法的返回值
          * @param jp
         */
        public void doAfter(JoinPoint jp){
            System.out.println("===========执行最终通知============");
        }
        /**
         * 
         * 异常通知:目标方法抛出异常时执行的代码
         * 可以访问到异常对象
         * @param jp
         * @param ex
         */
        public void doAfterThrowing(JoinPoint jp,Exception ex){
            System.out.println("===========执行异常通知============");
        }
    
        /**
          * 环绕通知:目标方法调用前后执行的代码,可以在方法调用前后完成自定义的行为。
          * 包围一个连接点(join point)的通知。它会在切入点方法执行前执行同时方法结束也会执行对应的部分。
          * 主要是调用proceed()方法来执行切入点方法,来作为环绕通知前后方法的分水岭。
          * 
          * 环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法。
          * 而且环绕通知必须有返回值,返回值即为目标方法的返回值
          * @param pjp
          * @return
          * @throws Throwable
         */
        public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
            System.out.println("======执行环绕通知开始=========");
             // 调用方法的参数
            Object[] args = pjp.getArgs();
            // 调用的方法名
            String method = pjp.getSignature().getName();
            // 获取目标对象
            Object target = pjp.getTarget();
            // 执行完方法的返回值
            // 调用proceed()方法,就会触发切入点方法执行
            Object result=pjp.proceed();
            System.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result);
            System.out.println("======执行环绕通知结束=========");
            return result;
        }
    }
    复制代码

    Spring配置:

    复制代码
    <?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:p="http://www.springframework.org/schema/p"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
        <!-- 声明一个业务类 -->
        <bean id="userManager" class="com.spring.service.impl.UserManagerServiceImpl">
            <property name="name" value="lixiaoxi"></property>
        </bean>  
        <!-- 声明通知类 -->
        <bean id="aspectBean" class="com.spring.aop.AopAspect" />
        <aop:config>
         <aop:aspect ref="aspectBean">
            <aop:pointcut id="pointcut" expression="execution(* com.spring.service.impl.UserManagerServiceImpl..*(..))"/>
            <aop:before method="doBefore" pointcut-ref="pointcut"/> 
            <aop:after-returning method="doAfterReturning" pointcut-ref="pointcut" returning="result"/>
            <aop:after method="doAfter" pointcut-ref="pointcut" /> 
            <aop:around method="doAround" pointcut-ref="pointcut"/> 
            <aop:after-throwing method="doAfterThrowing" pointcut-ref="pointcut" throwing="ex"/>
          </aop:aspect>
       </aop:config>
    </beans>
    复制代码

    测试类:

    复制代码
    package com.spring.test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import com.spring.service.IUserManagerService;
    public class TestAop {
        public static void main(String[] args) throws Exception{
            ApplicationContext act =  new ClassPathXmlApplicationContext("applicationContext3.xml");
            IUserManagerService userManager = (IUserManagerService)act.getBean("userManager");
            userManager.findUser();
            System.out.println("
    ");
            userManager.addUser();
        }
    }
    复制代码

    二、使用注解配置AOP

    采用注解来做aop, 主要是将写在spring 配置文件中的连接点写到注解里面。

    业务接口和业务实现与上边一样,具体切面类如下:

    复制代码
    package com.spring.aop;
    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;
    
    @Aspect
    public class AopAspectJ {
        /**  
         * 必须为final String类型的,注解里要使用的变量只能是静态常量类型的  
         */  
        public static final String EDP="execution(* com.spring.service.impl.UserManagerServiceImpl..*(..))";
        /**
         * 切面的前置方法 即方法执行前拦截到的方法
          * 在目标方法执行之前的通知
          * @param jp
         */
        @Before(EDP)
        public void doBefore(JoinPoint jp){
            System.out.println("=========执行前置通知==========");
        }
        /**
         * 在方法正常执行通过之后执行的通知叫做返回通知
         * 可以返回到方法的返回值 在注解后加入returning 
         * @param jp
         * @param result
         */
        @AfterReturning(value=EDP,returning="result")
        public void doAfterReturning(JoinPoint jp,String result){
            System.out.println("===========执行后置通知============");
        }
        /**
         * 最终通知:目标方法调用之后执行的通知(无论目标方法是否出现异常均执行)
         * @param jp
         */
        @After(value=EDP)
        public void doAfter(JoinPoint jp){
            System.out.println("===========执行最终通知============");
        }
        /**
         * 环绕通知:目标方法调用前后执行的通知,可以在方法调用前后完成自定义的行为。
         * @param pjp
         * @return
         * @throws Throwable
         */
        @Around(EDP)
        public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
            System.out.println("======执行环绕通知开始=========");
            // 调用方法的参数
            Object[] args = pjp.getArgs();
            // 调用的方法名
            String method = pjp.getSignature().getName();
            // 获取目标对象
            Object target = pjp.getTarget();
            // 执行完方法的返回值
            // 调用proceed()方法,就会触发切入点方法执行
            Object result=pjp.proceed();
            System.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result);
            System.out.println("======执行环绕通知结束=========");
            return result;
        }
        /**
         * 在目标方法非正常执行完成, 抛出异常的时候会走此方法
         * @param jp
         * @param ex
         */
        @AfterThrowing(value=EDP,throwing="ex")
        public void doAfterThrowing(JoinPoint jp,Exception ex) {
            System.out.println("===========执行异常通知============");
        }
    }

    spring的配置:
    <?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:p="http://www.springframework.org/schema/p"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
        <!-- 声明spring对@AspectJ的支持 -->
        <aop:aspectj-autoproxy/>    
        <!-- 声明一个业务类 -->
        <bean id="userManager" class="com.spring.service.impl.UserManagerServiceImpl">
            <property name="name" value="lixiaoxi"></property>
        </bean>   
         <!-- 声明通知类 -->
        <bean id="aspectBean" class="com.spring.aop.AopAspectJ" />
    </beans>
    
    测试类:
    
    package com.spring.test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import com.spring.service.IUserManagerService;
    public class TestAop1 {
        public static void main(String[] args) throws Exception{
            ApplicationContext act =  new ClassPathXmlApplicationContext("applicationContext4.xml");
             IUserManagerService userManager = (IUserManagerService)act.getBean("userManager");
             userManager.findUser();
             System.out.println("
    ");
             userManager.addUser();
        }
    }
    复制代码
    --------------------迷人的分割线分割线--------------------
    作者:架构师小跟班
    出处:http://www.cnblogs.com/xyhero/
    个人网站: http://www.jiagou1216.com
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    如果文中有什么错误,欢迎指出,以免更多的人被误导。
  • 相关阅读:
    opentsdb安装部署
    python发送邮件(html)例子
    python查库写库例子
    获取rds的cpu和内存使用情况
    数据库损坏的情况下如何获取到dbid
    grafana安装升级部署
    Specified key was too long
    mysql动态执行sql批量删除数据
    kafka删除topics
    cratedb导入json文件
  • 原文地址:https://www.cnblogs.com/it-deepinmind/p/13258912.html
Copyright © 2011-2022 走看看