AOP介绍
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。它是OOP的延续,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等。
主要意图
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
from:百度百科
一些相关的名词
- 通知:即你想实现的一些功能,如安全、事物、日记等,其中通知的类型有前置通知,后置通知,环绕通知,异常通知等。
- 连接点:个人理解是能够使用通知的地方;
- 切点:基于连接点上,你需要实现通知的的位置即为切点。
- 切面:由通知和切点组合而成。
AOP基本配置
AOP的测试基于IOC容器上,所以基础jar包为IOC容器测试时候的jar包,而除此之外还需要导入的特定jar包有:
- aopalliance.jar
- aspectjweaver.jar
简单的前置通知实现:
前置通知生成:
实现:一个普通的类 + 特定接口 -> 有特定功能的类的过程;
logBefore.java
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
//普通类->前置通知
public class logBefore implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("前置通知。。。。。。");
}
}
建立StudentAop.java和Student.java
//StudentAop.java
package org.eaxm;
public class StudentAop {
public void addStudent(int stuNo){
System.out.println("增加学生中。。。");
}
public void deleteStudent(int stuNo){
System.out.println("删除学生....");
}
}
//Student.java
package org.eaxm;
public class Student {
private int StuId;
private String name;
private int age;
private int sex;
public Student(){}
public Student(int stuId, String name, int age, int sex) {
StuId = stuId;
this.name = name;
this.age = age;
this.sex = sex;
}
public Student(int sex){this.sex = sex;}
public Student(String name){this.name = name;}
public int getStuId() {
return StuId;
}
public void setStuId(int stuId) {
StuId = stuId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
容器中配置关联:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
<!--本次用到的-->
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvn="http://www.springframework.org/schema/tool"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tool http://www.springframework.org/schema/tool/spring-tool.xsd
<!--本次用到的-->
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
">
<bean id="student1" class="org.eaxm.Student">
<property name="sex" value="2"></property>
<property name="age" value="3"></property>
<property name="name" value="fhggh"></property>
<property name="stuId" value="2019"></property>
</bean>
<!--addStudent方法所在类-->
<bean id="studentaop" class="org.eaxm.StudentAop"></bean>
<!--前置通知类-->
<bean id="logbefore" class="org.aop.logBefore">
</bean>
<!--将addStudent()方法与前置通知关联-->
<aop:config>
<!--配置切入点(在哪里执行通知)-->
<aop:pointcut id="pointcut"
expression="execution(public void org.eaxm.StudentAop.addStudent(org.eaxm.Student)) or execution(public void org.eaxm.StudentAop.deleteStudent(int))"/>
<!--advisor:相当于链接切入点和切入面-->
<aop:advisor advice-ref="logbefore" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
</beans>
其中如果在多个地方配置通知,则用execution(…) or execution(…)进行设置,而要注意返回值,参数要写全类名。
测试:
public static void testAop() {
ApplicationContext context = new ClassPathXmlApplicationContext("org/conf/applicationContext.xml");
Student student = (Student) context.getBean("student1");
StudentAop studentAop = (StudentAop) context.getBean("studentaop");
studentAop.addStudent(student);
studentAop.deleteStudent(13);
}
可以看出在StudentAop类的addStudent方法和deleteStudent方法处设置了前置通知,在执行到该方法之前会先执行相应的通知再执行该方法。
各个通知建立时需要的接口
建立不同通知时实现的接口是不一样的,如上面的前置通知实现的是MethodBeforeAdvice接口,而后置通知、异常通知、环绕通知所要实现的接口如下图表格所示:
表达式expression常见示例
以上只是在StudentAop类的两个方法上设置了通知,所以只有执行到该类的两个方法的时候才会调用通知,而通过表达式expression的设置可以让通知设置在特定的条件下关联。
通过以上的设置,可以批量设置通知,而不用一个方法一个方法的关联。
后置通知也是如此,只是实现的接口不一样而已,而两者通知中重写的方法以下:
//前置通知接口
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {}
//其中method为当前通知匹配的执行方法,objects为方法参数列表,o为引用的类对象
//后置通知接口
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object target){}
//前面第一个为方法返回值,而后面三个与上面的一一对应
以上是反射及动态代理的相关内容,通过getname()或者getclass().getname()可以获得相应的方法名和类名。
异常通知
异常通知虽然接口实现时不需要重写方法,但根据异常通知接口的定义可以发现,异常通知的实现类 必须编写以下方法:
public void afterThrowing([Method, args, target], ThrowableSubclass):
a.public void afterThrowing(Method, args, target, ThrowableSubclass)
b.public void afterThrowing( ThrowableSubclass)
注:中括号的意思是要么里面三个参数同时出现,要么同时不出现,不允许三个不完全出现的情况。
如果有兴趣可以打开ThrowsAdvice可以看到接口的要求的,部分要求截图如下:
异常通知的测试:
// 配置异常通知:LogException.java
package org.aop;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
public class LogException implements ThrowsAdvice {
//异常通知的具体方法
//一定注意不要漏了public
public void afterThrowing(Method method,Object[] arg,Object target,Throwable ex){
System.out.println("异常通知:目标对象:"+target.getClass().getSimpleName()+",方法名:"+method.getName()+ ",方法返回值:"
+method.getReturnType().getSimpleName()+ ",方法的参数个数"+arg.length+",异常类型:"+ex.getMessage());
}
}
进行链接配置:
<!--异常通知-->
<bean id="exceptionLog" class="org.aop.LogException"></bean>
<aop:config>
<!--切入点(连接线的一端)-->
<aop:pointcut id="exceptionpoint"
expression="execution(public void org.eaxm.StudentAop.addStudent(org.eaxm.Student))
or execution(public void org.eaxm.StudentAop.deleteStudent(int))"/>
<aop:advisor advice-ref="exceptionLog" pointcut-ref="exceptionpoint"></aop:advisor>
</aop:config>
异常通知只有在异常才能出现,所以在deleteStudent()方法中我修改如下:
public void deleteStudent(int stuNo){
int x = 15/stuNo; //当stuNo为0时出现异常
System.out.println(“删除学生…”);
}
测试方法:
public static void testAop() {
ApplicationContext context = new ClassPathXmlApplicationContext("org/conf/applicationContext.xml");
Student student = (Student) context.getBean("student1");
StudentAop studentAop = (StudentAop) context.getBean("studentaop");
studentAop.addStudent(student);
studentAop.deleteStudent(0); //分母为0出现异常
}
环绕通知
- 环绕通知是一个具有很强功能的通知,环绕通知实际上可以实现以上所有通知的功能;在目标方法的前后、异常发生时、最终等各个地方都可以 进行的通知,可以获取目标方法的 全部控制权(目标方法是否执行、执行之前、执行之后、参数、返回值等);
- 而且在使用环绕通知时,目标方法的一切信息 都可以通过invocation参数获取到
环绕通知 底层是通过拦截器实现的。
- 建立通知:
//环绕通知:logAround.java
package org.aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class logAround implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try{
System.out.println("基于环绕通知的【前置通知】。。。");
//invocation.proceed()之前的代码:前置通知
Object result = invocation.proceed();//控制目标方法的执行,如addStudent()
// result为目标方法addStudent()方法的返回值
//invocation.proceed()之后的代码:后置通知
System.out.println("基于环绕通知的【后置通知】。。。");
System.out.println("【后置通知】目标对象:" +invocation.getThis().getClass().getSimpleName() +"调用方法名:" +invocation.getMethod().getName()
+"参数个数:"+invocation.getArguments().length +",返回类型:"+invocation.getMethod().getReturnType());
}catch (Exception e){
//在catch里的代码:异常通知
System.out.println("基于环绕通知的【异常通知】。。。"+e);
}
return null;
}
}
- 配置链接:
<!--环绕通知-->
<bean id="aroundlog" class="org.aop.logAround"></bean>
<aop:config>
<aop:pointcut id="aroundpoint"
expression="execution(public void org.eaxm.StudentAop.addStudent(org.eaxm.Student))
or execution(public void org.eaxm.StudentAop.deleteStudent(int))"/>
<aop:advisor advice-ref="aroundlog" pointcut-ref="aroundpoint"></aop:advisor>
</aop:config>
- 测试
public static void testAop() {
ApplicationContext context = new ClassPathXmlApplicationContext("org/conf/applicationContext.xml");
Student student = (Student) context.getBean("student1");
StudentAop studentAop = (StudentAop) context.getBean("studentaop");
studentAop.addStudent(student);
studentAop.deleteStudent(0);
}
- 结果
当把 Object result = invocation.proceed();一行注掉的时候,还是会出现前置通知和后置通知,但不会出现异常通知。而当在环绕通知的最后改成return “aaa”;而后置通知打印参数中的返回值,返回值不会是null,而是aaa,原理是基于拦截器上,有兴趣可以自己敲一下;另附运行结果图:
注:上面method.getReturnType().getSimpleName()和invocation.getMethod().getReturnType()得到的是返回类型,而后置通知中的第一个参数Object o和环绕通知中的result得到的是返回值。
此文章是个人的Spring学习笔记,用于之后的温习回顾。。。。。。