轻松理解AOP思想(面向切面编程)

从功能的角度来定义的,从本质意义上来讲,Spring是一个库,一个Java库,所以我个人觉得应该这样回答Spring是什么:Spring是一个库,它的功能是提供了一个软件框架,这个框架目的是使软件之间的逻辑更加清晰,配置更灵活,实现这个目的的手段使用AOP和IoC,而AOP和IoC是一种思想,是一种什么样的思想呢,等下细说,先说AOP在Java里是利用反射机制实现(你也可以认为是动态代理,不过动态代理也是反射机制实现的,所以还是先不要管动态代理,我们这里化繁为简,不让它干扰咱们对AOP的理解),如何使用AOP呢,很简单滴,等下介绍。

下面先说AOP是什么样的思想,我们一步一步慢慢来,先看一下传统程序的流程,比如银行系统会有一个取款流程
在这里插入图片描述
我们可以把方框里的流程合为一个,另外系统还会有一个查询余额流程,我们先把这两个流程放到一起:
在这里插入图片描述
有没有发现,这个两者有一个相同的验证流程,我们先把它们圈起来再说下一步:
在这里插入图片描述
有没有想过可以把这个验证用户的代码是提取出来,不放到主流程里去呢,这就是AOP的作用了,有了AOP,你写代码时不要把这个验证用户步骤写进去,即完全不考虑验证用户,你写完之后,在另我一个地方,写好验证用户的代码,然后告诉Spring你要把这段代码加到哪几个地方,Spring就会帮你加过去,而不要你自己Copy过去,这里还是两个地方,如果你有多个控制流呢,这个写代码的方法可以大大减少你的时间,不过AOP的目的不是这样,这只是一个“副作用”,真正目的是,你写代码的时候,事先只需考虑主流程,而不用考虑那些不重要的流程,懂C的都知道,良好的风格要求在函数起始处验证参数,如果在C上可以用AOP,就可以先不管校验参数的问题,事后使用AOP就可以隔山打牛的给所有函数一次性加入校验代码,而你只需要写一次校验代码。不知道C的没关系,举一个通用的例子,经常在debug的时候要打log吧,你也可以写好主要代码之后,把打log的代码写到另一个单独的地方,然后命令AOP把你的代码加过去,注意AOP不会把代码加到源文件里,但是它会正确的影响最终的机器代码。

现在大概明白了AOP了吗,我们来理一下头绪,上面那个方框像不像个平面,你可以把它当块板子,这块板子插入一些控制流程,这块板子就可以当成是AOP中的一个切面。所以AOP的本质是在一系列纵向的控制流程中,把那些相同的子流程提取成一个横向的面,这句话应该好理解吧,我们把纵向流程画成一条直线,然把相同的部分以绿色突出,如下图左,而AOP相当于把相同的地方连一条横线,如下图右,这个图没画好,大家明白意思就行。
在这里插入图片描述在这里插入图片描述
这个验证用户这个子流程就成了一个条线,也可以理解成一个切面,aspect的意思我认为是方面,你用什么实物去类比,只要你能理解都可以。这里的切面只插了两三个流程,如果其它流程也需要这个子流程,也可以插到其它地方去。

在传统的编写业务逻辑处理代码时,我们通常会习惯性地做几件事情:日志记录、事务控制及权限控制等,然后才是编写核心的业务逻辑处理代码。当代码编写完成回头再看时,不禁发现,扬扬洒洒上百行代码中,真正用于核心业务逻辑处理才那么几行,如图6-4所示。方法复方法,类复类,就这样子带着无可奈何遗憾地度过了多少个春秋。这倒也罢,倘若到了项目的尾声,突然决定在权限控制上需要进行大的变动时,成千上万个方法又得一一"登门拜访",痛苦"雪上加霜"。
在这里插入图片描述
如果能把图6-4中众多方法中的所有共有代码全部抽取出来,放置到某个地方集中管理,然后在具体运行时,再由容器动态织入这些共有代码的话,最起码可以解决两个问题:

Java EE程序员在编写具体的业务逻辑处理方法时,只需关心核心的业务逻辑处理,既提高了工作效率,又使代码变更简洁优雅。

在日后的维护中由于业务逻辑代码与共有代码分开存放,而且共有代码是集中存放的,因此使维护工作变得简单轻松。

面向切面编程AOP技术就是为解决这个问题而诞生的,切面就是横切面,如图6-5所示,代表的是一个普遍存在的共有功能,例如,日志切面、权限切面及事务切面等。
在这里插入图片描述
下面我们以用户管理业务逻辑组件UserService的AOP实现过程(见图6-6)为例,深度剖析一下AOP技术的实现原理。AOP技术是建立在Java语言的反射机制与动态代理机制之上的。业务逻辑组件在运行过程中,AOP容器会动态创建一个代理对象供使用者调用,该代理对象已经按Java EE程序员的意图将切面成功切入到目标方法的连接点上,从而使切面的功能与业务逻辑的功能同时得以执行。从原理上讲,调用者直接调用的其实是AOP容器动态生成的代理对象,再由代理对象调用目标对象完成原始的业务逻辑处理,而代理对象则已经将切面与业务逻辑方法进行了合成。
在这里插入图片描述
白话AOP的概念
你是个公子哥,天天就是衣来伸手,饭来张口,整天只知道玩一件事
每天一睁眼,就光想着吃完饭就去玩(你必须要做的事)
在玩之前还需要穿衣服、穿鞋子、叠好被子、做饭等等
但是你只想吃饭然后玩,怎么办呢?
这些事情通通交给别人去干
在你走到饭桌吃饭之前
有一个专门的仆人帮你穿衣服、帮你穿鞋子、帮你叠好被子、帮你做饭
你就直接开始吃饭、去玩
当你玩完回来
一系列仆人又开始帮你干这个干那个
这一天就结束了

AOP的好处就是你只需要干你的事,其它事情别人帮你干
也许有一天,你不想穿衣服,那么你把相应仆人解雇
也许有一天,出门之前你还想带点钱,那么再雇一个仆人专门帮你干取钱的活。
这就是AOP,每个人各司其职,灵活组合,达到一种可配置的、可插拔的程序结构。
AOP最大的用途就在于提供了事务管理的能力:
访问数据库,不想管事务(太烦),所以Spring框架在你访问数据库之前,自动帮你开启事务,当你访问数据库结束之后,自动帮你提交/回滚事务!

现将图6-6中涉及到的一些概念解释如下。

切面(Aspect):其实就是共有功能的实现。如日志切面、权限切面、事务切面等。在实际应用中通常是一个存放共有功能实现的普通Java类,之所以能被AOP容器识别成切面,是在配置中指定的。

通知(Advice):是切面的具体实现。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际应用中通常是切面类中的一个方法,具体属于哪类通知,同样是在配置中指定的。

连接点(Joinpoint):就是程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出或字段修改等,但Spring只支持方法级的连接点。

切入点(Pointcut):用于定义通知应该切入到哪些连接点上。不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。

目标对象(Target):就是那些即将切入切面的对象,也就是那些被通知的对象。这些对象中已经只剩下干干净净的核心业务逻辑代码了,所有的共有功能代码等待AOP容器的切入。

代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象的核心业务逻辑功能加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。

织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期,当然不同的发生点有着不同的前提条件。譬如发生在编译期的话,就要求有一个支持这种AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;只有发生在运行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。

下面给出一个aop的简单例子,项目结构如下
在这里插入图片描述
Teacher.java

package com.sise.aop;

public interface Teacher {
    public void teach();
}

TeacherImpl.java

package com.sise.aopimpl;

import com.sise.aop.Teacher;

public class TeacherImpl implements Teacher{

	@Override
	public void teach() {
		// TODO Auto-generated method stub
		System.out.println("教师开始上课");
	}
	
}

Student.java

package com.sise.aopimpl;

public class Student {
	public Student() {

    }
    public void seats()
    {
        System.out.println("学生回到教室");   
    }
    public void sayhello()
    {
        System.out.println("学生向老师问好");
    }
    public void ask()
    {
        System.out.println("学生上课提问");
    }
    public void endclass()
    {
        System.out.println("学生下课了");
    }
}

Test.java

package com.sise.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.sise.aopimpl.TeacherImpl;

public class Test {

	public static void main(String[] args) {
		ApplicationContext apc=new ClassPathXmlApplicationContext("beans.xml");
        TeacherImpl teacher=(TeacherImpl) apc.getBean("TeacherImpl");
        teacher.teach();
	}
}

beans.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:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/aop   
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
    xmlns:aop="http://www.springframework.org/schema/aop">
    
    <!-- 在测试代码中通过getBean()被调用获取此装配类 -->
    <bean id="TeacherImpl" class="com.sise.aopimpl.TeacherImpl"/>
    
    <!-- 需要插入到TeacherImpl这个类的切面的类  -->  
    <bean id="Student" class="com.sise.aopimpl.Student" />
    
    <aop:config proxy-target-class="true">
    	<!-- 通知(Advice),在切面对象中,描述了切面要完成的工作和何时需要执行这个工作,切面中的每个方向称之为通知 -->
        <aop:aspect ref="Student">

            <!-- 将student的seats方法插入到TeacherImpl的teach()方法之前进行执行 -->
            <!-- 切入点(Pointcut),定义了切面发生的地点,如切面在哪个方法的前或后 -->
            <aop:before pointcut="execution(* com.sise.aopimpl.TeacherImpl.teach(..))" method="seats"/>  
            <aop:before pointcut="execution(* com.sise.aopimpl.TeacherImpl.teach(..))" method="sayhello"/>  
           
            <!-- 将student的ask方法插入到TeacherImpl的teach()方法执行完之后进行执行 -->
            <aop:after-returning pointcut="execution(* com.sise.aopimpl.TeacherImpl.teach(..))" method="ask"/>
           
            <!-- 将student的ask方法插入到TeacherImpl的teach()方法抛出异常之后进行执行 -->  
            <aop:after-throwing pointcut="execution(* com.sise.aopimpl.TeacherImpl.teach(..))" method="endclass"/>  
        </aop:aspect>  
    </aop:config>
      
</beans>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
  <display-name>AOP2</display-name>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  
  <servlet>
    <servlet-name>AOP2</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>
 
  <servlet-mapping>
    <servlet-name>AOP2</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

最终显示结果如下:
在这里插入图片描述

第二个aop例子,代码结构如下
在这里插入图片描述
Instrument.java

package com.tutorialspoint;

public abstract class Instrument {

	public Instrument() {
		super();
	}
	
	abstract void play();
	
}

Performer.java

package com.tutorialspoint;

public class Performer {
	
	private Instrument ins;
	
	public Performer(Instrument ins) {
		this.ins = ins;
	}
	
	public void play() {
		ins.play();
	}
}

Record.java

package com.tutorialspoint;

import java.text.SimpleDateFormat;
import java.util.Date;

public class Record {

	private SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
	
	public void starttime() {
		System.out.println(sdf.format(new Date()));
	}
	
	public void endtime() {
		System.out.println(sdf.format(new Date()));
	}
}

Violin.java

package com.tutorialspoint;

public class Violin extends Instrument {

	public Violin() {
		super();
	}

	@Override
	void play() {
		System.out.println("Violin Music!");
	}
	
	
}

MainApp.java

package com.tutorialspoint;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
	
	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
		
		Performer performer = (Performer)ctx.getBean("performer");
		performer.play();
	}

}

beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="performer" class="com.tutorialspoint.Performer">
        <constructor-arg ref="violin" />
    </bean> 

    <bean id="violin" class="com.tutorialspoint.Violin"></bean>
    <bean id="record" class="com.tutorialspoint.Record"></bean>
    
    <aop:config>
        <aop:aspect ref="record">
            <aop:pointcut expression="execution(* com.tutorialspoint.Performer.play(..))" id="play"/>
            <aop:before method="starttime" pointcut-ref="play"/>
            <aop:after method="endtime" pointcut-ref="play"/>
        </aop:aspect>
    </aop:config>
    
</beans>
   

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
  <display-name>AOP2</display-name>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  
  <servlet>
  	<servlet-name>AOP2</servlet-name>
  	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>
  
  <servlet-mapping>
  	<servlet-name>AOP2</servlet-name>
  	<url-pattern>/</url-pattern>
  </servlet-mapping>
  
</web-app>

结果如下:
在这里插入图片描述

参考一:https://my.oschina.net/yanquan345/blog/203415
参考二:http://blog.csdn.net/Intlgj/article/details/5671248

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值