Spring的核心部分:
- IOC:控制翻转,把创建对象过程和对象之间的调用过程交给
Spring
进行管理,为了降低耦合度 - AOP:面向切面,不修改源代码,进行功能增强。
AOP简介
OOP:面向对象编程,数据封装,继承和多态
AOP:把系统分解不同的关注点,或者称之为切面
通俗的说,就是不通过修改源代码的方式,在主干功能里面添加新功能。
AOP相关术语
Aspect:切面,即一个横跨多个核心逻辑的功能,或者称之为系统关注点;
切入点:知道对哪个类里面的哪个方法进行增强
execution([权限修饰符] [返回类型] [类全路径] 方法名称 )
装配AOP
以UserService
和MailService
为例,这两个属于核心业务逻辑,现在我们准备给UserService
的每个业务方法执行前添加日志,给MailService
的每个业务方法执行前后添加日志,在Spring中,需要以下步骤
- 首先引入Spring对AOP的支持:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
上面代码块自动引入AspectJ
- 然后定义一个
LoggingAspect
:
@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;
}
}
观察doAccessCheck()
方法,我们定义了一个@Before
注解,后面的字符串是告诉AspectJ应该在何处执行该方法,这里写的意思是:执行UserService
的每个public
方法前执行doAccessCheck()
代码。
再观察doLogging()
方法,我们定义了一个@Around
注解,它和@Before
不同,@Around
可以决定是否执行目标方法,因此,我们在doLogging()
内部先打印日志,再调用方法,最后打印日志后返回结果。
在LoggingAspect类的声明处,除了用@Component
表示它本身也是一个Bean外,我们再加上@Aspect
注解,表示它的@Before标注的方法需要注入到UserService的每个public方法执行前,@Around标注的方法需要注入到MailService的每个public方法执行前后。
- 紧接着,我们需要给
@Configuration
类加上一个@EnableAspectJAutoProxy
注解:
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AppConfig {
...
}
Spring的IOC容器看到这个注解,就会自动查找@Aspect的Bean,然后根据每个方法@Before
、@Around
等注解把AOP注入到特定的Bean中。
Spring容器启动时为我们自动创建的注入了Aspect
的子类,它取代了原始的UserService(原始的UserService实例作为内部变量隐藏在UserServiceAopProxy中)。如果我们打印从Spring容器获取的UserService实例类型,它类似UserService$$EnhancerBySpringCGLIB$$1f44e01c
,实际上是Spring使用CGLIB动态创建的子类,但对于调用方来说,感觉不到任何区别
总结
Spring容器内部实现AOP的逻辑比较复杂,需要使用AspectJ 解析注解
- 定义方法,并在方法上通过AspectJ的注解告诉Spring应该在何处调用此方法
- 标记
@Component
和@AspectJ
- 在
@Configuration
类上标注@EnbaleAspectJAutonomousProxy
拦截器有以下类型
@Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了;
@After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行;
@AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码;
@AfterThrowing:和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码;
@Around:能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能。
使用注解装配AOP
使用AOP时,被装配的Bean最好自己能清清楚楚地知道自己被安排了。
我们以一个实际例子演示如何使用注解实现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) {
...
}
...
}
然后,我们定义MetricAspect
:
@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");
}
}
}
注意metric()方法标注了@Around("@annotation(metricTime)")
,它的意思是,符合条件的目标方法是带有@MetricTime
注解的方法,因为metric()方法参数类型是MetricTime(注意参数名是metricTime不是MetricTime),我们通过它获取性能监控的名称
小结
使用注解实现AOP需要先定义注解,然后使用@Around("@annotation(name)")
实现装配;
参考博文:
https://www.liaoxuefeng.com/wiki/1252599548343744/1339039378571298