用过Spring的朋友都知道,Spring有两大核心功能,一个是IOC,一个是AOP。IOC对于开发者来讲可能用的还比较多,声明一个Bean交给Spring容器来管理,或者从Spring容器中找到某个Bean注入到另一个Bean中。而对于AOP来讲,普通开发者用的就不是很多了。往往是项目的整体架构都已经对切面进行处理完毕了,记录日志也好,处理数据库事务也好等等这些功能,程序员一般是不需要关注的,当然这也是AOP的核心--关注点分离。这篇笔记并不是来记录如何使用AOP的,对于AOP的讲解网络上有很多的文章,可以参考。这里主要介绍的是使用Java配置的方式实现AOP,以及使用自定义注解。
用一个实际的例子来演示使用注解和使用单纯的AOP的方式来面向切面编程。主要目标是实现在某个方法上打上我们自定义的注解,就可以被切面所拦截,在方法执行前或者方法执行后执行一些操作。
我们这里需要一个注解类,两个服务提供者,一个切面,再加一个配置类和一个运行的类,运行的类并充当服务的消费者。
注解类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Action {
String name();
}
这里定义了一个注解,表示是运行时生效,目标是标注在方法上。可以指定一个name属性。注解本身就是一个标记,是没有任何的功能的。
第一个服务的提供者,不使用注解
@Service
public class DemoService {
public void add() {
}
}
这里就提供了一个空的add方法
第二个服务的提供者,使用注解
@Service
public class DemoAnnotationService {
@Action(name="注解拦截的add操作")
public void add() {
}
}
这里和第一个服务的提供者类似,同样提供了一个空的add方法,只是在add方法上标注了我们定义的action注解,并指定了name属性的值
切面
@Aspect // 声明一个切面
@Component
public class LogAspect {
// 关于切入点,可以单独定义,如下。也可以直接写在通知中。单独定义的可以复用,而写到通知中的不可以进行复用。类似于函数和匿名函数。
@Pointcut("@annotation(com.hy.spring.test3.Action)") // 定义一个切入点
public void annotationPointCut() {}
@Before("execution(* com.hy.spring.test3.DemoService.*(..))") // 定义一个前置通知,并制定切入点表达式
public void begin(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("方法规则式拦截 " + method.getName());
}
@After("annotationPointCut()") // 声明一个后置通知,并使用之前定义的切入点
public void after (JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Action action = method.getAnnotation(Action.class);
System.out.println("注解式拦截 " + action.name());
}
}
这个是我们的核心代码,先看注解,这里用到了Aspect,Component,Pointcut,Before,After。分别介绍这5个注解
Aspect | 声明一个切面 |
Pointcut | 声明切点 |
Before | 声明前置通知 |
After | 声明后置通知 |
Component | 交给spring容器来管理 |
了解了注解的基本功能之后,再来对代码进行分析。如果对AOP不熟悉的小伙伴可以先了解一下AOP的相关内容,再来继续向下学习。这里其实就是两个通知,一个前置通知,一个后置通知,分别对应这两个切入点。切入点的配置方式有两种,第一种就是在前置通知中定义的形式,直接将切面表达式写到注解中,另外可以单独将切入点定义处理,并使用Pointcut来声明这个切入点表达式,方法的名字就是切点的名称。后置通知中就是使用的这种形式,在后置通知的注解中指定切点的方法名,这里一定要把方法的()加上,否则找不到切点。切点表达式定义的都非常简单,annotationPointCut()切点就是要切带有com.hy.spring.test3.Action这个注解的所有类, @Before("execution(* com.hy.spring.test3.DemoService.*(..))")要切的就是com.hy.spring.test3.DemoService这个包下的所有类。下面我们再来看切了之后干的啥事,不难发现,做的事情也是非常的简单,带有注解的就是打印了注解中配置的值,没有注解的就打印了方法的名字。
配置类
@Configuration
@ComponentScan(value= "com.hy.spring.test3")
@EnableAspectJAutoProxy // 这里很关键,必须要开启对AOP的支持
public class AopConfig {
}
ComponentScan是要扫描的包,这个注解会自动扫描指定包下的所有的类
EnableAspectJAutoProxy是开启切面的自动代理
测试类
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
DemoAnnotationService service = context.getBean(DemoAnnotationService.class);
service.add();
context.close();
}
}
这里只演示了注解的切面,非注解的同样。