Spring框架有四大原则
·使用POJO进行轻量级与最小侵入式开发
·通过依赖注入和基于接口编程实现松耦合
·通过AOP和默认习惯进行声明式编程
·通过AOP和模板减少模式化代码
为什么需要AOP
我们知道,面向对象是为了解决面向过程开发中的高冗余代码而出现的。OOP能够自上而下的,让类与类之间建立一种关系从而达到动态添加代码的目的。
而Aop则是以横切的方式,将可以重复性的横切逻辑到一个统一的模块中。
关于面向切面编程的一些术语
下面是详细介绍
通知(Advice)
Before : 在方法被调用之前调用通知
After : 在方法完成之后调用通知,无论方法执行是否成功
AfterReturning : 在方法成功执行之后调用通知
AfterThrowing : 在方法抛出异常后调用通知
Around : 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
切点(PointCut)
切点的格式如下:

连接点(JoinPoint)
常用AspectJ指示器:
AspectJ指示器 | 描述 |
arg () | 限制连接点的指定参数为指定类型的执行方法 |
@args () | 限制连接点匹配参数由指定注解标注的执行方法 |
execution () | 用于匹配连接点的执行方法 |
this () | 限制连接点匹配 AOP 代理的 Bean 引用为指定类型的类 |
target () | 限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型注解 |
within() | 限制连接点匹配指定类型 |
@within() | 限制连接点匹配指定注释所标注的类型(当使用 Spring AOP 时,方法定义在由指定的注解所标注的类里) |
@annotation | 限制匹配带有指定注释的连接点 |
下面我们来实战一下
我们在mvnrepository.com中找到了spring-boot-starter-aop这个依赖同时能够添加Spring AOP 和 AspectJ这两个依赖。
http://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop/2.0.0.RELEASE
首先我们先写两个测试的类,还是沿用上次使用的Student与StudentManager类来做说明。
Student类是个POJO,有两个属性:int -> id , String -> name
package com.example.demo;
import org.springframework.stereotype.Repository;
@Repository("Student")
public class Student {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
StudentManager类管理了一个IOC容器中的Student的实例,并提供setStudent ,show 两个方法
setStudent能改变Student实例的属性
show方法能够输出Student实例的 id 和 name
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("studentManager") //把当前类加入IOC容器进行管理
public class StudentManager {
@Autowired //自动注入一个Student的Bean实例
Student student;
public void setStudent(Student student) {
this.student = student;
}
public void show() {
System.out.println("Student-Id : "+student.getId());
System.out.println("Student-name : "+student.getName());
}
}
以上与两个类与AOP无关hhhhh,仅作为测试使用。
然后我们再写配置类,我们需要在配置类中添加对Aop的支持
使用@EnableAspectJAutoProxy注解
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.example.demo")
@EnableAspectJAutoProxy //添加对AspectJ的支持
@RequestMapping
public class DIConfig {
}
问题引出
当我们编写好这两个类的之后过了很长一段时间(假如),我们这个时候在运行这段代码的时候发现这里可能有问题,想在每个方法开始之前输出一句start,方法运行结束以后输出一句end,来表示这个方法被正确执行了。我们假设有许多类都需要做此类操作,我们假如用oop的思想的话,需要在每个类中去添加新的代码,来实现这个输出start,end这种非常简单的功能,是非常繁琐的。特别是项目大了以后,要做修改非常困难。
我们下面来用AOP实现这样一个简单的功能。
package com.example.demo;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class AOPtest {
@Pointcut("execution(* com.example.demo.StudentManager.*(..))")
public void studentCut() {
}
@Before("studentCut()")
public void start() {
System.out.println("start");
}
@After("studentCut()")
public void end() {
System.out.println("end");
}
@Before("execution(* com.example.demo.StudentManager.*(..))")
public void beforeIt() {
System.out.println("Before Do It");
}
}
我们定义了一个切面叫做AOPtest,实际上它是一个java类,我们使用了@Aspect注解,告诉Spring,这个类是一个切面。
然后我们使用了@Pointcut注解,定义了一个切点,叫做studentCut,切点后就是这个切点的描述信息。我们来解读一下这段描述:
@Pointcut("execution(* com.example.demo.StudentManager.*(..))")
@Pointcut | 声明这是一个切点 |
execution | 当方法被执行时调用 |
第一个* | 返回值可以为任意类型 |
com.example.demo.StudentManager | com.example.demo包下的StudentManager类 |
第二个* | StudentManager类的全部方法(这里可以替换为该类的任意方法名) |
(..) | 可以为任意参数 |
我们可以发现,使用这样的切点可以准确定位我们应该在哪里进行拦截操作。有点类似于正则表达式的作用,aop通过这种方式能够一次性匹配一堆类的方法进行拦截。
下面我们演示一下如何动态的在切点(Pointcut)插入代码
@Before("studentCut()")
public void start() {
System.out.println("start");
}
@After("studentCut()")
public void end() {
System.out.println("end");
}
我们使用了@Before ,@After 这两个注解分别在studentCut这个切点前后输出了“start”与“end”
@Before | 在切点前执行该方法 |
studentCut | 上面定义好的一个切点 |
@After | 在切点后执行该方法 |
使用了@Before注解的start()方法将会在拦截方法执行前被执行
使用了@After注解的start()方法将会在拦截方法执行后被执行
我们也可以不单独定义Pointcut,直接在通知(Advice)中写明:
@Before("execution(* com.example.demo.StudentManager.*(..))")
public void beforeIt() {
System.out.println("Before Do It");
}
让我们来测试一下
我们可以看到,我们成功的在执行studentManager.show()的时候,动态的加入了一些代码。
总结
AOP的思维作为oop思想的补充,在处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等,已经成为一种非常常用的解决方案。但是我们必须清楚,aop并不能替代oop的作用,它更多的是起一个辅助者的作用。它为程序提供了一个崭新的编程思考角度,可以重复性的横切逻辑到一个统一的模块中,只要通过纵向抽象和AOP横向抽取,程序员才可以真正解决重复代码问题,提高代码的重用性与复用性。
预告
下一节我们将简单介绍Spring的常用配置