一、什么是面向对象编程
将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
二、AOP术语
通知(Advice):切面的工作被称为通知。定义了切面是什么,何时使用。Spring切面有5中类型的通知(前before,后after,返回after-retuning,异常after-throwing,环绕around);
连接点(Join Point):连接点,也就是可以进行横向切入的位置;
切点(Poincut):通过匹配,确定切面在什么地方执行;
切面(Aspect):切入系统的一个切面。比如事务管理是一个切面,权限管理也是一个切面;
织入(Weaving):把切面应用到目标对象并创建代理对象的过程。可以在编译器、类加载期、运行期。(Spring AOP,就是在与运行期方式织入切面的)。
三、Spring AOP
Spring AOP构建在动态代理(JDK动态代理【默认】,CGLib动态代理【没有接口时使用】)之上,因此,局限于方法拦截。
切点用于准确定位在什么地方应用切面的通知,通知和切点是切面的基本元素。
1、SpringAOP所支持的AspectJ切点指示器:Spring中使用execution指示器来进行实际匹配。还有一些指示器来限制匹配如下。
2、编写切点
对于一个接口UserService:
public interface UserService {
boolean add(User user);
}
切点表达时可以表示如下
3、注解创建切面
①使用@Aspect声明一个类为切面,@Before、@After、@AfterReturning、@AfterThrowing、@Around来声明通知方法,使用@Pointcut来统一命名切点表达式。如下所示:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect //声明一个切面
//@Component
public class UserServiceAop {
//定义一个命名的切点(统一配置)
@Pointcut(value = "execution(* com.caofanqi.service.UserService.add(..))")
public void pointcut(){}
//在执行方法之前执行,可以写,表达式,也可以直接使用pointcut
@Before(value = "execution(* com.caofanqi.service.UserService.add(..))")
public void beforeMethod() {
System.out.println("before...");
}
//目标方法返回或抛出异常调用
@After("pointcut()")
public void afterMethod() {
System.out.println("after...");
}
//目标方法返回后调用
@AfterReturning("pointcut()")
public void aterReturningMethod() {
System.out.println("aterReturning...");
}
//目标方法出异常调用
@AfterThrowing("pointcut()")
public void afterThrowingMethod() {
System.out.println("afterThrowing...");
}
//将目标方法封装(环绕)起来
@Around("pointcut()")
public boolean AroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("AroundBefore...");
boolean proceed = (Boolean) joinPoint.proceed();
System.out.println("AroundAfter...");
return proceed;
}
}
②启动AspectJ注解的自动代理
JavaConfig方式:
@Configuration
@ComponentScan(basePackages = "com.caofanqi")
@PropertySource("classpath:app.properties")
@EnableAspectJAutoProxy //启动自动代理
public class JavaConfig {
//声明切面bean
@Bean
public UserServiceAop userServiceAop(){
return new UserServiceAop();
}
}
如果使用XML来装配bean的话,需要使用Spring aop命名空间的<aop:aspectj-autoproxy /> 开启自动代理。
AspectJ自动代理会为使用@Aspect注解的bean创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean。
4、 切面的执行顺序
如果我们配置了多个切面,而且想要控制先后的执行顺序该怎么办呢?这里可以使用@Order注解指定切面顺序。
我们添加一个LogAop,记录我们调用的方法,如下:
@Aspect
@Component
@Order(1) //切面的执行顺序,数字越小,环绕通知(前)、前置通知先执行,其他相反
public class LogAop {
@Pointcut(value = "execution(* com.caofanqi.service.*.*(..))")
public void logPointcut(){};
@Before("logPointcut()")
public void beforeMethod() {
System.out.println("logAop before...");
}
@After("logPointcut()")
public void afterMethod() {
System.out.println("logAop after...");
}
@AfterReturning("logPointcut()")
public void aterReturningMethod() {
System.out.println("logAop aterReturning...");
}
@AfterThrowing("logPointcut()")
public void afterThrowingMethod() {
System.out.println("logAop afterThrowing...");
}
@Around("logPointcut()")
public boolean AroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("logAop AroundBefore...");
boolean proceed = (Boolean) joinPoint.proceed();
System.out.println("logAop AroundAfter...");
return proceed;
}
}
然后设置UserServiceAop如下:
这时我们测试add方法结果如下:
确实是order越小越是最先执行,但更重要的是最先执行的最后结束。
如果是配置文件的话,可以在 <aop:aspect ref="logAop" order="1"> 上添加order属性。
5、处理通知中的参数
如果我们要在通知中使用被代理方法的参数,要怎么做呢?如下:
被代理方法:
切面:
我们需要关注的是args(friendName)限定符。它表明传递给meetFriend()方法的String类型的参数也会传递到通知中去。参数的名称friendName也与切点方法签名中的参数相匹配。
6、通过注解引入新的功能
如果想在此目标类中再增加一个目标方法是,该怎么办呢?
最简单的办法就是在建立此目标类的时候,增加此方法。但是如果原目标类非常复杂,动一发而牵全身。我们可以为需要添加的方法建立一个类,然后建一个代理类,同时代理该类和目标类。用一个图来表示
图中,A就是原目标类,B就是新添加的方法所在的类,通过建立一个代理类同时代理A和B,调用者调用该代理时,就可以同时A和B中的方法了。 通过@DeclareParents注解就可以实现该功能。
拓展接口和实现类:
public interface PersonService {
void show();
}
@Service
public class PersonServiceImpl implements PersonService {
public void show() {
System.out.println("person ...");
}
}
切面:
@Aspect
@Component
public class PersonInteroducer {
@DeclareParents(value = "com.caofanqi.service.UserService+",defaultImpl = PersonServiceImpl.class)
public static PersonService personService;
}
@DeclareParents由三部分组成:value,指定了那种类型的bean要引入接口;+表示UserService所有子类型。defaultImpl指定为引入功能提供实现的类。静态属性表示要引入的接口。
测试:
使用注解和自动代理让我们创建切面很简单,但是必须有源码,如果没有源码,就要使用XML方式了。如果各位需要用到的话,可以自行百度啦。很简单
源码:https://gitee.com/itcaofanqi/CaoFanqiStudyRepository/tree/master/stuspring