在spring2.0以上版本中,可以使用基于AspectJ注解或者基于xml配置aop。
一、启用AspectJ注解
在spring中启用AspectJ注解支持,一共4步:
- 要在spring应用中使用AspectJ注解,必须在classpath下包含AspectJ类库:aopalliance.jar,aspectj.weaver.jar,spring-aspects.jar。
- 将aop scheam添加到<beans>根元素中。
- 要在spring ioc容器中启用AspectJ注解支持,只要在Bean配置文件中定义一个用的xml元素<aop:aspectj-autoproxy>。
- 当spring ioc容器侦测到bean配置文件中的<aop:aspectj-autoproxy>元素时,会自动为AspectJ切面配置的bean创建代理。
二、实现基本逻辑
这是要导入的包结构,推荐使用maven:
先实现基本逻辑,再考虑加入切面处理:
1 2 3 4 5 6 7 8 9 10 11 12 |
package aopimpl; public interface calculator { int add(int x,int y); int sub(int x,int y); int mul(int x,int y); int div(int x,int y); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package aopimpl; import org.springframework.stereotype.Component; @Component public class calculatorimpl implements calculator{ @Override public int add(int x, int y) { int result = x + y; return result; } @Override public int sub(int x, int y) { int result = x - y; return result; } @Override public int mul(int x, int y) { int result = x * y; return result; } @Override public int div(int x, int y) { int result = x / y; return result; } } |
1 2 |
<!-- 配置自动扫描的包 --> <context:component-scan base-package="aopimpl"></context:component-scan> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package aopimpl; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class main { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); calculator calculator = applicationContext.getBean(calculator.class); System.out.println(calculator.add(2, 3));; System.out.println(calculator.div(466, 233));; } } |
运行结果:
1 2 |
5 2 |
基本的运算实现了。
三、添加日志功能
一共4个步骤。
首先在程序逻辑中寻找横切关注点,因为日志是一个关联的功能相似的横切关注点,所以可以抽取出来做一个切面。
(1)先创建一个日志类
1 2 3 4 5 6 7 8 |
package aopimpl; public class loggingaspect { public void before(){ System.out.println("the method begins"); } } |
(2)如何把这个类设置为切面?
- 首先把这个类放进ioc容器之中(例如:使用注解形式加上一个@Component)。
- 然后把这个类声明为切面(加上@Aspect注解)。
变成了这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package aopimpl; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; // 把这个类声明成一个切面: // 1.把这个切面放进ioc容器中 // 2.再声明为一个切面 @Aspect @Component public class loggingaspect { public void before(){ System.out.println("the method begins"); } } |
光是配置切面还是不够的。
(3)告诉这个日志方法在哪些类的哪些方法开始执行之前执行
加上注解@Before
声明该方法是一个前置通知:声明该方法在目标方法开始之前执行。
1 |
@Before("execution(public int aopimpl.calculator.add(int, int))") |
在什么目标方法开始之前执行。
变成了这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package aopimpl; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; // 把这个类声明成一个切面: // 1.把这个切面放进ioc容器中 // 2.再声明为一个切面 @Aspect @Component public class loggingaspect { // 3.配置方法的运行时机 @Before("execution(public int aopimpl.calculator.add(int, int))") public void before(){ System.out.println("the method begins"); } } |
(4)配置xml文件
使切面中的注解起作用。
想要调用一个目标方法,当这个目标方法与@Before注解声明的方法相一致的时候,aop框架会自动为目标方法所在的类生成代理对象,在调用该目标方法之前把前置方法加进去。
1 2 3 4 5 |
<!-- 配置自动扫描的包 --> <context:component-scan base-package="aopimpl"></context:component-scan> <!-- 使aspectj注解起作用:自动为匹配的类生成代理对象 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> |
运行结果:
1 2 3 |
the method begins 5 2 |
发现日志功能已经成功添加进去了。
(5)加强
但是现在只有the method begins呀,我们的参数到哪里去了,可以加上参数吗?
当然可以,需要引入一个概念叫连接点.
before方法引入连接点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package aopimpl; import java.util.Arrays; import java.util.List; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; //把这个类声明成一个切面: //1.把这个切面放进ioc容器中 //2.再声明为一个切面 @Aspect @Component public class loggingaspect { @Before("execution(public int aopimpl.calculator.add(int, int))") public void before(JoinPoint joinPoint) { String methodname = joinPoint.getSignature().getName(); List<Object> list = Arrays.asList(joinPoint.getArgs()); System.out.println("the method " + methodname + " begins with " + list.toString()); } } |
输出结果:
1 2 3 |
the method add begins with [2, 3] 5 2 |
1.joinpoint 连接点,我理解为代理运行的函数对象
1 |
String methodname = joinPoint.getSignature().getName(); |
- methodname 是方法名称。
- getSignature() 获取签名。
- getName() 获取方法名称。
1 |
List<Object> list = Arrays.asList(joinPoint.getArgs()); |
joinPoint.getArgs()获得函数的参数,是一个Object对象,所以List的泛型输入也要是Object对象。
2.那么问题来了,这里有一个add一个sub操作,怎么只显示一条日志,不是一个函数对应一个日志吗?
那是因为:
@Before注解中只适配了@Before(“execution(public int aopimpl.calculator.add(int, int))”),add这个方法而已。
如果把add改成*,@Before注解就会自动适配calculator下的所有方法。
1 |
@Before("execution(public int aopimpl.calculator.*(int, int))") |
输出结果:
1 2 3 4 |
the method add begins with [2, 3] 5 the method div begins with [466, 233] 2 |
成功输出日志。
四、总结一下
1.加入jar包。
2.在配置文件中加入aop的命名空间(aop),如果导入注解类和bean还需要加入context和bean的命名空间。
3.使用基于注解的方式。
(1)在配置文件加入如下配置
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
(2)把横切关注点的代码抽象到切面的类中(写方法类)
i.首先切面要放在ioc容器中,切面是ioc容器中的bean,即加入@Component注解。
ii.切面还需要加入@Aspect注解。
(3)在类中声明各种通知
i.声明一个方法。
ii.在方法加上通知注解。
一共5种:
- @Before:前置通知,在方法执行之前执行。
- @After:后置通知,在方法执行之后执行。
- @AfterRunning:返回通知,在方法返回结果之后执行。
- @AfterThrowing:异常通知,在方法抛出异常之后执行。
- @Around:环绕通知,围绕着方法执行。
iii.注意注解的时间点。
iiii.如果需要方法细节,可以在方法中声明一个类型为JointPoint的参数,然后就能访问链接细节,方法名称和参数值。