zoukankan      html  css  js  c++  java
  • Spring中的AOP

     一、AOP简介

    1.什么是AOP?

    (1)AOP的全称是Aspect-Oriented Programming,即面向切面编程,是OOP思想的一种补充,目前已经是一种比较成熟的编程方式。传统的OOP可以通过组合或者继承的方式来达到代码的重用,但是如果要实现某个功能,同样的代码会分散到各个方法中去,这样想关闭某个功能,或者想对其进行修改,就必须修改所有的相关的方法。

    (2)AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或者运行的时候,再将这些提取出来的代码应用到需要执行的地方。

    目前最流行的AOP框架有两个,分别是Spring AOP和AspectJ。Spring AOP使用纯java实现,不需要专门的编译过程和类加载器,在运行期间通过代理的方式向目标织入增强代码,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译的时候提供了横向代码的织入。

    2.AOP术语

    (1)Aspect(切面):在实际应用过程中,切面是指封装的用于横向插入下同功能(如事务、日志等)的类,如上图中的Aspect,该类要被Spring容器识别为切面,需要在配置文件中通过配置文件进行指定。

    (2)Jointpount(连接点):

     在程序的执行过程中的某个点,实际上是对象的一个操作,例如方法的调用或者异常的抛出,而在Spring AOP中,连接点就是指方法的调用。

    (3)PointCut(切入点):即切面与程序流程的交叉点,即那些需要处理的连接点,如下图所示,切入点是指类或者方法名,如某个通知需要用到以add开头的方法,那么所有满足这一规则的都是切入点。

    (4)Advice(通知/增强处理):AOP在特定的切入点执行增强处理,即定义好的切入点处所要执行的代码,可以将其理解为切面类中的方法,它是切面的具体实现。

    (5)Target Object(目标对象):指所有被通知的对象,也叫做被增强的对象。如果AOP框架采用的是动态AOP实现,那么该对象就是一个被代理的对象。

    (6)Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。

    (7)Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。

     二、动态代理

    0.AOP中的代理是由AOP框架动态生成的一个对象,该对象可以作为目标对象使用,Spring中AOP代理可以是JDK动态代理,也可以是CGLIB代理。

    1.JDK动态代理。

    (1)JDK动态代理是通过java.lang.reflect.Proxy类来实现的,可以通过Proxy类的neProxyInstance()方法创建代理对象,对于使用业务接口的类,Spring会默认使用JDK动态代理实现AOP。

    (2)动态代理的实现代码:

    //UserDao类
    public interface UserDao {
        /**
         *
         */
        public void addUser();
        public void deleteUser();
    }
    
    
    //UserDao实现类UserDaoImpl
    //
    /** * 目标类,本案例中会将类UserDaoImpl做为一个目标类,对其方法进行增强处理。 */ public class UserDaoImpl implements UserDao { public void addUser() { System.out.println("添加用户"); } public void deleteUser() { System.out.println("删除用户"); } }


    /**
     * 切面类,可以存在多个通知Advice(即增强的方法)
      在该类中模拟一个权限检查方法和模拟一个日志记录的方法,这两个方法就表示切面中的通知
    */ public class MyAscpect { //下面这两个方法表示切面中的通知 public void check_Permissions(){ System.out.println("模拟检查权限。。。。。"); } public void log(){ System.out.println("模拟记录日志"); } }import com.cisco.aspect.MyAscpect;import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 代理类
     * 该类需要实现InvocationHandler接口,并编写代理方法,并实现了接口中的invoke()方法,所有动态代理所调用的方法都交由该方法处理,
     * 在创建代理方法createProxy()中,使用Proxy类的newProxyInstance()方法来创建代理对象。
      newProxyInstance()方法包含3个参数,其中第一个参数表示当前类的加载器,第二个参数表示被代理对象实现的所有接口,
      第三个参数this表示代理类jdkProxy本身,在invoke()方法中,目标方法执行的前后,
    会分别执行切面类的check_Permission()方法和log()方法
    Proxy.newProxyInstance(classLoader,clazz,this);
    */
    public class JdkProxy implements InvocationHandler {
        //1.声明目标类接口
        private UserDao userDao;
        //2.创建代理方法
        public Object createProxy(UserDao userDao){
            this.userDao=userDao;
            //3.类加载器
            ClassLoader classLoader=JdkProxy.class.getClassLoader();
            //4.被代理对象实现的所有接口
            Class[] clazz = userDao.getClass().getInterfaces();
            //5.使用代理类进行增强,返回的是代理后的对象
            return Proxy.newProxyInstance(classLoader,clazz,this);
        }
    
        /**
         * 所有动态代理类的方法的调用,都会交由Invoke()方法进行处理,
         * @param proxy 被代理后的对象
         * @param method 将要被执行的方法信息(反射)
         * @param args 执行方法时候所需要的参数
         * @return
         * @throws Throwable
         */
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //6声明切面
            MyAscpect myAscpect = new MyAscpect();
            //7.前增强
            myAscpect.check_Permissions();
            //8.在目标类上调用方法,并传入参数
            Object obj=method.invoke(userDao,args);
            //9.后增强
            myAscpect.log();
            return obj;
        }
    }

    //最后是测试类

    package com.cisco.jdk;
    
    public class JdkTest {
        public static void main(String[] args){
            //1.创建代理对象
            JdkProxy jdkProxy = new JdkProxy();
            //2.创建目标对象
            UserDao userDao = new UserDaoImpl();
            //3.从代理对象中获取增强后的目标对象
            UserDao userDao1 = (UserDao) jdkProxy.createProxy(userDao);
            //4.执行方法
            userDao1.addUser();
            userDao1.deleteUser();
        }
    }

     三、基于代理类的AOP实现

    1.Spring的通知类型,Spring中的通知按照在目标中类方法的连接点位置,可以分为以下5种类型

    (1)环绕通知(org.aopalliance.intercept.MethodInterceptor):在目标方法执行前后实施增强,可以用于日志和事务管理等功能。

    (2)前置通知(org.springframework.aop.MethodBeforeAdvice):在目标方法执行前实施增强,可以用于权限管理等功能。

    (3)后置通知(org.springframework.aop.AfterAdvice):在目标方法执行之后实施增强,可用于关闭流、上传文件、删除临时文件等功能。

    (4)异常通知(org.springframework.aop.ThrosAdvice):在方法抛出异常后实施增强,可以用以处理异常激烈日志能等功能。

    (5)引介通知(org.springframework.aop.IntroductionInterceptor):在目标类中添加一些新的方法和属性,可用于修改老版本程序。(增强类)

    2.ProxyFactoryBean

    (1)ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean负责实例化一个Bean,而ProxyFactoryBean负责为其他Bean创建代理实例。在Spring中,使用ProxyFactoryBean时创建AOP代理的基本方式。

    (2)ProxyFactoryBean的常用可配置属性:

    target:代理的目标对象;ProxyInterface:代理实现的接口,如果是多个接口,可使用下面的各式<list><value></value></list>

    (3)ProxyFactoryBean典型的环绕通知案例:

    首先创建切面类MyAspect类,由于实现环绕通知需要实现MethodInterceptor接口,所以MyAspect类需要试下该接口,并试下接口中的invoke方法

    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    
    /**
     * 环绕通知需要实现MethodInterceptor接口
     * 这里为了演示效果,在目标方法前后分别执行了检查权限和记录日志的方法,这两个方法也就是增强的方法,也就是通知。
     */
    public class MyAspect implements MethodInterceptor {
    
        public Object invoke(MethodInvocation mi) throws Throwable {
            check_Permissons();
            //执行目标方法
            Object obj = mi.proceed();
            log();
            return obj;
        }
        public void check_Permissons(){
            System.out.println("模拟检查权限");
        }
        public void log(){
            System.out.println("模拟日志记录权限");
        }
    
    
    }

    其次为了演示效果,在目标方法前后分别执行了检查权限和记录日子和方法,这两个方法也就是增强的方法,也就是通知。创建配置文件applicationContext.xml,并指定代理对象。首先通过<bean>元素定义了目标类和切面,然后使用ProxyFactoryBean定义了代理的目标对象,在定义的代理对象中,分别通过<property>子元素指定了代理的实现接口,代理的目标对象、需要织入的目标类的通知以及代理的方式。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
        <!--1.目标类-->
        <bean id="userDao" class="com.cisco.jdk.UserDaoImpl"></bean>
    
        <!--2.切面类-->
        <bean id="myAspect" class="com.cisco.factoryBean.MyAspect"></bean>
    
        <!--3.在Spring代理工厂定义一个名称为userDaoProxy的代理对象-->
        <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
            <!--3.1指定代理实现的接口-->
            <property name="proxyInterfaces" value="com.cisco.jdk.UserDao"/>
            <!--3.2指定目标对象-->
            <property name="target" ref="userDao"/>
            <!--3.3指定切面,植入环绕通知-->
            <property name="interceptorNames" value="myAspect"/>
            <!--3.4指定代理方式,true:使用cglib,false:默认使用jdk动态代理-->
            <property name="proxyTargetClass" value="true"/>
        </bean>
    </beans>

     四、AspectJ开发

    1.AspectJ是一个基于Java语言的AOP框架,Spring AOP引入了对ASpectJ的支持,并允许使用ASpectJ进行编程。AspectJ试下AOP有两种方式:一种是基于XML的声明式AspectJ,另一种是基于注解的AspectJ。

    2.基于XML的声明式AspectJ。

    就XML的声明式AspectJ是指通过XML文件来定义切面、切入点以及通知。所有的切面、切入点和通知都必须定义在<aop:config>元素内,<aop:config>元素以及子元素如下图所示:

    对于Spring配置文件中的<beans>元素下可以包含多个<aop:config>元素,一个<aop:config>元素又可以包含属性和子元素,其子元素包括<aop:pointcut>  <aop:advisor>和<aop:aspect>。在配置的时候,这3个元素必须按照这个顺序来进行定义,在<aop:aspect>元素下,同样包含了属性和多个子元素,通过使用<aop:aspect>元素及其子元素就可以在XML配置文件中配置切面,切入点和通知,相应的配置如下:

    <?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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            https://www.springframework.org/schema/aop/spring-aop.xsd">
    
            <!--定义切面Bean-->
            <bean id="myAspect" class="com.cisco.aspect.MyAscpect" />
            <aop:config>
                <!--配置切面-->
                <aop:aspect id="aspect" ref="myAspect">
                    <!--配置切入点-->
                    <aop:pointcut id="myPointCut" expression="execution(* com.cisco.jdk.*.*(..))"/>
                    <!--配置通知-->
                    <!--前置通知-->
                    <aop:before method="myBefore" pointcut-ref="myPointCut"/>
    
                    <!--后置通知-->
                    <aop:after-returning method="myAfterRunning" pointcut-ref="myPointCut" returning="returnVal"/>
    
                    <!--环绕通知-->
                    <aop:around method="myAround" pointcut-ref="myPointCut"/>
    
                    <!--异常通知-->
                    <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
                    
                    <!--最终通知-->
                    <aop:after method="myAfter" pointcut-ref="myPointCut"/>
                </aop:aspect>
            </aop:config>
    </beans>

     上述配置的讲解:在Spring的配置文件中,配置切面使用的是<aop:aspect>,这个元素会将已经定义好的Spring Bean转换为切面Bean,所以在配置文件中要先顶一个普通的Spring Bean,如上述代码中的myAspect,定义完成后,通过<aop:aspect>元素的ref属性即可引用该Bean。在配置<aop:aspect>元素的时候,通常会指定id和ref属性;配置切入点:切入点是同构<aop:pointcut>元素来定义的,当

    ======================================================

    未完待续~

  • 相关阅读:
    Reducing File Size
    程序设计中的命名
    代码进阶
    如何显示当前Mipmap级别?
    何时使用泛型集合
    Using Mono DLLs in a Unity Project
    lock关键字
    VSS/RSS/PSS/USS
    AssetBundle依赖
    WWW.LoadFromCacheOrDownload
  • 原文地址:https://www.cnblogs.com/bigdata-stone/p/10909873.html
Copyright © 2011-2022 走看看