从功能的角度来定义的,从本质意义上来讲,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