传统的面向对象编程中,每个单元就是一个类,而类似于安全性这方面的问题,它们通常不能集中在一个类中处理因为它们横跨多个类,这就导致了代码无法重用,可维护性差而且产生了大量代码冗余,这是我们不愿意看到的。
Aspect Oriented Programming(AOP)可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现。
作为AOP的具体实现之一的AspectJ,它向Java中加入了连接点(Join Point)这个新概念,其实它也只是现存的一个Java概念的名称而已。它向Java语言中加入少许新结构:切点(pointcut)、通知(Advice)、类型间声明(Inter-type declaration)和方面(Aspect)。切点和通知动态地影响程序流程,类型间声明则是静态的影响程序的类等级结构,而方面则是对所有这些新结构的封装。
一个连接点是程序流中指定的一点。切点收集特定的连接点集合和在这些点中的值。一个通知是当一个连接点到达时执行的代码,这些都是AspectJ的动态部分。其实连接点就好比是程序中的一条一条的语句,而切点就是特定一条语句处设置的一个断点,它收集了断点处程序栈的信息,而通知就是在这个断点前后想要加入的程序代码。AspectJ中也有许多不同种类的类型间声明,这就允许程序员修改程序的静态结构、名称、类的成员以及类之间的关系。AspectJ中的方面是横切关注点的模块单元。它们的行为与Java语言中的类很像,但是方面还封装了切点、通知以及类型间声明。
1、AOP术语
1)连接点(Joinpoint)
程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些点中的特定点就称为“连接点”。Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位。
2)切点(Pointcut)
每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。AOP通过“切点”定位特定的连接点。连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责切点所设定的查询条件,找到对应的连接点。其实确切地说,不能称之为查询连接点,因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息。
3)通知(Advice)
Advice定义了切面中的实际逻辑(即实现 ),比如日志的写入的实际代码。换一种说法Advice是指在定义好的切入点处,所要执行的程序代码。
通知有以下几种:
前置通知( Before advice):在切入点匹配的方法执行之前运行使用@Before注解来声明
返回后通知( After returning advice):在切入点匹配的方法返回的时候执行。使用 @AfterReturning注解来声明
抛出后通知( After throwingadvice) : 在切入点匹配的方法执行时抛出异常的时候运行。使用 @AfterThrowing注解来声明
后通知( After (finally) advice):不论切入点匹配的方法是正常结束的,还是抛出异常结束的,在它结束后(finally)后通知(After (finally) advice)都会运行。使用 @After 注解来声明。这个通知必须做好处理正常返回和异常返回两种情况。通常用来释放资源。
异常通知(throws advice)当目标对象的方法出现异常,会统一在异常通知处理。如果连接点抛出异常,异常通知(throws advice)将在连接点返回后被调用。
环绕通知( Around Advice):环绕通知既在切入点匹配的方法执行之前又在执行之后运行。并且,它可以决定这个方法在什么时候执行,如何执行,甚至是否执行。在环绕通知中,除了可以自由添加需要的横切功能以外,还需要负责主动调用连接点 (通过 proceed)来执行激活连接点的程序。请尽量使用最简单的满足你需求的通知。(比如如果前置通知也可以适用的情况下,就不要使用环绕通知)
环绕通知使用 @Around注解来声明。而且该通知对应的方法的第一个参数必须是 ProceedingJoinPoint类型。在通知体内(即通知的具体方法内),调用 ProceedingJoinPoint的 proceed()方法来执行连接点方法。
4)目标对象(Target)
增强逻辑的织入目标类。如果没有AOP,目标业务类需要自己实现所有逻辑,而在AOP的帮助下,目标业务类只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用AOP动态织入到特定的连接点上。
5)引介(Introduction)
引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类,比如你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
6)织入(Weaving)
织入是将增强添加对目标类具体连接点上的过程。AOP像一台织布机,将目标类、增强或引介通过AOP这台织布机天衣无缝地编织到一起。根据不同的实现技术,AOP有三种织入的方式:
a、编译期织入,这要求使用特殊的Java编译器。
b、类装载期织入,这要求使用特殊的类装载器。
c、动态代理织入,在运行期为目标类添加增强生成子类的方式。
Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
7)代理(Proxy)
一个类被AOP织入增强后,就产出了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。
8)切面(Aspect)
切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。
Jar包引入
1、 首先我们先创建一个Spring web项目,具体步骤请参考:
http://blog.csdn.net/lu677521/article/details/79017626
2、 项目建好之后引入所需的三个jar包
a) aspectj-1.8.13.jar
b) aspectjrt-1.8.13.jar
c) aspectjweaver-1.8.13.jar
3、这三个jar包可以去官网下载,地址为
https://mvnrepository.com/artifact/org.aspectj/aspectjweaver
http://www.mvnrepository.com/artifact/org.aspectj/aspectjrt
这三个jar包也可以利用idea的下载jar包功能,具体方法如下(此方法只能下载低版本的,最好去官网最新版本的jar包):
Aop示例
1)配置XML文件方式
我们先定义一个Student类,有name和age两个属性
package aspect.com;
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
再定义一个Login类,声明一个Student类的属性,再定义一个方法,用来实现登录,我们要在xml文件中注入Student类
package aspect.com;
public class Login {
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public int login(){
System.out.println(student.getName()+"正在登录。。。");
return 1;
}
}
这时我们新建一个切面类Blog
package com;
public class Blog {
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public void before(){
System.out.println(student.getName()+"准备登录.....");
}
public void after(){
System.out.println(student.getName()+"已经登录.....");
}
public void returning(){
System.out.println(student.getName()+"已经在玩了.....");
}
public void throwing(){
System.out.println("throwing.....");
}
}
然后在Bean.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!--suppress SpringFacetInspection -->
<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 http://www.springframework.org/schema/aop/spring-aop.xsd"
>
<!--实例化Student类-->
<bean id="Student" class="com.Student">
<property name="name" value="张三"/>
<property name="age" value="12"/>
</bean>
<!--实例化Login类-->
<bean id="Login" class="com.Login">
<property name="student" ref="Student"/>
</bean>
<!--实例化Blog类-->
<bean id="Blog" class="com.Blog">
<property name="student" ref="Student"/>
</bean>
<!--搭建Aop框架-->
<aop:config>
<!--引入切面类-->
<aop:aspect id="Blog" ref="Blog">
<!--声明切点、连接点-->
<aop:pointcut id="Login_point" expression="execution(* com.Login.login(..))"/>
<!--连接点执行前将执行的方法-->
<aop:before pointcut-ref="Login_point" method="before"/>
<!--连接点执行后将执行的方法-->
<aop:after pointcut-ref="Login_point" method="after"/>
<!--连接点返回值后将执行的方法-->
<aop:after-returning pointcut-ref="Login_point" method="returning"/>
<!--连接点抛出异常后将执行的方法-->
<aop:after-throwing pointcut-ref="Login_point" method="throwing"/>
</aop:aspect>
</aop:config>
</beans>
最后我们新建测试类:
package aspect.com;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class testMain {
public static void main(String[] args) {
//获取Spring容器
ApplicationContext act= new ClassPathXmlApplicationContext("aspectcofig.xml");
//实例化Login
Login login=act.getBean("Login",Login.class);
//方法调用
login.login();
}
}
执行结果如下:
2)注解方式
我们先定义一个Student类,有name和age两个属性
package aspect.com;
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
再定义一个
Login
类,声明一个Student类的属性,再定义一个方法,用来实现登录,我们要在xml文件中注入Student类
package com;
public class Login {
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public int login(){
System.out.println(student.getName()+"正在登录。。。");
//这里我们手动抛出一个异常
throw new IndexOutOfBoundsException();
}
}
这时我们新建一个切面类Blog
package aspect.com;
import org.aspectj.lang.annotation.*;
//声明此类为切面类
@Aspect
public class Blog {
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
//找到切点、连接点,并定义一个方法
@Pointcut("execution(* aspect.com.Login.login(..))")
public void goPoint(){}
//定义一个连接点方法执行前执行的方法
@Before("goPoint()")
public void before(){
System.out.println(student.getName()+"准备登录.....");
}
//定义一个连接点方法执行后执行的方法
@After("goPoint()")
public void after(){
System.out.println(student.getName()+"已经登录.....");
}
//定义一个连接点方法返回值后执行的方法
@AfterReturning("goPoint()")
public void returning(){
System.out.println(student.getName()+"已经在玩了.....");
}
//定义一个连接点方法抛出异常后执行的方法
@AfterThrowing("goPoint()")
public void throwing(){
System.out.println(student.getName()+"玩脱了.....");
}
}
然后在Bean.xml配置如下
<?xml version="1.0" encoding="UTF-8"?>
<!--suppress ALL -->
<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 http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--实例化Student类并注入属性-->
<bean id="Student" class="aspect.com.Student">
<property name="name" value="张三"/>
<property name="age" value="12"/>
</bean>
<!--实例化Login类并注入属性-->
<bean id="Login" class="aspect.com.Login">
<property name="student" ref="Student"/>
</bean>
<!--实例化Blog类并注入属性-->
<bean id="Blog" class="aspect.com.Blog">
<property name="student" ref="Student"/>
</bean>
<aop:aspectj-autoproxy />
</beans>
最后我们新建测试类:
package aspect.com;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class testMain {
public static void main(String[] args) {
//获取Spring容器
ApplicationContext act= new ClassPathXmlApplicationContext("aspectcofig.xml");
//实例化Login
Login login=act.getBean("Login",Login.class);
//方法调用
login.login();
}
}
执行结果如下: