日志记录,性能统计,安全控制,事务处理,异常处理等等
.......................................................
.......................................................
关于AOP,一搜可以搜出一大堆相关资料和博文,都非常精彩,本篇我就不放过多的理论性文字了,直接结合代码一步步实现Spring 的AOP编程!
讲的时候,可能有些地方专业术语说的不是太专业、不太到位,但是对于一个初次接触AOP的我们来说,绝对是一个看了,你不会再问什么是AOP的小白了。
本篇案列立足于SSM整合后的web项目,以本地Maven仓库作为Jar包管理,开始之前先放两张图,文章最后,会把整个项目的环境上传到我的资源以供各位下载参考。
A:整个项目的目录节结构图:
B:Spring(AOP)依赖的相关的Jar包:
圈红钱的两个jar包,如果在你的项目中是手动导入的话,这两个需要单独下载!(我的统一由本地Maven管理,下载的事就交给配置好的Pom.xml文件了)
下载地址-----> 我的资源(最少1分,没分的可以去官网下载)
准备开车了,请各位一定要坐稳了!
一、包
pojo : 下面放类,什么时间类啊,日志类啊,结合本篇就是放大家都公用的部分的封装类,大家是谁,大家在本篇中
是包Impl下的各个实现类
service: 下面放我们自定义的接口,搞一些方法,交给Impl包下面的类来实现
Impl : 下面放接口的实现类
二、定义一个接口,AOPTest,申明两个方法
AOPTest.java
package com.appleyk.service;
public interface AOPTest {
void sayHello();
void doSomething();
}
三、定义接口AOPTest的两个实现类,AOPTestImpl1 和 AOPTestImpl2
AOPTestImpl1 .java
package com.appleyk.service.Impl;
import com.appleyk.service.AOPTest;
public class AOPTestImpl1 implements AOPTest{
@Override
public void sayHello() {
TimeBefor();
System.out.println("实现类1-->实现sayHello !");
TimeAfter();
}
@Override
public void doSomething() {
TimeBefor();
System.out.println("实现类1-->实现doSomething !");
TimeAfter();
}
private void TimeBefor(){
//获得自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为long
System.out.println("前置时间:"+System.currentTimeMillis());
}
private void TimeAfter(){
//获得自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为long
System.out.println("后置时间:"+System.currentTimeMillis());
}
}
AOPTestImpl2 .java(和实现类1的代码一样)
package com.appleyk.service.Impl;
import com.appleyk.service.AOPTest;
public class AOPTestImpl2 implements AOPTest{
@Override
public void sayHello() {
TimeBefor();
System.out.println("实现类2-->实现sayHello !");
TimeAfter();
}
@Override
public void doSomething() {
TimeBefor();
System.out.println("实现类2-->实现doSomething !");
TimeAfter();
}
private void TimeBefor(){
//获得自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为long
System.out.println("前置时间:"+System.currentTimeMillis());
}
private void TimeAfter(){
//获得自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为long
System.out.println("后置时间:"+System.currentTimeMillis());
}
}
为什么会在两个实现类里面定义时间的前置打印和后置打印方法,而且这两个部分还是重复的功能(注意,目前这些功能都是人为手动的增加的)。
这里我想插一句,这种在目标方法上加扩展功能的方式类似于Python的装饰函数,有兴趣的朋友可以看下我的Python学习笔记,里面有讲到这种用法,当然,原理千差万别,但是效果几乎是翻版!
(1)目的是为了在目标方法执行前后打印时间戳,根据前后时间戳,我们可以一目了然的看出来目标方法的执行时间效率
(2)这种方式显然有些冗余,类似于下面的一张图
二三步骤完成后,结构图如下:
applicationContext.xml
bean id),根据bean id获得对应的代理类(一个bean id 对应一个实现类),接口是没有实例的,但是转为为代理类
后可以有实例,这之间涉及到一个类型转化。
五、方法测试
TestAOP.java
package com.appleyk.aop.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.appleyk.service.AOPTest;
public class TestAOP {
@Test
public void Test1(){
try{
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
//通过spring上下文对象拿到spring容器中的bean,根据id获得相应的接口的实现类的代理类,注意需要类型转换
AOPTest t1 = (AOPTest)context.getBean("aopTestImpl1");//这里填bean ID
AOPTest t2 = (AOPTest)context.getBean("aopTestImpl2");
System.out.println("实现类Impl1效果如下:");
System.out.println("--------------");
t1.sayHello();
System.out.println("--------------");
t1.doSomething();
System.out.println("");
System.out.println("实现类Impl2效果如下:");
System.out.println("--------------");
t2.sayHello();
System.out.println("--------------");
t2.doSomething();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
因为我们给实现类1 和 实现类2 的两个目标方法都加了执行时间记录功能,因此,执行测试方法的时候,会看到如下
效果:
六、使用AOP编程,实现目标方法的前置和后置时间打印
上述的实现过程太过繁琐,项目的耦合度太高,为了解耦,我们想把实现类1的时间打印方法和实现类2中的时间打
印方法一刀切掉,重新整一个,反正功能都一样,占着地方,不如搞成一个,看着也舒服,维护起来也方便。
为了对比,我们重新再定义两个实现类,AOPTestImplA 和 AOPTestImplB
AOPTestImplA .java
package com.appleyk.service.Impl;
import com.appleyk.service.AOPTest;
public class AOPTestImplA implements AOPTest{
@Override
public void sayHello() {
System.out.println("实现类A-->实现sayHello !");
}
@Override
public void doSomething() {
System.out.println("实现类A-->实现doSomething !");
}
}
有了这两个实现类,我们还差一个封装类(暂且先别考虑,切面编程怎么会和下面的封装类扯到一块)
PrintTime.java
package com.appleyk.pojo;
public class PrintTime {
//前置通知 --> 目标方法执行前 打印内容
public void TimeBefore(){
System.out.println("前置时间:"+System.currentTimeMillis());
}
//后置通知 --> 目标方法执行后 打印内容
public void TimeAfter(){
System.out.println("后置时间:"+System.currentTimeMillis());
}
}
这个PrintTime类中的方法,完全是照搬之前的写法,一点都没有动!
下面,我们基于XML的配置,来实现AOP,让方法doSomething在执行的时候,打印前后时间记录,而方法sayHello
在执行的时候则没有该项功能。
重新配置applicationContext.xml如下:
applicationContext.xml
备注:上图中,后置通知,应该是
我们先来演示一下,AOP的前置和后置通知,稍后再演示环绕通知,在原有的测试类中增加一个方法Test2如下:
TestAOP.java
package com.appleyk.aop.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.appleyk.service.AOPTest;
public class TestAOP {
//Test1这里就不贴出来了
@Test
public void Test2(){
try{
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
//通过spring上下文对象拿到spring容器中的bean,根据id获得相应的接口的实现类的代理类,注意需要类型转换
AOPTest t1 = (AOPTest)context.getBean("aopTestImpl1");//这里填bean ID
AOPTest t2 = (AOPTest)context.getBean("aopTestImpl2");
System.out.println("实现类ImplA效果如下:");
System.out.println("--------------");
t1.sayHello();
System.out.println("--------------");
t1.doSomething();
System.out.println("");
System.out.println("实现类ImplB效果如下:");
System.out.println("--------------");
t2.sayHello();
System.out.println("--------------");
t2.doSomething();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
如果,被作为切入点的方法是一个查询语句,那么我们就可以在这个方法执行后,看到这个查询方法的耗时情况
如果,既想切sayHello 又想 切doSomething两个方法,只需要将XML中的切入点的表达式改成如下即可:
效果就不演示了,我们接下来看一下什么是环绕通知
演示AOP环绕通知,需要改两个地方,一个是对应的封装类PrintTime,一个就是我们的XML配置文件
PrintTime.java
package com.appleyk.pojo;
import org.aspectj.lang.ProceedingJoinPoint;
public class PrintTime {
//前置通知 --> 目标方法执行前 打印内容
public void TimeBefore(){
System.out.println("前置时间:"+System.currentTimeMillis());
}
//后置通知 --> 目标方法执行后 打印内容
public void TimeAfter(){
System.out.println("后置时间:"+System.currentTimeMillis());
}
//环绕通知
public void TimeAround(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("开始时间:"+System.currentTimeMillis());
joinPoint.proceed();//注意这个,很关键,没有这个,目标函数不会执行,是的,不会执行!你没看错!
System.out.println("结束时间:"+System.currentTimeMillis());
}
}
测试类TestAOP不用改,我们直接执行测试方法Test2,效果如下:
如果我们在PrintTime.java中注释掉下面这行代码,会怎么样呢?
joinPoint.proceed();
注释的就不贴了,直接上最后的执行结果:
joinPoint:连接点。在Spring 中,就是被拦截到的方法,调用一下proceed方法,目标函数才能被执行!当然,
连接点还可以是字段或者构造函数
当然,我们还可以再增加一个横切关注点
添加的方式和打印时间一样,为了省时间,这里我直接贴出代码和xml配置文件,就不在做过多的说明了
WriteLog.java
package com.appleyk.pojo;
public class WriteLog {
//前置通知 --> 目标方法执行前 打印内容
public void LogBefore(){
System.out.println("日志记录开始:xxxxx");
}
//后置通知 --> 目标方法执行后 打印内容
public void LogAfter(){
System.out.println("日志记录结束:ooooo");
}
}
applicationContext.xml
测试类TestAOP不用动,注意,上面xml增加的第二个横切关注点(日志打印),切的是say*开头的方法,因此,最终
看到的结果如下:
本篇只是简单的基于XML的配置方式,浅显的实现AOP编程(深入的,我还在学习中,只能先这样了)!
理论性的文字读起来不好理解,不如亲自敲一遍代码,简单的一实现,就豁然开朗了。
最后附上整个项目的环境---->AOP编程简单实例演示
如果你下载下来整个项目的话,想要跑起来,你要:
(1)设置项目中的本地Maven的路径指向
(2)安装ssm-parent到本地仓库
(3)删除ssm-web-test(指向的Location可能和你的电脑磁盘路径不一致)
(4)重新导入import 已经存在的 Maven Project,找到ssm-web-test(这个在资源里的ssm-web下面)