AOP

AOP:(Aspect Oriented Programing):面向切面编程
OOP:(Object Oriented Programing ):面向对象编程
面向切面编程是面向对象编程的一种补充;
定义:指在程序运行期间,动态的将某段代码插入到指定方法的指定位置进行运行的一种编程方式;
 
动态代理:
代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
 
1、给调用方法的接口创建代理实例
Proxy.newProxyInstance(loader, interfaces, h)
// loader :类加载器(被代理类的)
// Class<?>[] interfaces:所实现的所有接口(被代理对象的)
//invocationHandler h:方法执行器:通过它来执行被代理对象的所有方法

2、invocationHandler 核心方法,实现方法程序的调用,可以在这里面对调用的方法进行预处理等等。

public class MathCaculatorProxy {
    public static Caculator getProxy(final Caculator cal){
        //创建动态代理实例
        /**
         * return 一个代理实例
         *  loader :类加载器(被代理类的)the class loader to define the proxy class
         *  Class<?>[] interfaces:所实现的所有接口(被代理对象的)
         *  InvocationHandler h:方法执行器:通过它来执行被代理对象的所有方法
         */
        //1、获取被代理对象的类加载器
        ClassLoader loader = cal.getClass().getClassLoader();
        //2、获取被代理对象的所有接口
        Class<?>[] interfaces = cal.getClass().getInterfaces();
    
        Object proxy = Proxy.newProxyInstance(loader, interfaces, new InvocationHandler(){
            //2. InvocationHandler 的核心方法
            // 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象
            // 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
            
            /**
             * invoke:每次被代理对象执行方法的时候,其实会来到这里;
             * 宋哲帮宝宝执行方法的地方
             * Object proxy(代理), 宋哲:代理对象
             * Method method, 代理对象代理的方法;
             * Object[] args, 方法执行时传的参数的值
             */
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable{//Throwable是Exception的父类
                //真正的目标方法执行完成以后的返回值
                //obj:调用哪个对象的add方法;
                //method.invoke以后会返回方法的返回值
                //System.out.println("宋哲正在呼叫宝宝执行方法....");
                //method.invoke目标方法真正被执行....
                Object result = null;
                try {
                    LogUtils.logStart(method, args);
                    result = method.invoke(cal, args);
                    LogUtils.logReturn(method, result);
                } catch (Exception e) {
                    LogUtils.logException(method,e);
                    //动态代理的时候,我们一般建议都需要将异常继续抛出去,这样外界才能知道;
                    throw new RuntimeException(e);
                }
                return result;
            }
        });
        //返回代理对象(计算器)
        return (Caculator) proxy;
    }
}
SpringAOP:面向切面编程;底层就是动态代理;
Spring为了简化动态代理,提供了AOP功能;
使用SpringAOP完成日志记录动态切入的功能;

AOP功能使用步骤:

1、导包

基础包

commons-logging-1.1.3.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar

AOP功能包;

spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
增强版的面向切面功能:
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
添加的增强jar包是为了支持AspectJ框架和切点表达式。
 

AspectJ:Java社区里最完整最流行的AOP框架。

在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。

⑤AspectJ支持5种类型的通知注解:

[1]@Before:前置通知,在方法执行之前执行

[2]@After:后置通知,在方法执行之后执行

[3]@AfterRunning:返回通知,在方法返回结果之后执行

[4]@AfterThrowing:异常通知,在方法抛出异常之后执行

[5]@Around:环绕通知,围绕着方法执行

 切入点表达式:

通过表达式的方式定位一个或多个具体的连接点。

2、写配置
 

/**
* 1、AOP细节一:IOC容器中保存的是组件的代理对象;
*
* 为什么就不能用实际的类型获取?
* 真正的类型:com.sun.proxy.$Proxy12(代理类型);
* 为什么能用接口类型获取呢?
* 动态代理机制:被代理对象和代理对象唯一产生关联的地方就是实现了同一接口;
* 所以只能使用接口类型来获取;因为保存的是代理对象;
*
* JDK如果对象没有实现接口是不能创建动态代理的;
*
*/

所以用切面的业务组件,需要用接口来获取。

/**
     * AOP细节二
     * 切入点表达式:
     * execution(public int com.atguigu.impl.MathCalculator.*(intint))
     * execution(访问权限控制符  返回值类型  全类名.方法名(参数类型列表));
     *
     * 提供了两种通配符:
     *  *:匹配任意多字符;
     *      execution(public int com.atguigu.impl.*.a*(*, int))
     *      参数位置:
     *       *:任意类型的参数,匹配一个参数位置
     *      execution(public int com.atguigu.impl.*.*(int,*))
     *          只能匹配两个参数的,但是最后一个参数任意
     *      
     *  ..:匹配任意多字符,任意多个参数;
     *  1)、..写在方法的参数位置
     *      方法重载:execution(public int com.atguigu.impl.*.*(..))
     *      表示任意类的任意方法的任意参数(个数类型都不限);
     *
     *    execution(public int com.atguigu.impl.*.*(int,..))
     *      能匹配任意参数的,但是第一个参数必须是int
     *  2)、..写在包的层级位置:匹配多层路径
     * 
     * 
     *  特别:访问权限控制符(要么不写,要写也只能写public)
     * 
     * 
     *  最模糊的:
     *      execution(* *(..))
     *  最详细的:
     *      execution(public int com.atguigu.impl.MathCalculator.add(int,int))
     * 
     *  高级应用:
     *  &&:并且
     *      eg:切入的位置,比须满足前面的表达式还必须满足后面的表达式。交集
     *          execution(public int com.atguigu.impl.MathCalculator.add(..))&&execution(* *(int,int))
     *      div(int,int)不会切入,add(int,int)会切入
     *  ||:
     *      eg:切入的位置满足前面的表达式或者后面的都行;只要满足任意一个表达式的条件即可
     *          execution(public int com.atguigu.impl.MathCalculator.add(..))||execution(* *(int,int))
     *      div(int,int)会切入,add(int,int)会切入
     */
/**
     * AOP细节三:
     *      方法的细节信息,如返回值,异常,【方法名】,【参数列表】...如何获取;
     * 1)、在返回通知的时候可以用returning:指定哪个参数用来接受返回值
     *      指定的类型,表示的就是当前通知只是用来接受指定类型返回值的,如果返回值类型不一样,返回通知不会被调用
     * 2)、在异常通知的时候可以用throwing:指定哪个参数用来接受异常
     *      NullPointerException:当前通知方法只是用来接受空指针异常的;如果目标方法出现其他异常,通知方法都不会被调用
     * JoinPoint joinPoint(封装了当前连接点的详细信息);
     * AJAX:类似的;
     *   $.ajax({
     *      url:"xxx",
     *      success:function(data){
     *          alert(data)
     *      }
     *   })
     *
     */
//1、获取方法的目标方法运行时的参数列表;result = method.invoke(cal, 【args】);
        Object[] args = joinPoint.getArgs();
        //2、获取方法签名;当前方法的所有详细信息都在
        Signature signature = joinPoint.getSignature();
        //3、获取方法名
        String name = signature.getName();
/**
     * AOP细节四:
     * 1、通知方法的约束;
     *      只有一个约束;参数列表Spring必须清楚,每个参数代表什么;
     *
     * AOP细节五:
     * 通知方法的调用顺序;  @Before  @After  @AfterReturning @AfterThrowing
     * try{
     *      @Before
     *      method.invoke(obj,args);
     *      @AfterReturning
     * }catch(Exception e){
     *      @AfterThrowing
     * }finally{
     *      @After
     * }
     * 正常顺序:@Before==目标方法执行=>@After===>@AfterReturning
     * 异常顺序:@Before==目标方法执行=>@After===>@AfterThrowing
     *
     * @param joinPoint
     */
 

    /**
     * AOP细节六、抽取可重用的切入点表达式
     * 定义一个可重用的切入点表达式,以后的表达式直接引用;
     */
    @Pointcut("execution(public int com.atguigu.impl.MathCalculator.*(..))")
    public void mypointcuthaha(){}
 
@Before("mypointcuthaha()")
    public static void logStart(JoinPoint joinPoint){}
/**
 * 验证切面
 * 
 * @author lfy
 * 
 * @Order(1):指定整数值,默认不指定,一个非常大的整数2147483647
 *         数值越小优先级越高,越先执行
 * 
 */
    /**
     * AOP细节八:
     * 1、通知方法;
     * @Before
     * @After
     * @AfterThrowing
     * @AfterReturing
     * 
     * @Around:环绕通知;(四合一的);就是一个动态代理;
     * 参数:
     * 关注的几个点:
     * 1)、将目标方法执行后的返回返回出去;
     *         return  proceed = pjp.proceed(args);
     * 2)、将异常抛出去方便外界感知;
     * 3)、环绕的几个位置和其他通知在一起时:执行顺序;
     *     环绕通知在自己的切面里面拥有最高优先级,优先执行;环绕先进去先出来;
        【环绕】前置通知
        【日志】前置通知:【add】方法运行开始了;使用的参数列表【[10, 1, 1]】
        方法内部打印:11
        【环绕】返回通知,返回值:11
        【环绕】后置通知
        【日志】后置通知【add】最终结束;
        【日志】返回通知【add】正常返回,返回值:【11】
     * 
     * 
     * 多切面带环绕顺序:
     * 背景:BV  order1   LOG(普通+环绕)  order2
     * BV===前置
     * LOG===环绕前置
     * LOG===前置
     * 目标方法
     * LOG===环绕返回
     * LOG===环绕后置
     * LOG===后置
     * LOG===返回
     * BV===后置
     * BV===返回
     * 
     * 
     * 环绕在单切面环境:
     * LOG===环绕前置
     * LOG===前置
     * 目标方法
     * LOG===环绕返回
     * LOG===环绕后置
     * LOG===后置
     * LOG===返回
     * 
     * 业务逻辑:
     * 
     */
 
基于配置的AOP
<!-- 1、把写注解的整个逻辑用配置做出来 -->
    <!--
        1、将切面类和业务逻辑组件都加入到ioc容器中 
        2、告诉SpringIOC哪个是切面
        3、配置切面里面的没=每一个方法何时运行
     -->
     <!--@Component  @Service  -->
     <bean id="mathCalculator" class="com.atguigu.impl.MathCalculator"></bean>
     <bean id="BVaAspect" class="com.atguigu.utils.BVaAspect"></bean>
     <bean id="logUtils" class="com.atguigu.utils.LogUtils"></bean>
    
     <!-- aop配置 -->
     <aop:config>
        <!--配置切面的详细信息  -->
        <!-- 配置一个可以多个切面都能引用的切入点表达式 -->
        <aop:pointcut expression="execution(public int *.*(..))" id="mypoint"/>
        <!--一个aop:aspect就是一个切面,指定哪个组件是切面  ref="BVaAspect";@Aspect  -->
        
        <aop:aspect ref="logUtils" order="1">
            <aop:before method="logStart" pointcut-ref="mypoint"/>
            <aop:after method="logEnd" pointcut-ref="mypoint"/>
            <!-- public static void logReturn(JoinPoint joinPoint,Object data)   returing -->
            <aop:after-returning method="logReturn" pointcut-ref="mypoint" returning="data"/>
            <aop:after-throwing method="logException" pointcut-ref="mypoint" throwing="e"/>
        </aop:aspect>
        
        <aop:aspect ref="BVaAspect" order="0">
            <aop:pointcut expression="execution(public int *.*(..))" id="myselfpoint"/>
            <!-- 配置每个方法何时运行 -->
            <!--  method:指定方法名  @Before("xxx")-->
            <aop:before method="vaStart" pointcut="execution(public int *.*(..))"/>
            <!--pointcut-ref:配置引用哪个切入点表达式  -->
            <aop:after method="vaEnd" pointcut-ref="mypoint"/>
            <aop:after-returning method="vaReturning" pointcut-ref="myselfpoint"/>
            <aop:after-throwing method="vaException" pointcut-ref="mypoint"/>
        </aop:aspect>
     </aop:config>
    
     <!--重要的用配置,一般的用注解  -->
 
总结;
注解的AOP

1)对切面类的方法进行配置
2)将切面类和业务逻辑组件都加入到容器
3)告诉SpringIOC容器哪个类是切面类@Aspect
4)开启基于注解的AOP功能

基于XML的AOP

1)对切面类的方法进行配置
2)将切面类和业务逻辑组件都加入到容器
3)告诉SpringIOC容器哪个类是切面类@Aspect

原则: <!--重要的用配置,一般的用注解  -->

         指定切面的优先级

 在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的。

 切面的优先级可以通过实现Ordered接口或利用@Order注解指定。

实现Ordered接口,getOrder()方法的返回值越小,优先级越高。

若使用@Order注解,序号出现在注解中

原文地址:https://www.cnblogs.com/limingxian537423/p/7242648.html