第
3
章
AOP
面向切面编程
AOP
简介
AOP
(
Aspect Orient Programming
),面向切面编程。面向切面编程是从动态角度考虑程
序运行过程。
AOP
底层,就是采用动态代理模式实现的。采用了两种代理:
JDK
的动态代理
,与
CGLIB
的动态代理
。
(
AOP
为
Aspect Oriented Programming 的缩写,意为:面向切面编程,可通过运行期动态
代理实现程序功能的统一维护的一种技术。
AOP
是
Spring
框架中的一个重要内容。利用
AOP
可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程
序的可重用性,同时提高了开发的效率。)
面向切面编程,就是将交叉业务逻辑封装成切面,利用
AOP
容器的功能将切面织入到
主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、
事务、日志、缓存等。
若不使用
AOP
,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,
会使主业务逻辑变的混杂不清。
例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事
务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占
比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大
大干扰了主业务逻辑
---
转账。
面向切面编程对有什么好处?
1.
减少重复;
2.
专注业务;
注意:面向切面编程只是面向对象编程的一种补充。
使用
AOP
减少重复代码,专注业务实现:
AOP
编程术语
(
掌握
)
(
1
) 切面(
Aspect
)
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面
是通知(
Advice
)。实际就是对主业务逻辑的一种增强。
(
2
) 连接点(
JoinPoint
)
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
(
3
) 切入点(
Pointcut
)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。
被标记为
final
的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不
能被增强的。
(
4
) 目标对象(
Target
)
目 标 对 象 指 将 要 被 增 强 的 对 象 。 即 包 含 主 业 务 逻 辑 的 类 的 对 象 。 上 例 中 的
StudentServiceImpl
的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,
不被增强,也就无所谓目标不目标了。
(
5
) 通知(
Advice
)
通知表示切面的执行时间,
Advice
也叫增强。上例中的
MyInvocationHandler
就可以理
解为是一种通知。换个角度来说,
通知定义了增强代码切入到目标代码的时间点
,是目标方
法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时间
。
AspectJ
对
AOP
的实现
(
掌握
)
对于
AOP
这种编程思想,很多框架都进行了实现。
Spring
就是其中之一,可以完成面向
切面编程。然而,
AspectJ
也实现了
AOP
的功能,且其实现方式更为简捷,使用更为方便,
而且还支持注解式开发。所以,
Spring
又将
AspectJ
的对于
AOP
的实现也引入到了自己的框
架中。
在
Spring
中使用
AOP
开发时,一般使用
AspectJ
的实现方式。
AspectJ
简介
(
AspectJ
是一个优秀面向切面的框架,它扩展了
Java
语言,提供了强大的切面实现。)
官网地址:
http://www.eclipse.org/aspectj/
AspetJ
是
Eclipse 的开源项目,官网介绍如下:
a seamless aspect-oriented extension to the Javatm programming language
(一种基于
Java
平台 的面向切面编程的语言)
Java platform compatible
(兼容
Java
平台,可以无缝扩展)
easy to learn and use
(易学易用)
AspectJ
的通知类型
(
理解
)
AspectJ
中常用的通知有五种类型:
(
1
)前置通知
(
2
)后置通知
(
3
)环绕通知
(
4
)异常通知
(
5
)最终通知
AspectJ
的切入点表达式
(
掌握
)
AspectJ
定义了专门的表达式用于指定切入点。表达式的原型是:
execution(modifiers-pattern? ret-type-pattern
declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
解释:
modifiers-pattern]
访问权限类型
ret-type-pattern
返回值类型
declaring-type-pattern
包名类名
name-pattern(param-pattern)
方法名
(
参数类型和参数个数
)
throws-pattern
抛出异常类型
?表示可选的部分
以上表达式共
4
个部分。
execution(
访问权限
方法返回值 方法声明
(
参数
)
异常类型
)
切入点表达式要匹配的对象就是目标方法的方法名。所以,
execution
表达式中明显就
是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可
以使用以下符号:
举例:
execution(public * *(..))
指定切入点为:任意公共方法。
execution(* set*(..))
指定切入点为:任何一个以“
set”
开始的方法。
execution(* com.xyz.service.*.*(..))
指定切入点为:定义在
service
包里的任意类的任意方法。
execution(* com.xyz.service..*.*(..))
指定切入点为:定义在
service
包或者子包里的任意类的任意方法。“
..”
出现在类名中时,后
面必须跟“
*
”,表示包、子包下的所有类。
execution(* *..service.*.*(..))
指定所有包下的
serivce
子包下所有类(接口)中所有方法为切入点
AspectJ
的开发环境
(
掌握
)
(
1
)
maven
依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version>
</dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version>
</dependency>
插件
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
(
2
) 引入
AOP
约束
在
AspectJ
实现
AOP
时,要引入
AOP
的约束。配置文件中使用的
AOP
约束中的标签,
均是
AspectJ
框架使用的,而非
Spring
框架本身在实现
AOP
时使用的。
AspectJ
对于
AOP
的实现有注解和配置文件两种方式,常用是注解方式。
AspectJ
基于注解的
AOP
实现
(
掌握
)
AspectJ
提供了以注解方式对于
AOP
的实现。
(
1
) 实现步骤
A
、
Step1
:定义业务接口与实现类
package com.zsz.ba01;
public interface SomeService {
void doSome(String name,Integer age);
}
package com.zsz.ba01;
//目标类
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name,Integer age) {
// 给doSome()方法增加一个功能,在doSome()执行之前,输出方法的执行时间
System.out.println("===目标方法doSome===");
}
}
B
、
Step2
:定义切面类
类中定义了若干普通方法,将作为不同的通知方法,用来增强功能。
package com.zsz.ba01;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
* @Aspect:是aspectj框架中的注解。
*
* 作用:表示当前类是切面类。
* 切面类是用来给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置:在类定义的上面
*/
@Aspect
public class MyAspect {
/**
* 指定通知方法中的参数 : JoinPoint
* joinPoint:业务方法,要加入切面功能的业务方法
*
*/
@Before(value = "execution(public void com.zsz.ba01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore(JoinPoint jp){
//获取方法的完整定义
System.out.println("方法的签名(定义)="+jp.getSignature());
System.out.println("方法的名称"+jp.getSignature().getName());
//获取方法的实参
Object[] args = jp.getArgs();
for (Object arg : args){
System.out.println("参数="+arg);
}
System.out.println("前置通知, 切面功能:在目标方法之前输出执行时间:"+new Date());
}
}
C
、
Step3
:声明目标对象切面类对象
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--把对象交给spring容器,由spring容器统一创建,管理对象-->
<!--声明目标对象-->
<bean id="someService" class="com.zsz.ba01.SomeServiceImpl"></bean>
<!--声明切面对象-->
<bean id="myAspect" class="com.zsz.ba01.MyAspect"/>
</beans>
D
、
Step4
:注册
AspectJ
的自动代理
在定义好切面
Aspect
后,需要通知
Spring
容器,让容器生成“目标类
+
切面”的代理
对象。这个代理是由容器自动生成的。只需要在
Spring
配置文件中注册一个基于
aspectj
的
自动代理生成器,其就会自动扫描到
@Aspect
注解,并按通知类型与切入点,将其织入,并
生成代理。
<!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象
创建代理对象是在内存中实现的,修改内存对象的内存中的结构。
aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生产代理对象。
-->
<aop:aspectj-autoproxy/>
<aop:aspectj-autoproxy/>
的底层是由
AnnotationAwareAspectJAutoProxyCreator
实现的。
从其类名就可看出,是基于
AspectJ
的注解适配自动代理生成器。
其工作原理是,
<aop:aspectj-autoproxy/>
通过扫描找到
@Aspect
定义的切面类,再由切
面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
E
、
Step5
:测试类中使用目标对象的
id
package com.zsz;
import com.zsz.ba01.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest01 {
@Test
public void test01(){
String config="applicationContext.xml";
ApplicationContext ac= new ClassPathXmlApplicationContext(config);
//从容器中获取目标对象
SomeService someService = (SomeService) ac.getBean("someService");
//通过代理的对象执行方法,实现目标方法执行还时,增强了功能
//someService:com.sun.proxy.$Proxy8
System.out.println("someService:"+someService.getClass().getName());
someService.doSome("赵书正",20);
}
}