AOP细节一:容器中保存的是组件的代理对象
如果被代理对象实现了接口,使用接口类型获取
//细节一:AOP底层就是动态代理,容器中保存的组件是他的代理对象:$Proxy23,
// 所有用MyMathCalculator.class获取不到,因为他本身不是这个类型,他是其的代理对象
//通过接口获取
Calculator bean = ioc.getBean(Calculator.class);
bean.add(1,2);
System.out.println(bean);//com.atguigu.inter.impl.MyMathCalculator@2e1d27ba
System.out.println(bean.getClass());//class com.sun.proxy.$Proxy23
//可通过id还获取(首字母小写)
Calculator bean1 = (Calculator) ioc.getBean("myMathCalculator");
System.out.println(bean.getClass());//class com.sun.proxy.$Proxy23
如果被代理的对象没有实现接口,则使用本类类型获取
//没有接口就是本类类型
//若如此,CGLIB会帮我们创建好代理对象
//MyMathCalculator bean2 = ioc.getBean(MyMathCalculator.class);
MyMathCalculator bean2 = (MyMathCalculator) ioc.getBean("myMathCalculator");
//class com.atguigu.inter.impl.MyMathCalculator$$EnhancerBySpringCGLIB$$5d8c2ee8
System.out.println(bean2.getClass());
总结:有接口就转成接口类型(Calculator.class),没有接口就转成本类类型(MyMathCalculator.class)
AOP细节二:切入表达式的写法(通配符)
语法格式:
execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))
exp:
execution(public int com.atguigu.spring.ArithmeticCalculator.add(int,int))
*的用法
1、匹配一个或者多个字符 (MyMath*r)-->开头为MyMath,结尾为r
("execution(public int com.atguigu.inter.impl.MyMath*r.*(int,int))")
2、匹配任意一个参数 *(int,*) --->任意一个方法,第一个参数为int,第二个为任意类型,有且仅有两个参数
("execution(public int com.atguigu.inter.impl.MyMathCalculator.*(int,*))")
3、只能匹配一层路径
com.atguigu.inter.*.MyMathCalculator --->inter路径下任意路径下的MyMathCalculator
("execution(public int com.atguigu.inter.*.MyMathCalculator.*(int,int))")
4、权限位置*不能写:权限位置不写就行,默认就是public
..的用法
1、匹配任意多个参数,任意类型参数 *(..) --->任意方法任意参数
("execution(public int com.atguigu.inter.impl.MyMathCalculator.*(..))")
2、匹配任意多层路径 impl..MyMath --->impl路径下的任意路径MyMath
("execution(public int com.atguigu.inter.impl..MyMath.*(int,int))")
在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。相当于判断语句的用法
exp:
execution (* *.add(int,..)) || execution(* *.sub(int,..))
AOP细节三:执行顺序
try{
@Before
method.invoke(obj,args);
@AfterReturning
}catch(e){
@AfterThrowing:在目标方法抛异常之后
}finally{
@after:在目标方法运行结束之后
}
Spring4的时候,执行顺序
正常执行情况:@Before(前置通知)—>@After(后置通知)—>@AfterReturning(正常返回)
异常执行情况:@Before(前置通知)—>@After(后置通知)—>@AfterThrowing(方法异常)
Spring5执行顺序
正常执行情况:@Before(前置通知)—>@AfterReturning(正常返回)—>@After(后置通知)
异常执行情况:@Before(前置通知)—>@AfterThrowing(方法异常)—>@After(后置通知)
AOP细节四:JoinPoint获取目标方法的详细信息
只需在通知方法的参数列表写上一个参数:JoinPoint
joinPoint:封装了当前目标方法的详细信息
AOP细节五:通过throwing、returning属性来接收结果返回值和异常信息
//告诉Spring,用哪个参数接收返回值:returning = "result"
@AfterReturning(value = "execution(public * com.lly.cal.MyMathCalculator.*(..))",returning = "result")
public void logReturn(Object result) {
System.out.println("返回通知执行----------result:" + result);
}
//告诉Spring,用哪个参数接收异常:throwing = "e"
@AfterThrowing(value = "execution(public * com.lly.cal.MyMathCalculator.*(..))",throwing = "e" )
public void logexption(Exception e) {
System.out.println("异常通知执行------"+e.getMessage());
}
AOP细节六:Spring对通知方法的约束
Spring对通知方法的要求不严格:修改返回值;修改权限修饰符;去掉静态static都照样运行
但唯一有要求的是方法的参数列表一定不能乱写
原因:通知方法是Spring利用反射调用的,每次调用方法都需要确定这个方法的参数表的值;
参数表上的每一个参数,Spring都得知道是什么
即:参数列表上写的参数,一定要使用注解告诉Spring是什么?是returning;throwing等
ps:Object result参数的返回值类型会决定该方法能切的类型,如double result就
不切返回值是int等类型的方法,所以参数的返回值类型尽量往大的范围写。
AOP细节七:重用的切入点表达式使用@Pointcut注解
在AspectJ切面中,可以通过@Pointcut注解将一个切入点声明成简单的方法。切入点的方法体通常是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的
在编写AspectJ切面时,可以直接在通知注解中书写切入点表达式。但同一个切点表达式可能会在多个通知中重复出现
步骤
1、随便声明一个没有实现的返回void的空方法
2、给方法上标注@Pointcut注解
3、在需要用到这个内容的注解execution属性内写入这个空方法的方法名
@Pointcut("execution(public * com.lly.cal.MyMathCalculator.*(..))")
public void logPointcut(){}
AOP细节八:环绕通知
环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。
对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,
允许控制何时执行,是否执行连接点
在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。
如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。
注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的
返回值,否则会出现空指针异常
/***
* @Around环绕:是Spring中强大的通知
* try{
* //前置通知
* method.invoke(obj,args);
* //返回通知
* }catch(e){
* //异常通知
* }finally{
* //后置通知
* }
*
* 四合一通知就是环绕通知
* 环绕通知中有一个参数
*/
@Around("logPointcut()")
public Object Around(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
String name = pjp.getSignature().getName();
Object proceed = null;
try {
//@Before
System.out.println("【环绕前置通知】【"+name+ "方法开始】");
//利用反射调用目标方法,就是method.invoke(obj,args);
proceed = pjp.proceed(args);
//@AfterReturning
System.out.println("【环绕返回通知】【"+name+ "方法返回,返回结果值为"+ proceed+"】");
} catch (Exception e) {
//@AfterThrowing
System.out.println("【环绕异常通知】【"+name+ "方法异常,异常信息为"+ e+"】");
} finally {
//@After
System.out.println("【环绕后置通知】【"+name+ "方法结束");
}
//反射调用后的返回值也一定返回出去,不返回会空指针
return proceed;
}
AOP细节九:环绕通知顺序&抛出异常让其他通知感受到
* Spring4的顺序:
* 环绕前置
* 普通前置
* 目标方法执行
* 环绕返回/异常
* 环绕后置
* 普通返回/异常
* 普通后置
*
Spring5:
* 环绕前置
* 普通前置
* 目标方法执行
* 普通返回/异常
* 普通后置
* 环绕返回/异常
* 环绕后置
*/
@Around("OniMyPoint()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
String name = pjp.getSignature().getName();
//利用反射调用目标方法,就是method.invoke(obj,args);
Object proceed = null;
try {
//@Before
System.out.println("【环绕前置通知】【"+name+ "方法开始】");
proceed = pjp.proceed(args);
//@AfterReturning
System.out.println("【环绕返回通知】【"+name+ "方法返回,返回结果值为"+ proceed+"】");
} catch (Exception e) {
//@AfterThrowing
System.out.println("【环绕异常通知】【"+name+ "方法异常,异常信息为"+ e+"】");
//为了让外界能知道这个异常,这个异常一定要抛出去
throw new RuntimeException(e);
} finally {
//@After
System.out.println("【环绕后置通知】【"+name+ "方法结束");
}
//反射调用后的返回值也一定返回出去
return proceed;
}
ps:如果只有一个切面类,里面写有环绕通知和普通通知,那么异常不用抛也是可以感知的
如果有多个切面类,如果环绕通知里不抛出异常,那么别的切面就无法感知
AOP细节十:多切面运行顺序
写在前面
@Order注解可以设置切面的优先级,数字越小优先级越高
知道顺序和栈一样就行,先进后出,如图,清晰明了
奇怪的理解:
根据优先级执行切面(先选出log)---->log环绕前置执行--> proceed = pjp.proceed(args);
---->log的普通前置---->log的前置执行完,到va的环绕前置---->proceed = pjp.proceed(args);
----> va普通前置---->目标方法……
AOP应用
1、AOP加日志保存到数据库中
2、AOP做权限验证
3、AOP做安全检查
4、AOP做事务控制
基于xml的AOP
如果是别的包有的切面类想引用,注解是无法引用得,那么就可以使用配置方法配置切面
web.xml
<!--基于注解的AOP步骤:
1、将目标类、切面类加入到IOC容器中===>@Component
2、告诉Spring哪个是切面类===>@Aspect
3、在切面类中使用5个通知注解,来配置切面中的这些通知方法都何时何地运行
4、在配置文件中开启基于注解的AOP功能 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-->
<!--基于配置的AOP @Component-->
<!--切面加入到容器中-->
<bean id="myMathCalculator" class="com.atguigu.inter.impl.MyMathCalculator"></bean>
<bean id="logUtils" class="com.atguigu.utlis.LogUtils"></bean>
<bean id="validateApsect" class="com.atguigu.utlis.ValidateApsect"></bean>
<!--使用AOP名称空间-->
<aop:config>
<!--指定切面 @Aspect-->
<aop:aspect ref="logUtils">
<aop:pointcut id="myPoint" expression="execution(* com.atguigu.inter.impl.*.*(..))"/>
<!--配置哪个方法是前置通知 method属性:指定方法名
logStart @Before("切入点表达式")
-->
<aop:before method="logStart" pointcut="execution(* com.atguigu.inter.impl.*.*(..))"></aop:before>
<aop:after-returning method="logReturn" pointcut-ref="myPoint" returning="result"></aop:after-returning>
<aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="e"></aop:after-throwing>
<aop:after method="logEnd" pointcut-ref="myPoint"></aop:after>
<aop:around method="myAround" pointcut-ref="myPoint"></aop:around>
</aop:aspect>
<aop:aspect ref="validateApsect">
<aop:pointcut id="myPoint" expression="execution(* com.atguigu.inter.impl.*.*(..))"/>
<aop:before method="logStart" pointcut-ref="myPoint"></aop:before>
<aop:after-returning method="logReturn" pointcut-ref="myPoint" returning="result"></aop:after-returning>
<aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="e"></aop:after-throwing>
<aop:after method="logEnd" pointcut-ref="myPoint"></aop:after>
</aop:aspect>
</aop:config>