OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。对于“雇员”这样一个业务实体进行封装,自然是OOP/OOD的任务,我们可以为其建立一个“Employee”类,并将“雇员”相关的属性和行为封装其中。
而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。同样,对于“权限检查”这一动作片断进行划分,则是AOP的目标领域。而通过OOD/OOP对一个动作进行封装,则有点不伦不类。
这两种设计思想在目标上有着本质的差异,换言之,OOD/OOP面向名词领域,AOP面向动词领域。
AOP:Aspect Oriented Programming的简写,译为面向切面编程。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。让你更多地关注自己本身的业务,而不去想一些其他的事情,比如:安全,事务,日志等。
辅助理解AOP:
spring用代理类包裹切面,把他们织入到spring管理的bean中。也就是说代理类伪装成目标类,它会截取对目标类中方法的调用,让调用者对目标类的调用都先变成调用代理类,代理类中就先执行了切面,再把调用转发给真正的目标类bean。
现在可以想一想,怎么搞出来这个“伪装类”,才不会被调用者发现(通过JVM检查,JAVA是强类型检查,哪里都要检查类型)。
1、实现和目标类相同的接口,我也实现和你一样的接口,反正上层都是接口级别的调用,这样我就伪装成和目标类一样的类(实现了同一接口,咱是兄弟了),也就逃过了类型检查,到java运行期的时候,利用多态的后期绑定(所以spring采用运行时),伪装类(代理类)就变成了接口的真正实现,而他里面包裹了真实的那个目标类,最后实现具体功能的还是目标类,只不过伪装类在之前干了点事情(写日志,安全检查,事物等)。
这就好比:某人A让你办件事,每次这个时候你弟弟B2就会先出来,当然A分不出来,以为是你B1,你弟弟B2虽然办不了这事,但是他知道你能办,所以就答应下来了,并且收了点礼物(写日志等),收完礼物后得给人家把事办了,所以你弟弟又找你这个哥哥来了,最后实际办事的人还是你自己。但是你自己并不知道你弟弟收礼物这件事,你只是专心把这件事情做好。
顺着这个思路,要是本身这个类就没实现一个接口,计划生育不让你有兄弟,你是独生子女,怎么伪装我?我B1压根没有双胞胎弟弟。那么就用第2种代理方式,创建一个目标类的子类。B1生个儿子C,让儿子伪装我 。
2、继承目标类,生成子类调用。用子类来做为伪装类(代理类),当然这样也能逃过JVM的强类型检查。我继承的嘛,当然查不出来了。子类重写了目标类(父类)的所有方法,当然在这些重写的方法中,不仅实现了目标类的功能,还在这些功能之上实现了一些其他功能(写日志,安全检查,事物等)。
这就好比:儿子先从爸爸那把本事都学会了,所有人都找儿子办事,但是儿子每次办和爸爸同样的事之前,都要收点小礼物(写日志等),然后才去办真正的事。当然爸爸是不知道儿子这么干的了。这里就有件事情要说,某些本事是爸爸独有的(final的),儿子学不了,学不了就办不了这件事,办不了这个事情,自然就不能收人家礼了。
前一种兄弟模式,spring会使用JDK的java.lang.reflect.Proxy类,它允许spring动态生成一个新类来实现必要的接口,织入通知,并且把对这些接口的任何调用都转发到目标类。
后一种父子模式,spring使用CGLIB库生成目标类的一个子类,在创建这个子类的时候,spring织入通知,并且把对这个子类的调用委托到目标类。
相比之下,兄弟模式好些,他能更好的实现松耦合,尤其在今天都高喊着面向接口编程的情况下,父子模式只是在没有实现接口的时候也能织入通知,应当作为一种备选而不是首选。
一、AOP相关术语:
1、通知(Advice)
就是你想要的功能,也就是上面说的 安全,事务,日志等。你先定义好,然后在想用的地方用一下。 通知定义了切面是什么以及何时使用,描述了切面要完成的工作和何时需要执行这个动作。通知是织入到切入点上的一段程序代码。通知既包含了用于添加到目标连接点上的一段执行逻辑,又包含了用于定位连接点的方位信息,所以spring所提供的增强接口都是带方位名的:BeforeAdvice(方法调用前的位置)、AfterReturningAdvice(访问返回后的位置)、ThrowsAdvice等。
2、连接点(Joinpoint)
就是spring允许你使用通知的地方,程序使用通知的一个“时机”,这些“时机”就是连接点,例如方法被调用时、异常被抛出时等等。spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时织入。
3、切入点(Pointcut)
就是在这些允许使用通知的连接点中选择实际使用通知的地方,spring中允许我们方便的用正则表达式来指定。
4、切面(Aspect)
就是通知和切入点的结合,通知和切入点共同组成了切面。Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。
5、引入(Introduction)
就是向现有的类添加新的方法和属性,就是把切面(也就是新方法属性:通知定义的)用到目标类中。
6、目标(Target)
就是引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑。它在毫不知情的情况下,被织入切面,而自己专注于业务本身的逻辑。如果没有AOP,那么它的逻辑将要交叉别的事务逻辑,有了AOP之后它可以只关注自己要做的事。
7、代理(proxy)
应用通知的对象,详细内容参见设计模式里面的代理模式。
8、织入(Weaving)
把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器;
(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码;
(3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理是使用JDK的动态代理技术。
AOP的工作重心,主要包括两个:
1>如何通过切入点和通知定位到连接点上;
2>如何在通知中编写切面的代码。
二、具体实现:
Spring支持五种类型的通知:
1)Before(前) org.apringframework.aop.MethodBeforeAdvice;
2)After-returning(返回后) org.springframework.aop.AfterReturningAdvice;
3)After-throwing(抛出后) org.springframework.aop.ThrowsAdvice;
4)Arround(周围) org.aopaliance.intercept.MethodInterceptor。由AOP Alliance中的接口定义的而非Spring,周围通知相当于前通知、返回后通知、抛出后通知的结合;
5)Introduction(引入) org.springframework.aop.IntroductionInterceptor。
步骤可分为:
1、创建通知:实现这几个接口,把其中的方法实现;
2、定义切入点和通知者:在spring配制文件中配置这些信息
3、使用ProxyFactoryBean来生成代理
Spring提供了4种实现AOP的方式:
1、经典的基于代理的AOP
jar包:spring-aop-2.5.1.jar,aopalliance-1.0.jar
接口:
1
2
3
4
5
|
package
com.yourcompany.spring;
public
interface
Sleepable {
public
void
sleep();
}
|
目标:
1
2
3
4
5
6
7
|
package
com.yourcompany.spring;
public
class
Human
implements
Sleepable {
public
void
sleep() {
System.out.println(
"人类睡觉..."
);
}
}
|
通知:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package
com.yourcompany.spring;
import
java.lang.reflect.Method;
import
org.springframework.aop.AfterReturningAdvice;
import
org.springframework.aop.MethodBeforeAdvice;
public
class
SleepHelper
implements
MethodBeforeAdvice,AfterReturningAdvice{
public
void
before(Method arg0, Object[] arg1, Object arg2)
throws
Throwable {
System.out.println(
"睡觉前要脱衣服!"
);
}
public
void
afterReturning(Object arg0, Method arg1, Object[] arg2,Object arg3)
throws
Throwable {
System.out.println(
"起床后要穿衣服!"
);
}
}
|
applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
<!--?xml version=
"1.0"
encoding=
"UTF-8"
?-->
<beans xmlns=
"http://www.springframework.org/schema/beans"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemalocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"
>
<!--定义目标-->
<bean
class
=
"com.yourcompany.spring.Human"
id=
"human"
></bean>
<!--定义通知-->
<bean
class
=
"com.yourcompany.spring.SleepHelper"
id=
"sleepHelper"
></bean>
<!--定义切入点-->
<bean
class
=
"org.springframework.aop.support.JdkRegexpMethodPointcut"
id=
"sleepPointcut"
>
<property name=
"pattern"
value=
".*sleep"
>
</property></bean>
<!--定义切面-->
<bean
class
=
"org.springframework.aop.support.DefaultPointcutAdvisor"
id=
"sleepHelperAdvisor"
>
<!--定义切面的通知-->
<property name=
"advice"
ref=
"sleepHelper"
>
<!--定义切面的切入点-->
<property name=
"pointcut"
ref=
"sleepPointcut"
>
</property></property></bean>
<!--定义代理类-->
<bean
class
=
"org.springframework.aop.framework.ProxyFactoryBean"
id=
"humanProxy"
>
<property name=
"target"
ref=
"human"
>
<property name=
"interceptorNames"
value=
"sleepHelperAdvisor"
>
<property name=
"proxyInterfaces"
value=
"com.yourcompany.spring.Sleepable"
>
</property></property></property></bean>
</beans>
|
或者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<!--?xml version=
"1.0"
encoding=
"UTF-8"
?-->
<beans xmlns=
"http://www.springframework.org/schema/beans"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemalocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"
>
<!-- 定义目标 -->
<bean
class
=
"com.yourcompany.spring.Human"
id=
"human"
></bean>
<!-- 定义通知 -->
<bean
class
=
"com.yourcompany.spring.SleepHelper"
id=
"sleepHelper"
></bean>
<!-- 定义切面 -->
<bean
class
=
"org.springframework.aop.support.RegexpMethodPointcutAdvisor"
id=
"sleepAdvisor"
>
<property name=
"advice"
ref=
"sleepHelper"
>
<property name=
"pattern"
value=
".*sleep"
>
</property></property></bean>
<!--定义代理类-->
<bean
class
=
"org.springframework.aop.framework.ProxyFactoryBean"
id=
"humanProxy"
>
<property name=
"target"
ref=
"human"
>
<property name=
"interceptorNames"
value=
"sleepAdvisor"
>
<property name=
"proxyInterfaces"
value=
"com.yourcompany.spring.Sleepable"
>
</property></property></property></bean>
</beans>
|
程序主入口:
1
2
3
4
5
6
7
8
9
10
11
12
|
package
com.yourcompany.spring;
import
org.springframework.context.ApplicationContext;
import
org.springframework.context.support.ClassPathXmlApplicationContext;
public
class
Test {
public
static
void
main(String[] args) {
ApplicationContext context =
new
ClassPathXmlApplicationContext(
"applicationContext.xml"
);
Sleepable sleeper = (Sleepable)context.getBean(
"humanProxy"
);
sleeper.sleep();
}
}
|
2、@AspectJ注解驱动的切面
jar包:spring-aop-2.5.1.jar,aopalliance-1.0.jar,aspectj-1.5.4.jar,aspectjweaver-1.5.4.jar
接口:
1
2
3
4
5
|
package
com.yourcompany.spring;
public
interface
Sleepable {
public
void
sleep();
}
|
目标:
1
2
3
4
5
6
7
|
package
com.yourcompany.spring;
public
class
Human
implements
Sleepable {
public
void
sleep() {
System.out.println(
"人类睡觉..."
);
}
}
|
通知:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package
com.yourcompany.spring;
import
org.aspectj.lang.annotation.AfterReturning;
import
org.aspectj.lang.annotation.Aspect;
import
org.aspectj.lang.annotation.Before;
import
org.aspectj.lang.annotation.Pointcut;
@Aspect
public
class
SleepHelper{
@Pointcut
(
"execution(* *.sleep(..))"
)
public
void
sleepPoint(){
}
@Before
(
"sleepPoint()"
)
public
void
beforeSleep(){
System.out.println(
"睡觉前要脱衣服!"
);
}
@AfterReturning
(
"sleepPoint()"
)
public
void
afterSleep(){
System.out.println(
"睡醒后要穿衣服!"
);
}
}
|
applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
|
<!--?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"
xsi:schemalocation="
http:
//www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http:
//www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
">
<!-- 启用AspectJ对Annotation的支持 -->
<!-- 定义目标 -->
<bean
class
=
"com.yourcompany.spring.Human"
id=
"human"
></bean>
<!-- 定义通知 -->
<bean
class
=
"com.yourcompany.spring.SleepHelper"
id=
"sleepHelper"
></bean>
</aop:aspectj-autoproxy></beans>
|
程序主入口:
1
2
3
4
5
6
7
8
9
10
11
12
|
package
com.yourcompany.spring;
import
org.springframework.context.ApplicationContext;
import
org.springframework.context.support.ClassPathXmlApplicationContext;
public
class
Test {
public
static
void
main(String[] args) {
ApplicationContext context =
new
ClassPathXmlApplicationContext(
"applicationContext.xml"
);
Sleepable sleepable = (Sleepable)context.getBean(
"human"
);
sleepable.sleep();
}
}
|
3、纯POJO切面
spring在aop的命名空间里面还提供了一些配置元素:
定义一个AOP通知者
后通知
返回后通知
抛出后通知
周围通知
定义一个切面
前通知
顶级配置元素,类似于这种东西
定义一个切点
jar包:spring-aop-2.5.1.jar,aopalliance-1.0.jar,aspectjweaver-1.5.4.jar
接口:
1
2
3
4
5
|
package
com.yourcompany.spring;
public
interface
Sleepable {
public
void
sleep();
}
|
目标:
1
2
3
4
5
6
7
|
package
com.yourcompany.spring;
public
class
Human
implements
Sleepable {
public
void
sleep() {
System.out.println(
"人类睡觉..."
);
}
}
|
通知:
1
2
3
4
5
6
7
8
9
10
11
|
package
com.yourcompany.spring;
public
class
SleepHelper{
public
void
beforeSleep(){
System.out.println(
"睡觉前要脱衣服!"
);
}
public
void
afterSleep(){
System.out.println(
"睡醒后要穿衣服!"
);
}
}
|
applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<!--?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"
xsi:schemalocation="
http:
//www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http:
//www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
">
<!-- 启用AspectJ对Annotation的支持 -->
<!-- 定义目标 -->
<bean
class
=
"com.yourcompany.spring.Human"
id=
"human"
></bean>
<!-- 定义通知 -->
<bean
class
=
"com.yourcompany.spring.SleepHelper"
id=
"sleepHelper"
></bean>
</aop:after></aop:before></aop:aspect>
</aop:config>
</aop:aspectj-autoproxy></beans><span style=
"font-size:18px;"
>
</span>
|
程序主入口:
1
2
3
4
5
6
7
8
9
10
11
12
|
package
com.yourcompany.spring;
import
org.springframework.context.ApplicationContext;
import
org.springframework.context.support.ClassPathXmlApplicationContext;
public
class
Test {
public
static
void
main(String[] args) {
ApplicationContext context =
new
ClassPathXmlApplicationContext(
"applicationContext.xml"
);
Sleepable sleepable = (Sleepable)context.getBean(
"human"
);
sleepable.sleep();
}
}
|
4、注入式AspectJ切面
jar包:spring-aop-2.5.1.jar,aopalliance-1.0.jar,aspectjweaver-1.5.4.jar
接口:
1
2
3
4
5
|
package
com.yourcompany.spring;
public
interface
Sleepable {
public
void
sleep();
}
|
目标:
1
2
3
4
5
6
7
|
package
com.yourcompany.spring;
public
class
Human
implements
Sleepable {
public
void
sleep() {
System.out.println(
"人类睡觉..."
);
}
}
|
通知:
1
2
3
4
5
6
7
8
9
10
11
|
package
com.yourcompany.spring;
public
class
SleepHelper{
public
void
beforeSleep(){
System.out.println(
"睡觉前要脱衣服!"
);
}
public
void
afterSleep(){
System.out.println(
"睡醒后要穿衣服!"
);
}
}
|
applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<!--?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"
xsi:schemalocation="
http:
//www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http:
//www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
">
<!-- 启用AspectJ对Annotation的支持 -->
<!-- 定义目标 -->
<bean
class
=
"com.yourcompany.spring.Human"
id=
"human"
></bean>
<!-- 定义通知 -->
<bean
class
=
"com.yourcompany.spring.SleepHelper"
id=
"sleepHelper"
></bean>
<!--配置消息类中的所有方法 -->
</aop:after></aop:before></aop:pointcut></aop:aspect>
</aop:config>
</aop:aspectj-autoproxy></beans><span style=
"font-size:18px;"
><span style=
"font-size:18px;"
>
</span></span>
|
程序主入口:
1
2
3
4
5
6
7
8
9
10
11
12
|
package
com.yourcompany.spring;
import
org.springframework.context.ApplicationContext;
import
org.springframework.context.support.ClassPathXmlApplicationContext;
public
class
Test {
public
static
void
main(String[] args) {
ApplicationContext context =
new
ClassPathXmlApplicationContext(
"applicationContext.xml"
);
Sleepable sleepable = (Sleepable)context.getBean(
"human"
);
sleepable.sleep();
}
}
|