什么是SpringAOP?
通俗的话来讲:就是你的已经做好的项目,需要给他增加功能,或者在更新迭代的时候,把以前的老的程序里面的方法做增强的话,最原始的手段是去直接改代码,这样做的感觉是很不友好的,造成代码的侵入性。
而AOP的思想是,不去动原来的代码,而是基于原来代码产生代理对象,通过代理的方法,去包装原来的方法,就完成了对以前方法的增强。换句话说,AOP的底层原理就是动态代理的实现。
关于AOP的三个步骤,把他记住了,AOP就不会有问题:
1. 确定目标对象(target—>bean) 通俗的来讲就是“谁不行了,你要把他交给spring,就是哪个方法需要增强,你就把他交给spring。
2. 编写Advice通知方法 (增强代码) 就是写增强代码
3. 配置切入点和切面 第三点的作用就是:让你的增强代码作用于你要增强的目标对象上
SpringAOP有两种实现方式:传统版本和AspectJ。具体操作都能实现业务需求,但是在这里还是希望大家能使用AspectJ,毕竟整体配置起来较为简单、轻量化,而且现在企业几乎都是AspectJ,传统的方法了解一下即可。
1、首先介绍一下传统的方式:
传统SpringAOP的Advice编写:
AOP联盟为通知Advice定义了org.aopalliance.aop.Interface.Advice
Spring按照通知Advice在目标类方法的连接点位置,可以分为5类
(1)前置通知 org.springframework.aop.MethodBeforeAdvice
* 在目标方法执行前实施增强
(2)后置通知 org.springframework.aop.AfterReturningAdvice
* 在目标方法执行后实施增强
(3)环绕通知 org.aopalliance.intercept.MethodInterceptor
* 在目标方法执行前后实施增强
(4)异常抛出通知 org.springframework.aop.ThrowsAdvice
* 在方法抛出异常后实施增强
(5)引介通知 org.springframework.aop.IntroductionInterceptor
* 在目标类中添加一些新的方法和属性
简单的说:通知就是增强的方式方法
遵循aop联盟规范,传统Spring AOP编程的Advice有五种(前置通知、后置通知、环绕通知、异常通知、引介通知) ,
注意:传统SpringAOP的Advice 必须实现对应的接口!
那小猴就以案例驱动来带你认识一下AOP,以环绕通知为例:
【需求】:开发一个记录方法运行时间的例子。将目标方法的运行时间,写入到log4j的日志中。
传统AOP三步走:
第一步: 引入所需依赖:
org.springframework
spring-context
org.springframework
spring-aspects
junit
junit
test
org.slf4j
slf4j-log4j12
org.springframework
spring-test
test
引入applicationContext.xml配置文件和日志文件:
创建一个包,随便里面扔俩类
如果直接在源代码上增加功能的话,那就变成了侵入式编程了,所以在不更改源代码的情况下,使用AOP进行方法的更新。
使用动态代理的代码将对象配置到spring工厂中:
第二步:编写传统aop的Advice通知类。
首先我们新建一个包,名字叫oldaop,因为是用老的传统的方法来搞,然后包里创建一个类,名字叫TimeLogin,意思是运行时间。让他实现MethodInterceptor这个接口
注意啦,这里有坑,就是要选择的是上面这个aop的接口。
然后实现这个接口的方法
在此方法中,填写如下代码
//这个类是记录方法运行时间的
public class TimeLogin implements MethodInterceptor {
@Override
//methodInvocation:这个参数指的是代理对象的包装类,用来获取代理对象/目标对象/方法的参数等等...
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//调用代理对象原来的方法并返回方法的返回值
Object proceed = methodInvocation.proceed();
//返回这个返回值,整个代理的过程就结束了
return proceed;
}
即下图:
配置好之后,接下来就可以写你需要创建一个日志记录器来记录方法的运行时间:
//比如我们要记录方法运行的时间,并把它保存到日志记录器里,所以创建一个日志记录器
private static Logger logger = Logger.getLogger(TimeLogin.class);
然后开始在invoke方法中添加增强功能的代码:
//这个类是记录方法运行时间的
public class TimeLogin implements MethodInterceptor {
//比如我们要记录方法运行的时间,并把它保存到本地日志文件里,所以创建一个日志记录器
private static Logger logger = Logger.getLogger(TimeLogin.class);
@Override
//methodInvocation:这个指的是代理对象的包装类,用来获取代理对象/目标对象/方法的参数等等...
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//首先在方法前,添加一个记录时间的功能
long beforeTime = System.currentTimeMillis();
//调用代理对象原来的方法并返回方法的返回值
Object proceed = methodInvocation.proceed();
//然后在方法之后也添加一个记录时间的功能
long afterTime = System.currentTimeMillis();
//最后求出差值便是方法使用的时间
long useTime = afterTime - beforeTime;
//打印一下这个时间:
//methodInvocation.getMethod().getName() 代表哪一个方法名,修饰一下,即什么方法运行了多长时间
//如果在详细一点的话,需要把类名也加上,那么methodInvocation.getThis().getClass().getName() 即什么类的什么方法运行了多长时间
System.out.println(methodInvocation.getThis().getClass().getName()+"类的"+methodInvocation.getMethod().getName()+"方法使用了" + useTime+"毫秒");
//当然上方只是打印一下,那我们还需要做日志的记录到文件中
logger.info(methodInvocation.getThis().getClass().getName()+"类的"+methodInvocation.getMethod().getName()+"方法使用了" + useTime+"毫秒");
//返回这个返回值,整个代理的过程就结束了
return proceed;
}
}
然后我们去配一下log4j.properties文件,需要将这段代码添加进去,修改保存位置即可
log4j.rootLogger=DEBUG,A1,file
log4j.logger.org.apache=DEBUG
log4j.appender.A1.Target=System.err
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.File=G:/mylog.log
log4j.appender.file.layout.ConversionPattern=%p\t%d{ISO8601}\t%r\t%c\t[%t]\t%m%n
最好这里先把他的日志级别设置的小一点,我们把他改成INFO级别
OK,设置好之后,接下来就创建一个测试类,并填写如下代码:
import com.beijihou.spring.a_proxy.CustomerService;
import com.beijihou.spring.a_proxy.ProductService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
//spring里面的注解
@RunWith(SpringJUnit4ClassRunner.class)
//去读取配置文件
@ContextConfiguration(locations = "classpath:applicationConText.xml")
public class SpringTest {
@Autowired //关联配置文件中ID为customerService的bean
private CustomerService customerService;
@Autowired //关联配置文件中ID为productService的bean
private ProductService productService;
@Test
public void test(){
//调用各自的方法
customerService.shopping();
productService.sellMilk();
}
}
我们之前写的记录运行时间的功能并没有生效
那我们去G盘里看下文件有没有生成:
文件居然生成了,
这是什么原因呢?仔细想想是不是少了点什么东西?
我们的AOP三步走才走了两步吧 ,把最关键的一步忘记了,那就是配置切入点和切面:
第三步:配置切入点和切面
我们进入applicationContext.xlm这个配置文件中发现:
你的目标对象有了,增强代码也有了,但是,增强代码并没有作用到你的目标对象上,所以这时,切入点和切面就来了,为了获得你目标对象中的方法,并拦截这些方法,拦截之后然后把通知代码(即增强代码)作用到这些方法上即可。进入到applicationcontext.xlm中进行如下配置:
这张图就可以很清晰的看出来他们之间的关系:
切面的作用是将切点和通知关联起来。
切点的作用是关联目标对象,确认目标对象,拦截其方法
然后他们之间就产生了一种不可言语的关系…
产生关系之后呢,我们就可以重新快乐的跑一下了:
ok 成功的将记录运行方法运行时间的这个新功能鬼畜的实现了,而且最关键的是,没有代码的侵入行为,原来的代码依旧是原来的代码,一个字都没碰
记住:玩转SpringAOP,牢记这三点即可
至于AspectJ 今天是没时间出了,只能改天吧。
最后,如果有哪里看不懂的或者有什么问题就留下评论吧,我特别喜欢和大佬们在学术上交流、一起探讨一些问题,而且是越激烈,我越兴奋哦