前言
AOP相关概念就不说了,复制粘贴没意思,本文主要是把概念转化为实际应用,让虚无的概念落地。
工程结构
首先,做一个基于Xml的,全程不使用注解(但是注解是真的方便)。
写一个简单的接口,用于后面的AOP进行切片。
接口IworkerAop
打工人接口,专门提供打工方法。
public interface IworkerAop {
/**
* 打工人要干活儿
*/
void work();
void work(String ...args);//用来测试参数获取
}
再整俩实现类
Worker
和Boss
,老板算不算打工人啊?
public class Boss implements IworkerAop {
public void work() {
System.out.println("boss sleep all day");
}
public void work(String... args) {
for (int i = 0; i < args.length; i++) {
String arg = args[i];
System.out.println("arg:" + arg);
args[i] = arg + "boss-work";
}
}
}
public class Worker implements IworkerAop {
public void work() {
System.out.println("I'm coding");
}
public void work(String... args) {
for (int i = 0; i < args.length; i++) {
String arg = args[i];
System.out.println( arg+" 开始工作");
args[i] = arg + "hungry";
}
}
}
配置AOP方法
这些方法根据配置,会在切点执行。
public class AopXmlConfig {
//之前执行
public void beforeWork() {
System.out.println("go to company");
}
//之后执行
public void afterWork() {
System.out.println("eat");
}
}
做Xml的配置
在application-aop.xml
中配置之前写好的AOP方法,核心就在这里了,配切点,通知以及bean的注入。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:contex="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--让Spring帮我注入bean-->
<bean name="aopXmlConfig" class="com.example.xml.aop.springaopxmldemo.aopConfig.AopXmlConfig"/>
<bean name="boss" class="com.example.xml.aop.springaopxmldemo.workService.impl.Boss"/>
<bean name="worker" class="com.example.xml.aop.springaopxmldemo.workService.impl.Worker"/>
<!--配置AOP-->
<aop:config>
<!--配置切点-->
<aop:pointcut id="workPointCut"
expression="execution(* com.example.xml.aop.springaopxmldemo.workService.IworkerAop.work(..))"/>
<!--配置通知-->
<aop:aspect ref="aopXmlConfig">
<aop:before method="beforeWork" pointcut-ref="workPointCut"/>
<aop:after method="afterWork" pointcut-ref="workPointCut"/>
</aop:aspect>
</aop:config>
</beans>
主类SpringAopXmlDemoApplication
主类获取bean实例,执行一下切入点的方法看看效果。
public class SpringAopXmlDemoApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application-aop.xml");
IworkerAop worker = (IworkerAop) context.getBean("worker");
worker.work();
}
}
输出结果
可以看到,再AOP中配置的before和after方法成功切入。
老板说打工人只打工不行,加强一下,还得PUA打工人,不然老想着跳槽。
于是在打工人工作的时候进行PUA。
切入点方法参数获取以及修改
增加一个around通知方法:
public class AopXmlConfig {
/**
* 之前执行
*/
public void beforeWork() {
System.out.println("go to company");
}
/**
* 环绕执行
* @param point
* @throws Throwable
*/
public void aroundWork(ProceedingJoinPoint point) throws Throwable {
Object[] values = point.getArgs();//使用数组获取参数
printWorker(values);//打印参数
point.proceed(new Object[]{new String[]{"通过老板PUA的打工人1", "通过老板PUA的打工人2"}});
//注意这里,因为使用的是可变参数,实际上是数组,所以需要new数组作为参数,再放到一个数组里传入。
}
/**
* 打印参数
* @param values
*/
private void printWorker(Object[] values) {
for (Object o : values) {
System.out.println("监控打工人数量:" + Arrays.toString(((String[]) o)));
}
}
/**
* 之后执行
*/
public void afterWork() {
System.out.println("eat");
}
}
xml通知配置中增加一个环绕通知
<aop:around method="aroundWork" pointcut-ref="workPointCut"/>
还记得打工人接口里面的一个可变参数列表方法吧,这个方法就是为了传入多个打工人的
public class SpringAopXmlDemoApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application-aop.xml");
IworkerAop worker = (IworkerAop) context.getBean("worker");
worker.work("打工人1","打工人2");
}
}
执行结果
打工人被老板PUA,认真干活。
基于注解
基于注解就简单得多了,复制AopXmlConfig
改造一下,在这里直接一步到位了哈,@Aspect
配置切片,表示我这个类是配置AOP的,让Spring大哥你给我注意些,@Component
自动注入bean,@Pointcut
配置切点,@Before
相关注解配置通知到相关方法上。另外,在工具人实现类上加上@Service
注解,方便自动注入。
@Aspect
@Component
public class AopConfig {
@Pointcut(value = "execution(* com.example.xml.aop.springaopxmldemo.workService.impl.*.work(..))")
public void workPointCut() {
}
@Before(value = "workPointCut()")
public void beforeWork() {
System.out.println("anno-go to company");
}
@After(value = "workPointCut()")
public void afterWork() {
System.out.println("anno-eat");
}
}
主类配置
@EnableAspectJAutoProxy
启用AOP,@ComponentScan
扫描bean进行自动注入。
@EnableAspectJAutoProxy
@ComponentScan
public class SpringAopNoXmlDemoApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringAopNoXmlDemoApplication.class);
IworkerAop worker = (IworkerAop) context.getBean("worker");
worker.work("打工人1","打工人2");
}
}
执行结果
完全去掉了Xml,可怜的打工人又开始工作了
后记
在配置过程中有些值得注意的地方:
- 在xml配置中可以启用注解进行混合配置,怎么方便怎么来;
- 在获取bean实例的时候,我使用的是bean id的方式获取,这种方式默认使用小驼峰,也就是首字母小写的,需要注意;
- 环绕方法参数获取以及处理的时候,这里使用可变参数,导致
ProceedingJoinPoint
的getArgs()
获取的数组结果里面还是数组;
总的来说SpringAOP只是概念原理复杂,但是使用上却意外的简单,特别是基于注解(懒人福音),掌握了注解配置,那么在SpringBoot中使用AOP方式是完全一样的。