一、什么是AOP
AOP(Aspect Oriented Programming):面向切面编程,简单的来说AOP就是在程序运行过程中将某段代码动态的切入到指定方法的指定位置进行运行,即在不影响原来业务逻辑的基础上扩展新的功能.
二、为什么需要使用AOP
AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果.比如我们最常见的就是日志记录了,举个例子,我们现在提供一个查询学生信息的服务,但是我们希望记录有谁进行了这个查询.如果按照传统的OOP(Object Oriented Programming)实现的话,我们定义一个查询学生信息的服务接口(StudentInfoService)并且定义一个实现类(StudentInfoServiceImpl.java)实现该接口,手动的在该实现类中添加相关的日志记录就可以了.
但是,假如我们要实现的服务有多个呢?那就要在每个实现的类都添加这些记录过程,这样做的话就会有点繁琐,而且每个实现类都与记录服务日志的行为紧耦合,违反了面向对象的规则.那么怎样才能把记录服务的行为与业务处理过程中分离出来呢?看起来好像就是查询学生的服务自己在进行,但实际上却是背后进行相关的日志记录,并且查询学生的服务不知道存在这些记录过程,这就是我们要讨论AOP的目的,使用AOP主要是将辅助功能(日志记录、异常处理、性能统计等)和核心功能(业务逻辑)进行有效的分离,能够更好的进行解耦合.
三、AOP中的相关知识点
1、连接点(JoinPoint):可以被增强的方法
2、切入点(PointCut):实际被增强的方法
3、通知(Advice):具有增强功能的代码块
通知可以分为下面五种:
@Before:前置通知,在目标方法调用之前执行
@After:后置通知,在目标方法调用之后执行,并且该方法一定会被执行,类似于异常处理中的finally
@AfterReturning:返回通知,在目标方法正常返回之后执行
@AfterThrowing:异常通知,在程序出现异常之后执行
@Around:环绕通知,功能上大致等于上面四个通知之和
4、切面(Aspect):是一个动作,即把通知应用到切入点上的过程
5、切点表达式:指导通知应该切入到哪个包,哪个类,哪个方法中
语法: excution([权限修饰符] [返回类型] [包类全路径] [方法名] ([参数列表]))
一般情况下权限修饰符、返回值类型不写,使用通配符 * 来代替
举例一:对com.spring01.xiaomaomao.dao.UserServiceImpl类中的add()方法进行增强
excution(* com.spring01.xiaomaomao.dao.UserServiceImpl.add(..))
举例二:对com.spring01.xiaomaomao.dao.UserServiceImpl类中的所有方法进行增强
excution(* com.spring01.xiaomaomao.dao.UserServiceImpl.*(..))
举例三:对com.spring01包及其子包下所有类中的所有方法进行增强
excution(* com.spring01..*(..))
四、AOP的底层原理(这个后面再写...)
1、当存在接口的时候使用的动态代理
2、当没有接口的时候使用的是cglib代理
五、AOP注解方式的实现
1、创建一个UserDao接口
public interface UserDao {
public abstract void insert(int a);
public abstract void delete(int a, int b);
public abstract void update(int a, int b, int c);
public abstract String query(int a, int b, int c, int d);
}
2、创建UserDao接口的实现类
// 将UserDaoImpl的对象注入IOC容器中,交由Spring管理
@Repository
public class UserDaoImpl implements UserDao {
@Override
public void insert(int a) {
System.out.println("执行插入操作...");
}
@Override
public void delete(int a, int b) {
System.out.println("执行删除操作...");
}
@Override
public void update(int a, int b, int c) {
System.out.println("执行修改操作...");
}
@Override
public String query(int a, int b, int c, int d) {
System.out.println("执行查询操作...");
return "小毛毛测试Spring AOP!!!";
}
}
3、创建spring的配置类
// @Configuration标记这是一个Spring的配置类
@Configuration
// 开启注解扫描,扫描com.spring01包及其子包下的所有注解
@ComponentScan(value="com.spring01")
// 开启切面编程功能
@EnableAspectJAutoProxy()
public class SpringConfiguration {
}
4、创建一个通知类
// 将通知类交由Spring容器进行管理 @Component // 把当前类标记为一个切面 @Aspect // 定义一个通知类 public class Advice { // 前置通知,在目标方法调用之前执行前置通知 @Before("execution(* com.spring01.dao..*(..))") // JoinPoint jointPoint 可以获取到方法的参数信息,方法签名等,签名里有定义方法名,声明类型等信息 public void before(JoinPoint jointPoint) { String funName = jointPoint.getSignature().getName(); Object[] funArgs = jointPoint.getArgs(); System.out.println("前置通知: " + funName + "方法开始执行,所使用的参数是:" + Arrays.asList(funArgs)); } // 后置通知,在目标方法调用之后执行后置通知,并且后置通知一定会执行,类似于异常处理中的finally @After(value = "execution(* com.spring01.dao..*(..))") public void after(JoinPoint joinPoint) { System.out.println("后置通知: " + joinPoint.getSignature().getName() + "方法执行结束,所使用的参数是:" + Arrays.asList(joinPoint.getArgs())); } // 返回通知,当目标方法正常返回之后执行返回通知 // 方法上有必须要定义一个参数Object result(这里之所以定义为Object类型,主要是为了可以接收任意类型的返回值,因为你不能确定返回值到底是什么类型的) // 在注解后面还需要定义returning = "result"是为了告诉容器这个方法的返回值是result @AfterReturning(value = "execution(* com.spring01.dao..*(..))", returning = "result") public void afterReturning(JoinPoint joinPoint, Object result) { System.out.println("返回通知: " + joinPoint.getSignature().getName() + "方法正常执行并返回,返回值是:" + result); } // 异常通知,当程序出现异常以后执行异常通知 // 方法上有必须要定义一个参数Exception exception(这里之所以定义为Exception类型,主要是为了可以接收任意类型的异常) @AfterThrowing(value = "execution(* com.spring01.dao..*(..))", throwing = "exception") public void afterThrowing(JoinPoint joinPoint, Exception exception) { System.out.println("异常通知: " + joinPoint.getSignature().getName() + "方法出现了异常,异常信息是:" + exception.getCause()); } // 环绕通知 @Around(value = "execution(* com.spring01..*(..))") public Object around(ProceedingJoinPoint pjp) { String funName = pjp.getSignature().getName(); Object[] args = pjp.getArgs(); Object proceed = null; try { System.out.println("环绕通知前置:"); //类似于invoke方法,获取参数,拿到目标方法的返回值 proceed = pjp.proceed(args); System.out.println("环绕通知正常返回,返回值是:" + proceed); } catch (Throwable throwable) { System.out.println("环绕通知异常:"); throwable.printStackTrace(); } finally { System.out.println("环绕通知后置:"); } return proceed; } }
5、测试类
public class TestSpringAnnotation {
@Test
public void testSpringAnnotation() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
UserDao userDao = context.getBean("userDaoImpl", UserDao.class);
userDao.insert(1);
System.out.println("-------------------------------");
userDao.delete(2,3);
System.out.println("-------------------------------");
userDao.update(4,5,6);
System.out.println("-------------------------------");
userDao.query(7,8,9,10);
System.out.println("-------------------------------");
}
}
6、测试结果
环绕通知前置: 前置通知: insert方法开始执行,所使用的参数是:[1] 执行插入操作... 返回通知: insert方法正常执行并返回,返回值是:null 后置通知: insert方法执行结束,所使用的参数是:[1] 环绕通知正常返回,返回值是:null 环绕通知后置: ------------------------------- 环绕通知前置: 前置通知: delete方法开始执行,所使用的参数是:[2, 3] 执行删除操作... 返回通知: delete方法正常执行并返回,返回值是:null 后置通知: delete方法执行结束,所使用的参数是:[2, 3] 环绕通知正常返回,返回值是:null 环绕通知后置: ------------------------------- 环绕通知前置: 前置通知: update方法开始执行,所使用的参数是:[4, 5, 6] 执行修改操作... 返回通知: update方法正常执行并返回,返回值是:null 后置通知: update方法执行结束,所使用的参数是:[4, 5, 6] 环绕通知正常返回,返回值是:null 环绕通知后置: ------------------------------- 环绕通知前置: 前置通知: query方法开始执行,所使用的参数是:[7, 8, 9, 10] 执行查询操作... 返回通知: query方法正常执行并返回,返回值是:小毛毛测试Spring AOP!!! 后置通知: query方法执行结束,所使用的参数是:[7, 8, 9, 10] 环绕通知正常返回,返回值是:小毛毛测试Spring AOP!!! 环绕通知后置: -------------------------------
六、其它注意点
1、从上面的例子中可以看出切点都重复了,碰到这种情况可以定义可重复切点,具体步骤如下:
//随意定义一个返回值类型为void的方法,在其上加上@Pointcut(value = "execution(* com.xiaomaomao..*.*(..))")注解,
//引入的时候只需要引入该方法名即可
@Pointcut(value = "execution(* com.spring01.dao..*(..))")
public void pointCutExpression() {
}
@Before("pointCutExpression()")
public void before(JoinPoint jointPoint) {
String funName = jointPoint.getSignature().getName();
Object[] funArgs = jointPoint.getArgs();
System.out.println("前置通知: " + funName + "方法开始执行,所使用的参数是:" + Arrays.asList(funArgs));
}
@Component
@Aspect
@Order(1)
public class PersonProxy{
}