这些就是第三步,我们要告诉这些通知方法,在什么时候需要执行。
配置里面的最后一个:
开启基于注解的AOP模式。
在配置文件ioc.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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="com.rtl" ></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
AOP的测试:
1、因为我们之前在配置的时候,就已经将切面类LogUtils和目标类MyCaculator加入到Spring容器里面去了。
所以我们在测试的时候,需要使用ioc.getBean的形式去获取对象。而不要new MyCaculator()
这里切记:
MyCalculator implements Caculator
Calculator calculator = ioc.getBean(Calculator.class);
测试类:
测试结果:
测试结果是正确的,但是,测试结果显示的执行顺序是有问题的。
我们后面来解决这个顺序的问题。
查看一下getBean获取的对象的类型。
Calculator calculator = ioc.getBean(Calculator.class);
AOP的底层就是动态代理。
所以,容器中保存的其实就是它的代理对象
通过bean.getClass(),得到的是$Proxy22
$Proxy22这个就不是它的本类类型。
问:本类的类型在IOC容器里面有吗?
答:有。
加了@Service注解的,时候,容器已启动,需要为它创建对象,但是创建的不是本类对象,而是它的代理对象。$Proxy22,而不是MyMathCalculator.class
之前也说过:代理对象和被代理对象唯一能产生关联的就是,他们都有同一个接口:Calculator
在IOCTest类上面没有注解,然后IOCTest这个类有个成员变量,叫做@Autowired private BookService bookService;
加了自动注入的标签,BookService 类上加了注解,容器启动可以为它创建对象,但是最后打印,它的对象就是null。
解释:
test方法执行,
立马执行:
ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc4.xml");
这句话,容器启动,会创建好BookService对象在容器里面,
但是,如果你要使用@Autowired这个注解进行DI自动注入,那么,你这个IOTest必须要是Spring的会员才行。但是你并不是。所以。你不能把Spring容器里面已经创建好的BookService对象自动注入给你的成员变量。
但是下面这种情况又不是null了
原因就是,它使用的是getBean的方式去获取对象,而不是自动注入的形式。
接着将AOP
问:为什么我们在测试AOP的时候,用getBean的方式去获取对象的时候,要传入接口的类型呢?
Calculator calculator = ioc.getBean(Calculator.class);
答:因为,代理对象和被代理对象的唯一联系就是他们实现了同一个接口Calculator
问:Calculator 接口并没有加到容器里面去,为什么通过getBean的形式可以获取到呢?
Calculator calculator = ioc.getBean(Calculator.class);
答:我们应该逆向来看。
首先:我们传入的是Calculator.class,那Spring会先按照类型去容器里面找对象。这时候找的就是它的实现类了。
所以,一般不会给接口加上注解。
要不然,容器一启动还要给接口创建对象,就很尴尬
接口上加了对象,Spring也很智能,它一看这个是interface,他就不会创建。
结论:在IOC容器里面保存的是代理对象。
下面:
如果LogUtils类上的@Component注解去掉的话。
问:含义是什么?
答:浅显意思是:Spring容器里面已经没有这个LogUtils对象了。深层意思:之前LogUtils类是一个切面类。它里面的通知方法需要动态的切入到MyMathCalculator类的方法去。现在切面类没有了,也就是没有人去切MyMathCalculator你了,这时候,你自己还是你自己,而不是什么代理对象了。
测试:
然后在加上:
结论:如果有切面对象去切你了,那容器就要为你创建代理对象来实现动态的切入。
3、如果你不用接口类型拿,你用id拿,也是代理对象:
id写的是实现类MyMathCalculator类名的首字母小写。
补充:
之前说过,使用动态代理写代码的形式,必须有一个硬性条件,就是需要实现接口。
那现在使用AOP的形式,就没有这个条件要求了;例如:
我们的MyMathCalculator类现在不实现接口了
那我们写测试的时候。获取对象的时候,就直接写本类
MyMathCalculator bean = ioc.getBean(MyMathCalculator.class);
这样就是Spring AOP的cglib帮我们创建了。
还是照样实现动态的切入
cglib帮我们创建好了代理对象
固定写法:
@Before("execution(访问权限符 返回值类型方法全类名(参数类型))")
切入点表达式的通配符:
1、 *
匹配一个或多个字符
匹配类名是MyMath开头的
@Before("execution(public int com.rtl.impl.MyMath*.*(int,int))")
匹配任意一个参数
第一个参数是int类型,第二个参数是任意类型,且只有两个参数。
@Before("execution(public int com.rtl.impl.MyMathCalculator.*(int,*))")
2、…
匹配任意多个参数,任意类型的参数
所以结论就是:
方法如果正常执行的话。
方法切入的顺序是:
@Before 前置通知
@After 后置通知
@AfterReturing 正常返回
所以方法异常执行的顺序就是:
1、@Before
2、@After
3、@AfterThrowing
现在的切面类里面的通知方法,没有打印出通知方法的详细信息。
例如:方法名,方法的参数值,方法的返回结果
现在只是简单的打印出一个字符串罢了。
做法:
只需要在通知方法里面传入一个参数:
JoinPoint 封装了当前目标方法的详细信息。
对比:
之前的通知方法:
现在的通知方法:
JoinPoint这个类是来自这里的:注意是大写的JointPoint
import org.aspectj.lang.JoinPoint;
JointPoint:这个类封装了当前目标方法(加减乘除)的详细信息
1、获取目标方法的运行参数:
Object[] args = joinPoint.getArgs();
2、获取目标方法的方法签名:
Signature signature = joinPoint.getSignature();
获取方法名:
Signature signature = joinPoint.getSignature();
String name = signature.getName();
1、目标方法的返回值的获取
第一步:
在通知方法上加上参数,Object result
第二步:
我们要告诉Spring,这通知方法的第二个参数Object result是用来接收目标方法的返回值的。
怎么告诉呢?
我们发现@AfterReturing注解里面有四个属性,其中第三个属性就是returing
@AfterReturning(value = "execution(public int com.rtl.impl.MyMathCalculator.*(int,int))",returning = "result")
returning = “result” 后面这个result就是对应参数名。
2、目标方法执行过程中如果出现异常了,怎么获取异常信息呢?
我们发现@AfterThrowing注解里面有四个属性,其中第三个属性就是throwing
@AfterThrowing(value = "execution(public int com.rtl.impl.MyMathCalculator.*(int,int))",
throwing = "exception")
现在测试:方法的返回值的获取和异常信息的获取。
1、返回值测试:
2、异常信息的获取:
Spring对通知方法的要求不严格,唯一有要求的就是通知方法的参数不能乱写:
为什么对通知方法的参数要求严格呢?
因为:通知方法的调用不是由我们cxy去调用的,而是,Spring在合适的时机帮我们调用的,那么调用这些方法就需要给参数传值,框架是利用反射技术传值的。所以,每一次,方法的调用,必须要确定这个参数的值,所以参数表的每一个参数,Spring 都需要认识。
我们在写参数的时候,都会说明
比如:我们写了一个参数
Exception exception
那么我们在上面的注解就说明了参数的含义
throwing = “exception”
还有一个要注意的就是:
我们在写异常的时候,一定要往大里面写。
加入你写的是A异常,但是这个时候,通知方法发生异常的时候,这个原因不是A异常,那这个时候就打印不出来异常信息了。
但是如果你写的是Exception最大的异常,那么,不管目标方法发生什么异常,都能拿到它的异常信息。
它很强大,但是很少使用
它之所以强大,是因为,它可以改变目标方法的参数。比如:目标方法的参数值。
比如本来人家是参数是10/2,,结果被你改成20/2
其他的四个通知
@Before
@After
@AfterThrowing
@AfterReturn
都不能改变目标方法的参数。