一、简介
1、Aop介绍
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,也是Spring框架的重要内容。AOP是基于IOC容器之上的,能对IOC容器做出更详细的操作。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
2、名词解释
(1)、切面(Aspect):一个横切功能的模块化,也即通知所在的类,它能应用于多个业务类。
(2)、切入点(Pointcut):可以理解为要插入某个方法。
(3)、通知(Advice):通知分为前置通知、后置通知、异常通知、最终通知和环绕通知。其中环绕通知的功能是最强大的。
a、前置通知(Before Advice):在切入点执行之前插入的通知。
b、后置通知(After Returning Advice):在切入点执行完毕之后插入的通知。
c、异常通知(After Throwing Advice):当切入点抛出异常时插入的通知。
d、最终通知(After Finnally Advice):当切入点执行完毕时插入的通知(不论是正常返回还是异常退出)。
e、环绕通知(Around Advice):可以贯穿切入点执行的整个过程,可以获取目标方法的全部控制权(目标方法是否执行、执行前、执行后、参数、返回值等),底层是通过拦截器实现的。
3、个人理解
我个人将实现AOP的类分为业务类(一般情况下的类)与通知类。具体步骤为在通知类中先实现通知,然后编写好业务类,最后通过连接把通知方法与业务方法连接起来。这就是一个面向切面编程程序。通知都能获取到业务方法的各种属性,且获取属性的方式也各有不同,详情请看第三大点。
二、实现通知的三种方式
(一)、通过使用接口实现通知
1、编写类
1)、编写通知类,实现通知类的接口
2)、编写业务类,确定给什么方法添加通知
2、Ioc容器配置
1)、将业务类、通知纳入springIoc容器
2)、确定连接线
3)、定义切入点(连接线的一端)、定义通知类(连接线的另一端)
4)、通过pointcut-ref将两端连接起来
3、不同通知需要实现的接口与方法
1)、前置通知
a、编写MethodBeforeAdvice接口的实现类,
b、重写before方法
2)、后置通知
a、编写AfterReturningAdvice接口的实现类,
b、重写afterReturning方法
3)、异常通知
a、编写ThrowsAdvice接口的实现类,
b、”编写“ afterThrowing方法
public void afterThrowing([Method, args, target], ThrowableSubclass)
4)、环绕通知
a、编写MethodInterceptor接口的实现类,
b、重写invoke方法
4、部分代码
配置代码
<!-- 配置业务类 -->
<!-- addStudent()方法所在的类 -->
<bean id="studentDao" class="impl.StudentDaoimpl">
</bean>
<bean id="studentService" class="service.StudentServicelmpl">
<property name="studentDao" ref="studentDao"></property>
</bean>
<!-- 前置通知类 -->
<!-- 将通知纳入springIoc容器 =============
连接线的一方:业务类的具体方法=============-->
<bean id="LogBefore" class="aop.LogBefore"></bean>
<!-- 将addStudent()和通知进行关联-->
<aop:config>
<!-- 配置切入点(在哪里执行通知)
expression中添加要连接的方法
=============连接线的另一端:通知类=============-->
<aop:pointcut id="pointcut" expression="execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student)) or execution(public void service.StudentServicelmpl.deleteStudentByNo(int))"/>
<!-- ============advisor:连接线,连接切入点和切面的线==============-->
<aop:advisor advice-ref="LogBefore" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
通知类代码
//前置通知
public class LogBefore implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("前置通知即将执行。。。。");
}
}
(二)、使用注解实现通知
1、编写类
1)、编写通知类,在类名前加注解:@Component() 和 @Aspect
@Component():在括号中写下该类的命名,在IOC容器中添加扫描器后就等于将该类纳入IOC容器中。
@Aspect:声明该类是一个通知
2)、编写业务类,确定给什么方法添加通知
2、Ioc容器配置
1)、开启注解对AOP的支持
2)、将该类所在的包导入扫描器中
3、不同的通知需要添加的注解(通知都是通过方法实现的)
1)、前置通知:在方法前添加@Before()注解
2)、后置通知:在方法前添加@AfterReturning()注解
3)、异常通知:在方法前添加@AfterThrowing注解
4)、最终通知:在方法前添加@After注解
5)、环绕通知:在方法前添加@Around注解
()里添加需要连接的方法
(三)、通过配置实现通知
1、概念:也即基于Schema配置,Schema就是头文件
2、实现步骤
1)、编写一个普通类
2)、将该类通过配置转为一个通知
3)、编写一个业务类
4)、在IOC容器中连接两个类
三、具体代码
xml配置文件
spring-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--配置扫描器必须引入的xmlns 属性: xmlns:context="http://www.springframework.org/schema/context" -->
<!--配置aop的通知必须引入的xmlns 属性: xmlns:aop="http://www.springframework.org/schema/aop" -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop">
//现在业务类中注入值
<bean id="student" class="cn.chern.Student">
<property name="stuNo" value="2"></property>
<property name="stuName" value="ls"></property>
<property name="stuAge" value="24"></property>
</bean>
<!--========================================== 接口实现通知 ===============================================-->
<!-- 配置业务类 -->
<!-- addStudent()方法所在的类 -->
<bean id="studentDao" class="impl.StudentDaoimpl">
</bean>
<bean id="studentService" class="service.StudentServicelmpl">
<property name="studentDao" ref="studentDao"></property>
</bean>
<!-- 前置通知类 -->
<!-- 将通知纳入springIoc容器 =============
连接线的一方:业务类的具体方法=============-->
<bean id="LogBefore" class="aop.LogBefore"></bean>
<!-- 将addStudent()和通知进行关联-->
<aop:config>
<!-- 配置切入点(在哪里执行通知)
=============连接线的另一端:通知类=============-->
<aop:pointcut id="pointcut" expression="execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student)) or execution(public void service.StudentServicelmpl.deleteStudentByNo(int))"/>
<!-- ============advisor:连接线,连接切入点和切面的线==============-->
<aop:advisor advice-ref="LogBefore" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
<!-- 后置通知类 -->
<bean id="LogAfter" class="aop.LogAfter"></bean>
<aop:config>
<aop:pointcut id="pointcut2" expression="execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))"/>
<aop:advisor advice-ref="LogAfter" pointcut-ref="pointcut2"></aop:advisor>
</aop:config>
<!-- 异常通知类 -->
<bean id="LogException" class="aop.LogException"></bean>
<aop:config>
<aop:pointcut id="pointcut3" expression="execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))"/>
<aop:advisor advice-ref="LogException" pointcut-ref="pointcut3"></aop:advisor>
</aop:config>
<!-- 环绕通知类 -->
<bean id="LogAround" class="aop.LogAround"></bean>
<aop:config>
<aop:pointcut id="pointcut4" expression="execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))"/>
<aop:advisor advice-ref="LogAround" pointcut-ref="pointcut4"></aop:advisor>
</aop:config>
<!--========================================== 注解实现通知 ===============================================-->
<!-- 配置扫描器 -->
<context:component-scan base-package="aop"></context:component-scan>
<!-- 开启注解对AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--========================================== schema配置实现通知 ===============================================-->
<!-- 将通知纳入springIoc容器
=============连接线的一方:业务类的具体方法=============-->
<bean id="LogSchema" class="aop.LogSchema"></bean>
<!-- 将addStudent()和通知进行关联-->
<aop:config>
<!-- 配置切入点(在哪里执行通知)
=============连接线的另一端:通知类=============-->
<aop:pointcut id="pcSchema" expression="execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))"/>
<aop:aspect ref="LogSchema">
<!-- 连接线:连接业务addStudent 和 通知before -->
<aop:before method="before" pointcut-ref="pcSchema"></aop:before>
<!-- 连接线:连接业务addStudent 和 通知afterReturning -->
<aop:after-returning method="afterReturning" pointcut-ref="pcSchema" returning="returningValue"></aop:after-returning>
<aop:after-throwing method="whenException" pointcut-ref="pcSchema" throwing="e"></aop:after-throwing>
<aop:around method="around" pointcut-ref="pcSchema"></aop:around>
</aop:aspect>
</aop:config>
</beans>
业务类
Student.java
package cn.chern;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Student {
private int stuNo;
private String stuName;
private int stuAge;
public int getStuNo() {
return stuNo;
}
public void setStuNo(int stuNo) {
this.stuNo = stuNo;
}
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public int getStuAge() {
return stuAge;
}
public void setStuAge(int stuAge) {
this.stuAge = stuAge;
}
}
使用接口实现通知的四个java文件
LogBefore.java
package aop;
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("【接口】前置通知即将执行。。。。");
}
}
LogAfter.java
package aop;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class LogAfter implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
//o1为调用的目标对象,method为调用的方法,objects为调用的方法的参数个数,o为方法的返回值
System.out.println("【接口】后置通知:目标对象"+o1+"调用的方法名:"+method.getName()+",方法的参数个数:"+objects.length+"方法的返回值为:"+o);
}
}
LogException.java
package aop;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
public class LogException implements ThrowsAdvice {
//异常通知的具体方法
public void afterThrowing(Method method, Object[] args, Object target, Throwable ex){
System.out.println("【接口】异常通知:目标对象"+target+"调用的方法名:"+method.getName()+",方法的参数个数:"+args.length+",异常类型:"+ex.getMessage());
}
}
LogAround .java
package aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class LogAround implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object result = null;
try{
System.out.println("【接口-环绕】前置通知。。。");
//methodInvocation.proceed()方法前的代码为:前置通知
result = methodInvocation.proceed();//控制着目标方法执行,不写的话目标方法不执行
//methodInvocation.proceed()方法后的代码为:后置通知
System.out.println("【接口-环绕】后置通知:");
System.out.println("目标对象"+methodInvocation.getThis()+"\n调用的方法名:"+methodInvocation.getMethod().getName()+"\n方法的参数个数:"+methodInvocation.getArguments().length+"\n方法的返回值为:"+result);
}catch (Exception e){
//异常通知
System.out.println("【接口-环绕】异常通知。。。。");
}
return result;//目标方法的返回值
}
}
使用注解实现通知
package aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.sql.Array;
import java.util.Arrays;
@Component("LogAnnotation") //等同于将该类纳入springIoc容器中
//@Aspect:声明该类是一个通知
@Aspect
public class LogAnnotation {
//通知与业务类直接连接,无需在springIoc容器配置
//前置通知
//获取目标对象参数需要使用的对象:
//环绕通知为:ProceedingJoinPoint,其他通知为:JoinPoint
@Before("execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))")//属性:定义切点
public void myBefore(JoinPoint jp){
System.out.println("【注解】前置通知:目标对象"+jp.getTarget()+",方法名"+jp.getSignature().getName()+",参数列表"+ Arrays.toString(jp.getArgs()));
}
//后置通知
@AfterReturning(pointcut = "execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))",returning = "returningValue")
public void myAfterreturing(JoinPoint jp,Object returningValue){//returingValue是返回值,但需要告诉spring
System.out.println("【注解】后置通知:目标对象"+jp.getTarget()+",方法名"+jp.getSignature().getName()+",参数列表"+ Arrays.toString(jp.getArgs())+",返回值为:"+returningValue);
}
环绕通知
@Around("execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))")
public void myAround(ProceedingJoinPoint jp){
//方法执行之前:前置通知
System.out.println("【注解-环绕】前置通知");
try{
//方法执行时
jp.proceed();
//方法执行之后:后置通知
System.out.println("【注解-环绕】后置通知");
}catch (Throwable e){
//发生异常时:异常通知
System.out.println("【注解-环绕】异常通知");
}finally {
//最终通知
System.out.println("【注解-环绕】最终通知");
}
}
//异常通知:如果只捕获特定类型的异常,则可以通过第二个参数实现
@AfterThrowing("execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))")
public void myException(){
System.out.println("【注解】异常通知");
}
//最终通知
@After("execution(public void service.StudentServicelmpl.addStudent(cn.chern.Student))")
public void myAfter(){
System.out.println("【注解】最终通知");
}
}
使用Schema配置实现通知
LogSchema .java
package aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import java.lang.reflect.Method;
import java.util.Arrays;
public class LogSchema {
//前置通知方法
public void before() throws Throwable {
System.out.println("【Schema】前置通知");
}
//后置通知方法
public void afterReturning(JoinPoint jp, Object returningValue) throws Throwable {
System.out.println("【Schema】后置通知,目标对象"+jp.getTarget()+",方法名"+jp.getSignature().getName()+",参数列表"+ Arrays.toString(jp.getArgs())+",返回值为:"+returningValue);
}
//异常通知方法
public void whenException(NullPointerException e){
System.out.println("【Schema】异常通知:"+e.getMessage());
}
//环绕通知配置
public void around(ProceedingJoinPoint jp){
//方法执行之前:前置通知
System.out.println("【Schema-环绕】前置通知");
try{
//方法执行时
jp.proceed();
//方法执行之后:后置通知
System.out.println("【Schema-环绕】后置通知");
}catch (Throwable e){
//发生异常时:异常通知
System.out.println("【Schema-环绕】异常通知");
}finally {
//最终通知
System.out.println("【Schema-环绕】最终通知");
}
}
}
主方法
Test.java
package cn.chern;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void testAop(){
ApplicationContext conext = new ClassPathXmlApplicationContext("spring-config.xml");
IStudentService studentService = (IStudentService) conext.getBean("studentService");
Student student = new Student();
student.setStuAge(23);
student.setStuName("zs");
studentService.addStudent(student);
studentService.deleteStudentByNo(1);
}
public static void main(String[] args) {
testAop();
}
}