首页 > 其他分享 >【Spring第九篇】AOP

【Spring第九篇】AOP

时间:2022-10-20 10:31:34浏览次数:74  
标签:Spring void AOP springframework 第九篇 import org com public


文章目录


​导包​

<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 增强后,就产生一个结果代理类​

​切入点、通知以及切面​

【Spring第九篇】AOP_方法名

​目标对象​

【Spring第九篇】AOP_Spring_02

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开头

【Spring第九篇】AOP_xml_03

​在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();
}
}

【Spring第九篇】AOP_方法名_04

通知分类

  • @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>

【Spring第九篇】AOP_xml_05


标签:Spring,void,AOP,springframework,第九篇,import,org,com,public
From: https://blog.51cto.com/u_15837794/5778762

相关文章

  • SpringBoot项目部署
    我们要想在linux系统上运行这个项目,就要保证他运行所用的端口没有被占用,不然运行就会报错查看端口使用情况netstat-anp|grep9999可以看到这个端口被占用了(没被占用的......
  • 手写自定义springboot-starter,感受框架的魅力和原理
    一、前言Springboot的自动配置原理,面试中经常问到,一直看也记不住,不如手写一个starter,加深一下记忆。看了之后发现大部分的starter都是这个原理,实践才会记忆深刻。核心思......
  • 配置Spring报错:class path resource [applicationContext.xml] cannot be opened beca
    无法打开applicationContext.xml文件问题:classpathresource[applicationContext.xml]cannotbeopenedbecauseitdoesnotexistExceptioninthread"main"org.s......
  • AOP和spring事务
    AOP面向切面编程,在不惊动原始设计的基础上增强功能,叫做无侵入式/无入侵式连接点:所有的方法(所有英雄)切入点:要追加功能的方法(已选择的英雄)通知:共性功能要追加......
  • Spring
    Bean创建的声明周期无参构造方法创建对象依赖注入使用反射判断属性是否有@AutoWried注解,如果有则给属性赋值初始化前使用反射判断方法上是否有@PostConstruct注......
  • SpringBoot+MybatisPlus--使用
    1、在entity包下面创建数据实体类,添加注解@Data,如果和数据库名字不一样的话,还需要+@TableField注解。字段名字不一样也需要添加此注解@TableName(value="user")publi......
  • SpringCloud FeignClient的坑(httpClient连接池的使用)
    SpringCloudFeignClient的坑(httpClient连接池的使用)前言在头条上已经发布过不少的文章了,根据文章的浏览量来看,go语言的市场需求明显是小于java的需求量的,最近也开始发布......
  • SpringBoot对接口请求参数(@RequestBody 和 @ Request Param)进行解密过滤
      /***@Description:拦截所有请求过滤器,并将请求类型是HttpServletRequest类型的请求替换为自定义*/@javax.servlet.annotation.WebFilter(filterName="Web......
  • SpringBoot+MybatisPlus--文件上传
    文件上传时,对页面的form表单有如下要求: 采用post方式提交数据   method="post"采用multipart格式上传文件  enctype="multipart/form-data"使用inp......
  • 手写自定义springboot-starter,感受框架的魅力和原理
    一、前言Springboot的自动配置原理,面试中经常问到,一直看也记不住,不如手写一个starter,加深一下记忆。看了之后发现大部分的starter都是这个原理,实践才会记忆深刻。核心思......