Spring AOP

异常:
java.lang.IllegalArgumentException:error at ::0 formal unbound in pointcut(返回值参数出错)


AOP:面向方面编程


a.切面(Aspect):一个横切功能的模块化,这个功能可能会横切多个对象(业务)。(一个方法就是一个“切面”)
b.切入点(Pointcut):可以插入“横切逻辑”的方法。
c.通知(Advice):
1.前置通知(Before Advice):在切入点XXX()方法执行之前,插入通知。
2.后置通知(After Returning Advice):在切入点XXX()方法执行完毕之后,插入通知。
3.异常通知(After Throwing Advice):当切入点XXX()方法抛出异常时,插入的通知。
4.最终通知(After Finally Advice):当切入点XXX()方法执行完毕时,插入的通知(不论是正常返回还是异常退出)。
5.环绕通知(Around Advice):可以贯穿切入点XXX()方法执行的整个过程。


普通类 --》特定功能的类
继承类、实现接口、注解、配置


普通类 --->“通知”


一、实现接口

通知类型 需要实现的接口 接口中的方法
前置通知 org.springframework.aop.MethodBeforeAdvice before()
后置通知 org.springframework.aop.MethodReturningAdvice afterReturning()
异常通知 org.springframework.aop.ThrowsAdvice 无
环绕通知 org.springframework.aop.MethodInterceptor invoke()


前置通知实现步骤:
1.jar
aopaliance.jar
aspectjweaver.jar
2.配置

3.编写
aop:每当执行add()之前 自动执行的一个方法log()
add();业务方法

package com.lv.jee.spring.comment;

public class Student {
    private int stuNo;
    private String stuName;
    private int stuAge;
    public int getStuNo() {
        return stuNo;
    }
    public void setStuNo(int stuNo) {
        this.stuNo = stuNo;
    }
    public String getStuName() {
        return stuName;
    }
    public void setStuName(String stuName) {
        this.stuName = stuName;
    }
    public int getStuAge() {
        return stuAge;
    }
    public void setStuAge(int stuAge) {
        this.stuAge = stuAge;
    }
}
package com.lv.jee.spring.comment;

import org.springframework.stereotype.Component;

import com.lv.jee.spring.dao.IStudentDao;
/*
 * <bean id="studentDao" class="com.lv.jee.spring.comment.StudentDaoImpl" ></bean>
 * 
 */
@Component("studentDao")
public class StudentDaoImpl implements IStudentDao{
    public void addStudent(Student student) {
        System.out.println("增加学生。。。。");
    }
}

log(); 自动执行的通知,即aop前置通知

 

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

package com.lv.jee.spring.aop;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

/**
 * 前置通知
 * @author Bin
 * @date2019年3月21日
 */
public class LogBefore implements MethodBeforeAdvice{
    //前置通知的具体内容
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("前置通知");
    }

}

 

<!-- 通知所在的类 -->
    <!-- ==========连接线的一方=========== -->
    <bean id="logBefore" class="com.lv.jee.spring.aop.LogBefore">
    </bean>
    
    
    <!-- 前置通知 -->
    <!-- 将方法所在类和通知进行关联 -->
    <aop:config>
    
        <!-- 配置切入点(在哪里执行通知) -->
        <!-- ==========连接线的另一方=========== -->
        <!--     
        <aop:pointcut expression="execution(public void com.lv.jee.spring.service.StudentServiceImpl.addStudent(com.lv.jee.spring.comment.Student))"  id="poioncutadd"/>
        <aop:pointcut expression="execution(public void com.lv.jee.spring.service.StudentServiceImpl.deleteStudentByNo(int))"  id="poioncutdel"/>
         -->
         <aop:pointcut expression="execution(public void com.lv.jee.spring.service.StudentServiceImpl.deleteStudentByNo(int)) or execution(public void com.lv.jee.spring.service.StudentServiceImpl.addStudent(com.lv.jee.spring.comment.Student))"  id="poioncut"/>
        <!-- 相当于链接切入点和切面的线 -->
        <!-- ==========连接线=========== -->
        <!-- 
        <aop:advisor advice-ref="logBefore" pointcut-ref="poioncutadd"/>
        <aop:advisor advice-ref="logBefore" pointcut-ref="poioncutdel"/> 
        -->
        <aop:advisor advice-ref="logBefore" pointcut-ref="poioncut"/>
        
    </aop:config>

 

 

例如定义切入点表达式 execution(* com.sample.service.impl..*.*(..))
execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分:
1、execution(): 表达式主体。
2、第一个*号:表示返回类型,*号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
4、第二个*号:表示类名,*号表示所有的类。
5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。


后置通知:(方法等正常执行完)
a.通知类 普通类实现接口
b.业务类、业务方法
c.配置(连接线)(1.将业务类、通知 纳入SpringIoC容器 2.定义切入点(一端)、定义通知类(另一端),通过pointcut-ref将两端连接起来)

 

package com.lv.jee.spring.aop;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;
/*
 *|后置通知,实现接口
 */
public class LogAfter implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("后置通知:目标对象"+target+", 调用的方法名"+method.getName()+",方法的参数个数:"+args.length+",方法的返回值:"+returnValue);
    }

}

 

<!-- 将通知纳入SpringIoC容器 -->
    <bean id="logAfter" class="com.lv.jee.spring.aop.LogAfter">
    </bean>
    
    <!-- 后置通知 -->
    <aop:config>
        <!-- 切入点(连接线的一端:业务类的具体方法) -->
        <aop:pointcut expression="execution(public void com.lv.jee.spring.service.StudentServiceImpl.addStudent(com.lv.jee.spring.comment.Student))"  id="poioncut2"/>
        
        <!-- 连接线 -->
        <aop:advisor advice-ref="logAfter" pointcut-ref="poioncut2"/>
        <!-- 
        <aop:advisor advice-ref="com.lv.jee.spring.aop.LogAfter" pointcut-ref="poioncut2"/> 
        -->
    </aop:config>

 

 

异常通知:(方法等正常执行完)
根据异常通知接口的定义可以发现,异常通知的实现类必须编写以下方法:
public void afterThrowing([Method, args, target], ThrowableSubclass);
a.public void afterThrowing(ThrowableSubclass);
b.public void afterThrowing(Method, args, target, ThrowableSubclass);

package com.lv.jee.spring.aop;

import java.lang.reflect.Method;

import org.springframework.aop.ThrowsAdvice;

public class LogException implements ThrowsAdvice {
    //异常通知的具体方法
    public void afterThrowing(Method method,Object[] args,Object target, Throwable ex) {
        System.out.println("异常通知:目标对象:"+target+",方法名:"+method.getName()+",方法的参数个数:"+args.length+",异常类型"+ex.getMessage());
    }
}

 

<!-- 异常通知 -->
    <bean id="logException" class="com.lv.jee.spring.aop.LogException">
    </bean>
    <aop:config>
        <aop:pointcut expression="execution(public void com.lv.jee.spring.service.StudentServiceImpl.addStudent(com.lv.jee.spring.comment.Student))"  id="poioncut3"/>
        <aop:advisor advice-ref="logException" pointcut-ref="poioncut3"/>
    </aop:config>

 


环绕通知:在目标方法的前后、异常发生时、最终等各个地方都可以进行通知。
最强大的一个通知:可以获取目标方法的全部控制权(目标方法是否执行、执行之前、执行之后、参数、返回值等)。
在使用环绕通知时,目标方法的一切信息,都可以通过invocation参数获取到。
环绕通知底层是通过拦截器实现的。

 

 

package com.lv.jee.spring.aop;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class LogAround implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // TODO Auto-generated method stub
        //方法体1
        Object result = null;
        try {
            //方法体2
            System.out.println("用环绕通知实现的【前置通知】");
            System.out.println("目标对象target:"+invocation.getThis()+", 调用的方法名:"+invocation.getMethod().getName()+
                    ",方法的参数个数:"+invocation.getArguments().length);
            //invocation.proceed(); 之前的代码:前置通知
            result = invocation.proceed();    //控制着目标方法的执行,addStudent
            //result就是目标方法addStudent()方法的返回值
            //invocation.proceed(); 之前的代码:后置通知
            System.out.println("用环绕通知实现的【后置通知】");
            System.out.println("目标对象target:"+invocation.getThis()+", 调用的方法名:"+invocation.getMethod().getName()+
                    ",方法的参数个数:"+invocation.getArguments().length+",方法的返回值:"+result);
        } catch (Exception e) {
            // TODO: handle exception
            //方法体3
            //异常通知
            System.out.println("用环绕通知实现的【异常通知】");
        }
        return result;    //目标方法的返回值
    }

}

 

<!-- 环绕通知 -->
    <bean id="logAround" class="com.lv.jee.spring.aop.LogAround">
    </bean>
    <aop:config>
    <!-- 
        <aop:pointcut expression="execution(public void com.lv.jee.spring.service.StudentServiceImpl.addStudent(com.lv.jee.spring.comment.Student))"  id="poioncut4"/>
         -->
        <aop:pointcut expression="execution(public * com.lv.jee.spring.service.StudentServiceImpl.addStudent(..))"  id="poioncut4"/>
        <aop:advisor advice-ref="logAround" pointcut-ref="poioncut4"/>
    </aop:config>

 

 

二、基于注解形式的AOP:
1.jar
aopaliance.jar
aspectjweaver.jar
2.配置

3.编写
1.将业务类、通知 纳入SpringIoC容器
纳入SpringIoC容器(注解或者Bean)
2.开启注解对AOP的支持--<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
通知:
import org.aspectj.lang.annotation.Aspect;

@Aspect //声明该类是一个通知
public class LogAspectAnnotation {

}

注意:通过注解形式 将对象增加到 IoC容器时,需要设置扫描器
<context:component-scan base-package="com.lv.jee.spring.aop.annotation"></context:component-scan>

扫描器---会将指定的包中的:
@Component @Service @Respository @Controller修饰的类产生的对象 增加到IoC容器中。

通过注解形式 实现的AOP,如果想获取 目标对象的一些参数,则需要使用一个对象:JoinPoint

注解形式的返回值:
a.在注解的参数列表里声明返回值的参数名
b.注解形式实现AOP时,通知的方法的参数不能多、少

 

package com.lv.jee.spring.aop.schema;


import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class LogSchema {
    //后置通知----JoinPoint适用于注解、Schema
    public void afterReturningSchema(JoinPoint jp,Object returningValue) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("¥¥¥¥¥¥Schema后置通知");
    }
    
    //前置通知
    public void beforeSchema() throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("¥¥¥¥¥¥¥Schema前置通知");
    }
    
    //异常通知
    public void afterThrowingSchema(JoinPoint jp,NullPointerException e) {
        System.out.println("¥¥¥¥¥¥¥Schema异常通知:"+e.getMessage());
    }
    
    //环绕通知
    //注意:环绕通知会返回目标方法的返回值,返回值类型Object
    public Object myAround(ProceedingJoinPoint jp){
        System.out.println("环绕通知-------------前置通知");
        Object result = null;
        try {
            result = jp.proceed();
            System.out.println("环绕通知-------------后置通知");
        } catch (Throwable e) {    //这里的Exception要修改成Throwable
            // TODO: handle exception
            System.out.println("环绕通知-------------异常通知");
        } finally {
            System.out.println("环绕通知-------------最终通知");
        }
        return result;
    }
}

 

package com.lv.jee.spring.aop.annotation;
import java.util.Arrays;

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.springframework.stereotype.Component;

//@Component("logAnnotation")    等价于<bean id="" class=""></bean>
@Component("logAnnotation")        //纳入SpringIoC容器    //依赖扫描器
@Aspect        //声明该类为通知类    //依赖开启
public class LogAspectAnnotation {
    //没有指定包和类的话,addStudent(..)是任意包和类里的方法
    @Before("execution(public * com.lv.jee.spring.service.StudentServiceImpl.addStudent(..))")    //属性:定义切点
    public void myBefore(JoinPoint jp) {
        System.out.println("#【注解形式--前置通知】:目标对象:"+jp.getTarget()+",方法名:"+jp.getSignature().getName()+",参数列表:"+Arrays.toString(jp.getArgs()));
    }
    
    //addStudent(..)是任意包和类里的方法
    //后置方法
    @AfterReturning(pointcut="execution(public * addStudent(..))",returning="returningValue")//在前面添加returning="returningValue"
    public void myAfter(JoinPoint jp,Object returningValue) {    //returningValue是返回值,但需要告诉Spring
        System.out.println("#【注解形式--后置通知】:目标对象:"+jp.getTarget()+",方法名:"+jp.getSignature().getName()+",参数列表:"+Arrays.toString(jp.getArgs())+",返回值:"+returningValue);
    }
    
    
    //环绕通知
    @Around("execution(public * com.lv.jee.spring.service.StudentServiceImpl.addStudent(..))")    //属性:定义切点
    public void myAround(ProceedingJoinPoint jp) {
        System.out.println("#【注解形式--环绕通知】:目标对象:"+jp.getTarget()+",方法名:"+jp.getSignature().getName()+",参数列表:"+Arrays.toString(jp.getArgs()));
        //方法执行之前:前置通知
        System.out.println("<[环绕]方法执行之前:前置通知>");
        try {
            //方法执行时
            jp.proceed();
            //方法执行之后:后置通知
            System.out.println("<[环绕]方法执行之后:后置通知>");
        } catch (Throwable e) {
            // TODO: handle exception
            //方法执行发生异常时:异常通知
            System.out.println("<[环绕]方法执行发生异常时:异常通知>");
        } finally {
            //最终通知
            System.out.println("<[环绕]最终通知>");
        }
    }
    
    
    //异常通知:如果只捕获特点类型的已存在异常,则可以通过第二参数实现:e
    @AfterThrowing(pointcut="execution(public * com.lv.jee.spring.service.StudentServiceImpl.addStudent(..))",throwing="e")    //属性:定义切点
    public void myAfterThrowing(JoinPoint jp,NullPointerException e) {
        System.out.println("#【注解形式--异常通知】:目标对象:"+jp.getTarget()+",方法名:"+jp.getSignature().getName()+",参数列表:"+Arrays.toString(jp.getArgs()));
    }
    
    
    //最终通知
    @After("execution(public * com.lv.jee.spring.service.StudentServiceImpl.addStudent(..))")    //属性:定义切点
    public void myAfter(JoinPoint jp) {
        System.out.println("#【注解形式--最终通知】:目标对象:"+jp.getTarget()+",方法名:"+jp.getSignature().getName()+",参数列表:"+Arrays.toString(jp.getArgs()));
    }
}

 

 

三、通过配置将类-->通知(基于Schema配置)

类似于实现接口的方式
Schema方式通知:
a.编写一个普通类
b.将该类通过配置,转为一个“通知”

JoinPoint适用于注解、Schema
//环绕通知
public void myAround(ProceedingJoinPoint jp) {}

<!-- 开启注解对AOP的支持 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <!-- Component扫描 -->
    <!-- 
    <context:component-scan base-package="com.lv.jee.spring.aop.annotation">
    </context:component-scan> 
    -->
    <bean id="" class="com.lv.jee.spring.aop.annotation.LogAspectAnnotation">
    </bean>
    
    <!-- Schema配置 -->    
    <!-- 将准备转为通知的类纳入SpringIoC容器 -->
    <bean id="logSchema" class="com.lv.jee.spring.aop.schema.LogSchema">
    </bean>
    <aop:config>
        <!-- 切入点(连接线的一端:业务类的具体方法) -->
        <aop:pointcut expression="execution(public void com.lv.jee.spring.service.StudentServiceImpl.addStudent(com.lv.jee.spring.comment.Student))"  id="poioncut5"/>
        
        <!-- 连接线
        <aop:advisor advice-ref="logSchema" pointcut-ref="poioncut5"/>
         -->
        <!--  -->
        <aop:aspect ref="logSchema">
            <!-- 连接线:连接业务 -->
            <aop:before method="beforeSchema" pointcut-ref="poioncut5"/>
            <!-- 连接线:连接业务 -->
            <aop:after-returning method="afterReturningSchema" pointcut-ref="poioncut5" returning="returningValue"/>
            <!-- 连接线:连接业务 -->
            <aop:after-throwing method="afterThrowingSchema" pointcut-ref="poioncut5" throwing="e"/>
            
            <aop:around method="myAround" pointcut-ref="poioncut5"/>
        </aop:aspect>
    </aop:config>

 

posted @ 2019-04-06 16:28  B-binary  阅读(222)  评论(0编辑  收藏  举报