Spring AOP-->面向切面编程简单理解和简单使用

AOP

Aspect Oriented Programming(AOP) 英译中:面向切面(Aspect)编程

AOP主要功能

日志记录,性能统计,安全控制,事务处理,异常处理等等


.......................................................

.......................................................

关于AOP,一搜可以搜出一大堆相关资料和博文,都非常精彩,本篇我就不放过多的理论性文字了,直接结合代码一步步实现Spring 的AOP编程!


讲的时候,可能有些地方专业术语说的不是太专业、不太到位,但是对于一个初次接触AOP的我们来说,绝对是一个看了,你不会再问什么是AOP的小白了。


本篇案列立足于SSM整合后的web项目,以本地Maven仓库作为Jar包管理,开始之前先放两张图,文章最后,会把整个项目的环境上传到我的资源以供各位下载参考。


A:整个项目的目录节结构图:

Spring AOP-->面向切面编程简单理解和简单使用_第1张图片




B:Spring(AOP)依赖的相关的Jar包:

Spring AOP-->面向切面编程简单理解和简单使用_第2张图片

圈红钱的两个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)这种方式显然有些冗余,类似于下面的一张图


Spring AOP-->面向切面编程简单理解和简单使用_第3张图片



二三步骤完成后,结构图如下:


Spring AOP-->面向切面编程简单理解和简单使用_第4张图片



四、applicationContext.xml配置(暂时还未涉及到切面编程,纯手动实现

applicationContext.xml




	
	
	
	

后面,测试的时候会利用Spring上下文对象(xxxxContext),拿到Spring ICO容器中的bean(方法:getbean,参数:

bean id),根据bean id获得对应的代理类(一个bean id 对应一个实现类),接口是没有实例的,但是转为为代理类

后可以有实例,这之间涉及到一个类型转化。


五、方法测试


Spring AOP-->面向切面编程简单理解和简单使用_第5张图片

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 的两个目标方法都加了执行时间记录功能,因此,执行测试方法的时候,会看到如下

效果:

Spring AOP-->面向切面编程简单理解和简单使用_第6张图片


六、使用AOP编程,实现目标方法的前置和后置时间打印


上述的实现过程太过繁琐,项目的耦合度太高,为了解耦,我们想把实现类1的时间打印方法和实现类2中的时间打

印方法一刀切掉,重新整一个,反正功能都一样,占着地方,不如搞成一个,看着也舒服,维护起来也方便。


Spring AOP-->面向切面编程简单理解和简单使用_第7张图片

为了对比,我们重新再定义两个实现类,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 !");
		
	}
  
}

AOPTestImplB的和A一样,demo就不贴出来了!


有了这两个实现类,我们还差一个封装类(暂且先别考虑,切面编程怎么会和下面的封装类扯到一块

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类中的方法,完全是照搬之前的写法,一点都没有动!


Spring AOP-->面向切面编程简单理解和简单使用_第8张图片


下面,我们基于XML的配置,来实现AOP,让方法doSomething在执行的时候,打印前后时间记录,而方法sayHello

在执行的时候则没有该项功能。


重新配置applicationContext.xml如下:

applicationContext.xml




	
	
	
	
	
	
	  
	     
	    
	    
	    
	    
	  
	

Spring AOP-->面向切面编程简单理解和简单使用_第9张图片


备注:上图中,后置通知,应该是,因为图是改不了了,特此说明!


我们先来演示一下,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();
	}
       }
}

执行Test2测试方法,看到的效果如下:

Spring AOP-->面向切面编程简单理解和简单使用_第10张图片

如果,被作为切入点的方法是一个查询语句,那么我们就可以在这个方法执行后,看到这个查询方法的耗时情况

如果,既想切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());
   }
}

applicationContext.xml


	  
	     
	    
	    
	    
	    
	    
	    
	  
	


测试类TestAOP不用改,我们直接执行测试方法Test2,效果如下:


Spring AOP-->面向切面编程简单理解和简单使用_第11张图片


如果我们在PrintTime.java中注释掉下面这行代码,会怎么样呢?

joinPoint.proceed();


注释的就不贴了,直接上最后的执行结果:


Spring AOP-->面向切面编程简单理解和简单使用_第12张图片


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*开头的方法,因此,最终

看到的结果如下:


Spring AOP-->面向切面编程简单理解和简单使用_第13张图片



本篇只是简单的基于XML的配置方式,浅显的实现AOP编程(深入的,我还在学习中,只能先这样了)!


理论性的文字读起来不好理解,不如亲自敲一遍代码,简单的一实现,就豁然开朗了。



最后附上整个项目的环境---->AOP编程简单实例演示


如果你下载下来整个项目的话,想要跑起来,你要:

(1)设置项目中的本地Maven的路径指向

(2)安装ssm-parent到本地仓库

(3)删除ssm-web-test(指向的Location可能和你的电脑磁盘路径不一致)

(4)重新导入import 已经存在的  Maven Project,找到ssm-web-test(这个在资源里的ssm-web下面)
















你可能感兴趣的:(Java,Web)