文章目录
- AOP核心概念
- AOP:切点表达式
- AOP:使用切点表达式@annotation
- 通知分类
- 获取被增强方法相关信息
- 【不使用自动注入】AOP方式一:使用配置文件方法
- 【不使用自动注入】AOP方式二:自定义切点
- 【不使用自动注入】AOP方式三:使用注解方法
导包
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.16</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.8</version>
</dependency>
</dependencies>
AOP核心概念
- Joinpoint(连接点):所谓连接点是指那些可以被增强到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
-
Pointcut(切入点):所谓切入点是指被增强的连接点(方法)
-
Advice(通知/ 增强):所谓通知是指具体增强的代码
- Target(目标对象):被增强的对象就是目标对象
-
Aspect(切面):是切入点和通知(引介)的结合
-
Proxy (代理):一个类被 AOP 增强后,就产生一个结果代理类
切入点、通知以及切面
目标对象
AOP:切点表达式
可以使用切点表达式来表示要对哪些方法进行增强。
写法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略,大部分情况下省略
- 返回值类型、包名、类名、方法名可以使用星号* 代表任意
- 包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类
- 参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表
例如:
execution(* com.sangeng.service.*.*(..)) 表示com.sangeng.service包下任意类,方法名任意,参数列表任意,返回值类型任意
execution(* com.sangeng.service..*.*(..)) 表示com.sangeng.service包及其子包下任意类,方法名任意,参数列表任意,返回值类型任意
execution(* com.sangeng.service.*.*()) 表示com.sangeng.service包下任意类,方法名任意,要求方法不能有参数,返回值类型任意
execution(* com.sangeng.service.*.delete*(..)) 表示com.sangeng.service包下任意类,要求方法不能有参数,返回值类型任意,方法名要求已delete开头
在resouces目录下创建applicationContext.xml配置文件
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 指定要扫描的包-->
<context:component-scan base-package="com.kk"/>
<!-- 开启注解的支持-->
<context:annotation-config/>
<!-- 开启aop注解支持-->
<aop:aspectj-autoproxy/>
</beans>
创建MyAspect.java类
package com.kk.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @author :
* @Date : 2022/4/9
* @Desc :
*/
@Component
@Aspect
public class MyAspect {
// @Pointcut("execution(* com.kk.service.*.*(..))")
// public void pt(){
//
// }
// @Before("pt()")
// public void methodbefore(){
// System.out.println("方法被调用了");
// }
//需要无参才生效
// @Pointcut("execution(* com.kk.service..*.*())")
// public void ptt(){
//
// }
@Pointcut("execution(* com.kk.service.*.delete*(..))")
public void ptt(){
}
@Before("ptt()")
public void methodbeforeppt(){
System.out.println("方法被调用了");
}
}
创建service层,在service层创建UserService和PhoneService
UserService
package com.kk.service;
import com.kk.aspect.InvokeLog;
import org.springframework.stereotype.Service;
/**
* @author : k
* @Date : 2022/4/9
* @Desc :
*/
@Service
public class UserService {
@InvokeLog
public void qx(){
System.out.println("UserService中qx的核心代码");
}
public void deleteAll(){
System.out.println("以delete开头的");
}
}
PhoneService
package com.kk.service;
import com.kk.aspect.InvokeLog;
import org.springframework.stereotype.Service;
/**
* @author : k
* @Date : 2022/4/9
* @Desc :
*/
@Service
public class PhoneService {
@InvokeLog
public void qx(){
System.out.println("PhoneService中qx的核心代码");
}
}
测试:
package com.kk;
import com.kk.service.PhoneService;
import com.kk.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author : k
* @Date : 2022/4/9
* @Desc :
*/
public class Demo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
PhoneService phoneService = context.getBean(PhoneService.class);
UserService userService = context.getBean(UserService.class);
phoneService.qx();
userService.qx();
userService.deleteAll();
}
}
AOP:使用切点表达式@annotation
我们也可以在要增强的方法上加上注解。然后使用@annotation来表示对加了什么注解的方法进行增强。
写法:@annotation(注解的全类名)
例如:
创建一个注解类InvokeLog
package com.kk.aspect;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author : k
* @Date : 2022/4/10
* @Desc :
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface InvokeLog {
}
在MyAspect类中使用此注解类
@Pointcut("@annotation(com.kk.aspect.InvokeLog)")
package com.kk.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @author :
* @Date : 2022/4/9
* @Desc :
*/
@Component
@Aspect
public class MyAspect {
@Pointcut("@annotation(com.kk.aspect.InvokeLog)")
public void ptt(){
}
@Before("ptt()")
public void methodbeforeppt(){
System.out.println("方法被调用了");
}
}
UserSerice以及PhoneService
若哪个类的哪个方法需要使用aop,则在方法上面加 @InvokeLog 即可
UserSerice
package com.kk.service;
import com.kk.aspect.InvokeLog;
import org.springframework.stereotype.Service;
/**
* @author : k
* @Date : 2022/4/9
* @Desc :
*/
@Service
public class UserService {
@InvokeLog
public void qx(){
System.out.println("UserService中qx的核心代码");
}
public void deleteAll(){
System.out.println("以delete开头的");
}
}
PhoneService
package com.kk.service;
import com.kk.aspect.InvokeLog;
import org.springframework.stereotype.Service;
/**
* @author : k
* @Date : 2022/4/9
* @Desc :
*/
@Service
public class PhoneService {
@InvokeLog
public void qx(){
System.out.println("PhoneService中qx的核心代码");
}
}
测试:
package com.kk;
import com.kk.service.PhoneService;
import com.kk.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author : k
* @Date : 2022/4/9
* @Desc :
*/
public class Demo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
PhoneService phoneService = context.getBean(PhoneService.class);
UserService userService = context.getBean(UserService.class);
phoneService.qx();
userService.qx();
userService.deleteAll();
}
}
通知分类
- @Before:前置通知,在目标方法执行前执行
- @AfterReturning: 返回后通知,在目标方法执行后执行,如果出现异常不会执行
- @After:后置通知,在目标方法之后执行,无论是否出现异常都会执行
- @AfterThrowing:异常通知,在目标方法抛出异常后执行
- @Around:环绕通知,围绕着目标方法执行
其他代码不变,只需修改MyAspect中的代码即可
package com.kk.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @author :
* @Date : 2022/4/9
* @Desc :
*/
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* com.kk.service..*.*(..))")
public void ptt(){
}
@Before("ptt()")
public void methodbeforeppt(){
System.out.println("before");
}
@AfterReturning("ptt()")
public void afterReturning(){
System.out.println("afterReturning");
}
@After("ptt()")
public void after(){
System.out.println("after");
}
@AfterThrowing("ptt()")
public void afterThrowing(){
System.out.println("after");
}
//环绕通知非常特殊,它可以对目标方法进行全方位的增强。
@Around("ptt()")
public void around(ProceedingJoinPoint pjp){
System.out.println("around目标方法前");
try {
pjp.proceed();//目标方法执行
System.out.println("around目标方法后");
} catch (Throwable e) {
e.printStackTrace();
}finally {
System.out.println("finally中进行增强");
}
}
}
获取被增强方法相关信息
我们实际对方法进行增强时往往还需要获取到被增强代码的相关信息,比如方法名,参数,返回值,异常对象等。
我们可以在除了环绕通知外的所有通知方法中增加一个JoinPoint类型的参数。这个参数封装了被增强方法的相关信息。我们可以通过这个参数获取到除了异常对象和返回值之外的所有信息。
例如:
@Before("pt()")
public void methodbefore(JoinPoint jp){
Object[] args = jp.getArgs();//方法调用时传入的参数
Object target = jp.getTarget();//被代理对象
MethodSignature signature = (MethodSignature) jp.getSignature();//获取被被增强方法签名封装的对象
System.out.println("Before方法被调用了");
}
案例:
需求:要求让所有service包下类的所有方法被调用前都输出全类名,方法名,以及调用时传入的参数
package com.kk.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author : k
* @Date : 2022/4/13
* @Desc :
*/
@Component
@Aspect
public class PrintLogAspect {
//对哪些方法增强
@Pointcut("execution(* com.kk.service..*.*(..))")
public void pt(){
}
//怎么增强
@Before("pt()")
public void printLog(JoinPoint joinPoint){
//输出 被调用的方法所在的类名 方法名 调用时传入的参数
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String classMethod = joinPoint.getSignature().getDeclaringTypeName();//类名 com.kk.service.UserService
String methodName = signature.getName();//方法名 updateById
Object[] args = joinPoint.getArgs();//调用时传入的参数 556688
System.out.println(classMethod+"=="+methodName+"=="+Arrays.toString(args));
}
}
果需要获取被增强方法中的异常对象或者返回值
则需要在方法参数上增加一个对应类型的参数,并且使用注解的属性进行配置。这样Spring会把你想获取的数据赋值给对应的方法参数。
例如:
package com.kk.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author :
* @Date : 2022/4/9
* @Desc :
*/
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* com.kk.service..*.*(..))")
public void ptt(){
}
@Before("ptt()")
public void methodbeforeppt(JoinPoint joinPoint){
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println(method);//public void com.kk.service.UserService.updateById(java.lang.Integer)
// method.invoke();
System.out.println("before");
}
@AfterReturning(value = "ptt()", returning = "ret")
public void afterReturning(JoinPoint joinPoint,Object ret){
System.out.println("afterReturning");
}
@After("ptt()")
public void after(JoinPoint joinPoint){
System.out.println("after");
}
@AfterThrowing(value = "ptt()", throwing ="t")
public void afterThrowing(JoinPoint joinPoint,Throwable t){
System.out.println("after");
}
}
相信你肯定觉得上面的获取方式特别的麻烦难以理解。就可以使用下面这种万能的方法。
直接在环绕通知方法中增加一个ProceedingJoinPoint类型的参数。这个参数封装了被增强方法的相关信息。
该参数的proceed()方法被调用相当于被增强方法被执行,调用后的返回值就相当于被增强方法的返回值。
例如:
package com.kk.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author :
* @Date : 2022/4/9
* @Desc :
*/
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* com.kk.service..*.*(..))")
public void ptt(){
}
@Around("ptt()")
public void around(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs(); //获取方法参数
MethodSignature signature = (MethodSignature) pjp.getSignature();
Object target = pjp.getTarget(); //获取被增强的对象
try {
Object ret = pjp.proceed();//目标方法的执行
//ret就是被增强方法的返回值
System.out.println(ret);
} catch (Throwable e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
System.out.println(pjp);
}
}
【不使用自动注入】AOP方式一:使用配置文件方法
功能:要求代码在执行的时候添加日志并且打印出日志信息,当不改变业务的源代码
编写两个日志类
Log 实现MethodBeforeAdvice
public class Log implements MethodBeforeAdvice {
/**
* void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
* @param method 要执行的目标对象的方法,获取实现类的名字 target.getClass().getName() ,获取方法的名字method.getName()
* @param args 参数
* @param target 目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
After_Log 实现 AfterReturningAdvice
public class After_Log implements AfterReturningAdvice {
/**
*
* @param returnValue 返回值
* @param method 获取方法的名字method.getName()
* @param args
* @param target
* @throws Throwable
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue);
}
}
applicationContext.xml
将实现类以及两个自定义的log类注入到spring IoC容器中,由spring托管
<!-- 注册bean-->
<bean id="userService" class="com.kk.service.UserServiceImpl"/>
<bean id="log" class="com.kk.log.Log"/>
<bean id="afterLog" class="com.kk.log.After_Log"/>
注意点:需要导入aop的约束:
xmlns:aop=“http://www.springframework.org/schema/aop”
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册bean-->
<bean id="userService" class="com.kk.service.UserServiceImpl"/>
<bean id="log" class="com.kk.log.Log"/>
<bean id="afterLog" class="com.kk.log.After_Log"/>
<!-- 方式一:使用原生的Spring API接口-->
<aop:config>
<!-- 切入点 expression表达式 execution 要执行的位置 就是这个切入点要在哪里执行 -->
<aop:pointcut id="pointcut" expression="execution(* com.kk.service.UserServiceImpl.*(..))"/>
<!-- 这句话的意思就是 我们把这个log类 ( <bean id="log" class="com.kk.log.Log"/>) 切入到这个pointcut 方法里边-->
<!-- advice-ref引用哪一个类进行切入 pointcut-ref将这个类切入到哪里-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
【不使用自动注入】AOP方式二:自定义切点
自定义类 DiyPointCut
public class DiyPointCut {
public void before(){
System.out.println("=======方法执行前======");
}
public void after(){
System.out.println("=======方法执行后======");
}
}
applicationContext.xml
将自定义类 DiyPointCut注入到spring IoC容器中,由spring托管
<!-- 方式二:自定义类-->
<bean id="diy" class="com.kk.diy.DiyPointCut"/>
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册bean-->
<bean id="userService" class="com.kk.service.UserServiceImpl"/>
<!-- 方式二:自定义类-->
<bean id="diy" class="com.kk.diy.DiyPointCut"/>
<aop:config>
<!-- 自定义切面 ref 要引用的类-->
<aop:aspect ref="diy">
<!-- 切入点,就是在哪个地方使用aop,就是这个切入点要在哪里执行-->
<aop:pointcut id="point" expression="execution(* com.kk.service.UserServiceImpl.*(..))"/>
<!-- 通知 method 就是要使用的方法(自定义类 DiyPointCut里边的方法) pointcut-ref这个方法要在哪里执行-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
【不使用自动注入】AOP方式三:使用注解方法
通过注解编写切面
注意:需要使用@Aspect这个注解标注这个类是一个切面
自定义类 AnnotationPointCut
@Aspect //标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.kk.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("=======方法执行前======");
}
@After("execution(* com.kk.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("=======方法执行后======");
}
//在环绕增强中,我们可以给定一个参数 代表我们要处理切入的点
@Around("execution(* com.kk.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
//执行方法
Object proceed = jp.proceed();
System.out.println("环绕后");
}
}
applicationContext.xml
注意:需要开启注解支持
<!--开启注解支持 -->
<aop:aspectj-autoproxy/>
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册bean-->
<bean id="userService" class="com.kk.service.UserServiceImpl"/>
<!-- 方式三:-->
<bean id="annotationPointCut" class="com.kk.diy.AnnotationPointCut"/>
<!--开启注解支持 -->
<aop:aspectj-autoproxy/>
</beans>