Aspect Oriented Programming,即面向切面编程。对一个大型项目的代码而言,整个系统要求关注安全检查、日志、事务等功能,这些功能实际上“横跨”多个业务方法。在一般的OOP编程里,需要在每一个业务方法内添加相关非业务方法的调用,这实际上是冗余的。如果能够类似IoC一样,这样的安全检查,日志,事务功能单独提取放到外面,核心业务方法不需要关注,就能降低代码耦合度。如果我们以AOP的视角来编写上述业务,可以依次实现:
AOP对于解决特定问题,例如事务管理非常有用,这是因为分散在各处的事务代码几乎是完全相同的,并且它们需要的参数(JDBC的Connection)也是固定的。另一些特定问题,如日志,就不那么容易实现,因为日志虽然简单,但打印日志的时候,经常需要捕获局部变量,如果使用AOP实现日志,我们只能输出固定格式的日志,因此,使用AOP时,必须适合特定的场景。
常见的AOP术语包括:
例如,编写日志loggin切片
@Aspect
@Component
public class LoggingAspect {
// 在执行UserService的每个方法前执行:
@Before("execution(public * com.itranswarp.learnjava.service.UserService.*(..))")
public void doAccessCheck() {
System.err.println("[Before] do access check...");
}
// 在执行MailService的每个方法前后执行:
@Around("execution(public * com.itranswarp.learnjava.service.MailService.*(..))")
public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
System.err.println("[Around] start " + pjp.getSignature());
Object retVal = pjp.proceed();
System.err.println("[Around] done " + pjp.getSignature());
return retVal;
}
}
我们需要给@Configuration类加上一个@EnableAspectJAutoProxy注解,Spring的IoC容器看到这个注解,就会自动查找带有@Aspect的Bean,然后根据每个方法的@Before、@Around等注解把AOP注入到特定的Bean中。
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AppConfig {
...
}
AOP原理实际上就是一个代理类的实现,这个代理是借由Spring容器实现的,例如,将LoggingAspect.doAccessCheck注入UserService的每个public方法中,可以通过如下实现:
public UserServiceAopProxy extends UserService {
private UserService target;
private LoggingAspect aspect;
public UserServiceAopProxy(UserService target, LoggingAspect aspect) {
this.target = target;
this.aspect = aspect;
}
public User login(String email, String password) {
// 先执行Aspect的代码:
aspect.doAccessCheck();
// 再执行UserService的逻辑:
return target.login(email, password);
}
public User register(String email, String password, String name) {
aspect.doAccessCheck();
return target.register(email, password, name);
}
...
}
容器自动为我们创建了注入了Aspect的子类,取代原始的UserService,并把被@Before @Around关键词修饰的方法覆写。
AOP拦截器通常是指用于拦截和处理方法调用的组件或类。这些拦截器可以在方法调用前、后或异常抛出时执行特定的逻辑
通过使用这些注解,可以将拦截器逻辑与特定的切点(Pointcut)相结合,实现对核心业务逻辑的拦截和处理。可以使用execution()表达式来定义切点,指定要拦截的方法的执行。这些注解可以与Spring AOP的其他功能和配置一起使用,如切面(Aspect)、通知(Advice)和配置文件等,以实现更复杂的AOP编程。
使用注解装配AOP时,最好需要在被装配的Bean处使用注解标记,被装配的Bean最好自己能清清楚楚地知道自己被安排了。例如,Spring提供的@Transactional。
我们以一个实际例子演示如何使用注解实现AOP装配。为了监控应用程序的性能,我们定义一个性能监控的注解:
@Target(METHOD)
@Retention(RUNTIME)
public @interface MetricTime {
String value();
}
/**************************************/
@Component
public class UserService {
// 监控register()方法性能:
@MetricTime("register")
public User register(String email, String password, String name) {
...
}
...
}
/**************************************/
@Aspect
@Component
public class MetricAspect {
@Around("@annotation(metricTime)")
public Object metric(ProceedingJoinPoint joinPoint, MetricTime metricTime) throws Throwable {
String name = metricTime.value();
long start = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long t = System.currentTimeMillis() - start;
// 写入日志或发送至JMX:
System.err.println("[Metrics] " + name + ": " + t + "ms");
}
}
}
这段代码中,一直对@Around("@annotation(metricTime)")
存在疑惑,为什么不是MetricTime(类)而是metricTime(参数名),关键在@annotation。
@annotation 是 Spring AOP 中的一个切点指示器,用于匹配被任意注解标记的方法或类。在切点表达式中使用 @annotation(annotationType),其中 annotationType 是要匹配的注解类型,可以是任何有效的注解类型。这个切点指示器的作用是,在切面中匹配被特定注解标记的方法或类,以便在相应的切面方法中对它们进行处理。
以下实例中,@Around(“@annotation(com.example.MyAnnotation)”) 表达式表示匹配被 @MyAnnotation 注解标记的方法。当调用这些被标记的方法时,切面中的 myAdvice 方法会被触发
@Aspect
@Component
public class MyAspect {
// 匹配被 @MyAnnotation 标记的方法
@Around("@annotation(com.example.MyAnnotation)")
public Object myAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 在方法执行前后执行自定义逻辑
// ...
return joinPoint.proceed();
}
}
但在@Around("@annotation(metricTime)")
中,由于切面方法metric要用到被标记注解的实例,而不只是匹配注解类型。那么在 @annotation() 中需要传入该注解的实例。具体来说,如果被切入的方法的参数列表中有一个具有某个特定注解的参数,则可以在切面表达式中使用 @annotation(parameterName) 来匹配并获取该注解的实例。