文章目录
一、AOP简介
1、什么是AOP
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
2、主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等。
3、主要意图
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
4、为什么使用它?
在传统的业务代码中,通常都会进行事务处理等等操作,虽然使用OOP(面向对象编程)可以通过组合或者继承的方式来达到代码的重用,但是如果要实现某个功能(如日志记录),相同的代码仍然会分散到各个方法中。这样,如果想要关闭某个功能,或者要对其进行修改,就必须修改所有相关方法。
这样不但增加了工作量,而且提高了代码的出错率。
AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行的时候再将这些提取的代码应用到需要执行的地方。
说的再简单一点,就是专心写自己的业务代码,当需要其他业务逻辑(类)的时候,直接引入进去就可以,不用担心它们直接存在的联系。
这不但提高了开发效率,而且增强了代码的可维护性。
5、AOP术语
二、AOP的使用
提前导入jar包
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/aspectj/aspectjrt -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.7</version>
</dependency>
//Service接口
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
//接口实现类
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("更新了一个用户");
}
@Override
public void select() {
System.out.println("查询了一个用户");
}
}
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + method.getName() + "方法,返回结果为:" + returnValue);
}
}
public class Log implements MethodBeforeAdvice {
@Override
/*method:要执行的目标对象的方法
*args:参数
*target:目标对象
* 这个方法会在我们执行方法之前提前执行
*/
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行");
}
}
1、原生API接口
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--id用来被引用-->
<bean id="userService" class="com.study.service.UserServiceImpl"/>
<bean id="log" class="com.study.log.Log"/>
<bean id="afterLog" class="com.study.log.AfterLog"/>
<!--方式一,使用Spring原生的API接口-->
<!--配置aop,提前导入aop的约束-->
<aop:config>
<!--切入点:expression:表达式-->
<aop:pointcut id="pointcut" expression="execution(* com.study.service.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试:
//方法一的测试
@Test
public void test1() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//动态代理代理的是接口
UserService userService = (UserService) context.getBean("userService");
userService.add();
}
结果:
2、自定义类
public class DiyPointCut {
public void before(){
System.out.println("方法执行前");
}
public void after(){
System.out.println("方法执行后");
}
}
<!--方式二,自定义类-->
<!--<bean id="diy" class="com.study.diy.DiyPointCut"/>
<aop:config>
<!--自定义切面,ref:要引用的类-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="point" expression="
execution(* com.study.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
测试:
//方法一的测试
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//动态代理代理的是接口
UserService userService = (UserService) context.getBean("userService");
userService.add();
}
结果:
3、注解
@Aspect
@Component //可以使整个类不用注册Bean,但是需要开启扫描包
public class AnnotationPointCut {
@Before("execution(* com.study.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("方法执行前");
}
@After("execution(* com.study.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("方法执行后");
}
//在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
@Around("execution(* com.study.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
jp.getSignature();
Object proceed = jp.proceed();
System.out.println("环绕后");
}
}
<!--方式三-->
<!--下面两行任取一行,结果一样,取第二行需要在类前加注解的元素-->
<bean id="AnnotationPointCut" class="com.study.diy.AnnotationPointCut"/>
<context:component-scan base-package="com.study.diy"/>
<!--开启注解支持-->
<aop:aspectj-autoproxy/>
测试:
//方法三的测试
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//动态代理代理的是接口
UserService userService = (UserService) context.getBean("userService");
userService.select();
}
结果:
三、遇到的问题
1、配置切面
在Spring的配置文件中,配置切面使用的是< aop:aspect>元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的Spring Bean。定义完成后,通过< aop:aspect>元素的ref属性即可引用该Bean。
< aop:aspect>的两个属性:
- id:用于定义切面的唯一标识名称
- ref:用于引用普通的Spring Bean
2、配置切入点
在Spring的配置文件中,配置切入点是通过< aop:pointcut>元素来定义的。
当< aop:pointcut>元素作为< aop:config>元素的子元素定义时,表示该切入点是全局切入点,可以被多个切面共享;
当< aop:pointcut>元素作为< aop:aspect>元素的子元素时,表示该切入点只对当前切面有效。
< aop:pointcut>的两个属性:
- id:用于定义切入点的唯一标识名称
- expression:用于指定切入点关联的切入点表达式
对expression的拓展
< aop:pointcut expression=“execution(* com.study. * . * (…))” id=“pointcut”>execution后面传入的值 就是定义的切入点表达式,该切入点表达式的意思是匹配com.study包下面的任意类方法执行。
其中execution是表达式的主体,第1个 * 表示返回返回类型,使用*代表所有类型
com.study表示的是需要拦截的包名,
后面第二个 * 表示的是类名,使用 * 表示所有的类;
第三个 * 表示的是方法名,使用 * 表示所有方法;
后面的()表示方法的参数,其中的"…"表示任意参数,第一个 * 与包名之间有一个空格
3、配置通知
- pointcut:用于指定一个切入点表达式
- pointcut-ref:指定一个已经存在的切入点名称
- method:指定一个方法名
- throwing:只对< after-throwing>元素有效,用于抛出异常
- returning:只对< after-throwing>元素有效,用于访问目标方法的返回值
4、注解
- @Aspect:定义切面
- @Pointcut:定义切入点表达式
- @Before:定义前置通知
- @AfterReturning:定义后置通知
- @Around:定义环绕通知
- @AfterThrowing:定义异常通知来处理程序中未处理的异常
- @After:定义最终final通知
- @DeclareParents:定义引介通知
5、JointPoint类中的方法
-
joinpoint.getargs():获取带参方法的参数
-
joinpoint.getTarget():获取他们的目标对象信息
-
joinpoint.getSignature():获取被增强的方法相关信息
* getSignature()); 修饰符 + 包名 + 组件名(类名) + 方法的名字
* getSignature().getName()); 方法名
* getSignature().getDeclaringTypeName()); 包名 + 组件名(类名)