spring boot aop 学习笔记_joinpoint获取请求路由-程序员宅基地

技术标签: aop  java  springBoot  

 

1.什么是AOP

   AOP是面向切面编程的思想,而Spring AOP是这种思想的技术实现!

  AOP采用"横切"的技术,剖解开封装的对象内部,将影响了多个类的公共行为封装到一个可重用模块。将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

2.OOP 与AOP的区别

  AOP(Aspect Oriented Programming),即面向切面编程。众所周知,OOP(面向对象编程)通过的是继承、封装和多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。OOP从纵向上区分出一个个的类来,而AOP则从横向上向对象中加入特定的代码。

3.AOP的优点

  1、面向切面编程使得每个关注点都集中于一个地方而不是分散在多处代码中,便于后期的统一维护管理。

  2、服务模块更简洁,它们只包含主要关注点,而次要关注点的代码被转移到切面中了。

  3、对原方法进行方法增强,且不影响原方法的正常使用。

  4、使用简单可插拔的配置,在实际逻辑执行之前、之后或周围动态添加横切关注点。

4.Spring AOP的一些概念

viewpoint: 在运行时的某个或某些对象的某个或某些方法(如,method)的前面或后面,或是是这些方法抛出异常时,我要通过

     AOP实现增加一段执行代码。

1)连接点(Join point)

连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时,抛出异常时,甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

viewpoint: 连接点就是哪些地方,我们可能去通过AOP插入一段执行代码。可以认为是类中的方法。

2) 切点(Pointcut)

切点定义了在何处工作,也就是真正被切入的地方,也就是在哪个方法应用通知。切点的定义会匹配通知所有要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。

viewpoint: AOP通过正则匹配到的一个或多个链接点的集合。

3) 通知(Advice)

通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。

Spring切面可以应用5种类型的通知:

前置通知(Before):在目标方法被调用之前调用通知功能

后置通知(After):在目标方法完成之后调用通知,不关心方法的输出是什么。是“返回通知”和“异常通知”的并集。

返回通知(After-returning):在目标方法成功执行之后调用通知

异常通知(After-throwing):在目标方法抛出异常后调用通知

环绕通知(Around)通知包裹了被通知的方法,可同时定义前置通知和后置通知。

viewpoint: 对于那些匹配到的切点(方法)之前,之后,异常时 我们加入一些额外的动作,比如打印日志,性能判断等等。

4) 切面(Aspect)

切面是通知和切点的结合的集合。通知和切点共同定义了切面的全部内容——它是什么,在何时和何处完成其功能。比如事务管理是一个切面,权限管理也是一个切面。

5.spring AOP 的一个demo

   问题1:实现过程分那几部?

   问题2:5种类型的通知的顺序怎样?

step1:maven引入jar 坐标

     <dependency>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-starter-aop</artifactId>  
     </dependency>  

step2:创建个controller (此时是结合web接口的,这不是必须的,根据需求而定)

@RestController
public class HandlerTestController {

    @RequestMapping(value="aopTest",method = RequestMethod.GET)
    public String showMessage(HttpServletRequest request,String message) throws Exception {
//        if(true){
//            throw new Exception("抛出异常");
//        }
        return "message:"+message;
    }

}
 step3: 创建一个aspect切面类 
package com.example.studyspringboot.studyboot.controller;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
//参考文档
//https://blog.csdn.net/qq_36582604/article/details/80502070?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-2
/**
 * web请求日志切面类---专门针对控制层,如谁被请求了,花了多少时间,请求发送的参数,返回得值等
 *
 * @author lps
 */
@Aspect     // 表示一个切面bean
@Component  // bean容器的组件注解。虽然放在contrller包里,但它不是控制器。如果注入service,但我们又没有放在service包里
@Order(3)   // 有多个日志时,ORDER可以定义切面的执行顺序(数字越大,前置越后执行,后置越前执行)
public class WebLogAspect {
    //定义切入点
    /*1、execution 表达式主体
	  2、第1个* 表示返回值类型  *表示所有类型
	  3、包名  com.*.*.controller下
	  4、第4个* 类名,com.*.*.controller包下所有类
	  5、第5个* 方法名,com.*.*.controller包下所有类所有方法
	  6、(..) 表示方法参数,..表示任何参数
	  */
    @Pointcut("execution(public * com.*.*.*.controller.*.*(..))")
    public void weblog() {
    }

    @Before("weblog()")
    public void dobefore(JoinPoint joinPoint) {        //方法里面注入连接点
        System.out.println("---@Before---");
    }

    //环绕通知,环绕增强,相当于MethodInterceptor
    @Around("weblog()")
    public Object arround(ProceedingJoinPoint pjp) {
        Object result = null;
        try {
            System.out.println("@Around before");
            result = pjp.proceed();
            System.out.print("执行结果");
            System.out.println(result != null ? result : "");
            System.out.println("@Around return");//执行结果
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("@Around exception:" + e);
        }
        return result;
    }

    //方法的返回值注入给ret
    @AfterReturning(returning = "ret", pointcut = "weblog()")
    public void doafter(Object ret) {
        System.out.println("---@AfterReturning---");
    }
    //后置异常通知
    @AfterThrowing("weblog()")
    public void throwss(JoinPoint jp) {
        System.out.println("---@AfterThrowing---");
    }

    //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
    @After("weblog()")
    public void after() throws Throwable {//JoinPoint jp
        System.out.println("---@After---");
    }
}

运行结果1---没有异常

@Around before
---@Before---
---@AfterReturning---
---@After---
执行结果message:yes
@Around return

运行结果2---存在异常

@Around before
---@Before---
java.lang.Exception: 抛出异常
---@AfterThrowing---
---@After---
@Around exception:java.lang.Exception: 抛出异常

增强注解的参数说明

   除了@Around外,每个方法里都可以加或者不加参数JoinPoint,如果有用JoinPoint的地方就加,不加也可以,JoinPoint里包含了类名、被切面的方法名,参数等属性,可供读取使用。

   @Around参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。

   @AfterReturning方法里,可以加returning = “XXX”,XXX即为在controller里方法的返回值。

   @AfterThrowing方法里,可以加throwing = "XXX",供读取异常信息,

5个增强的注解是不是都要用上呢?

viewpoint: 我个人感觉没必要。视情况而定,一个@Around就可以概括所有(不能使用JoinPoint,

                 如果想获取相关信息不能只用它)。

        如果只想在切点前面加增强直接用@Before;

        如果只想在切点后面加增强直接用@After;兼顾@AfterThrowing和@After

        如果只想在切点抛出异常后加增强直接用@AfterThrowing;

        如果只想在切点(方法)执行成功后加增强直接用 @AfterReturning;

       考虑到切点(方法)的返回值,则需要用到@AfterReturning或者@Around。

JoinPoint 中可以获取哪些信息呢?

 参考文档: https://blog.csdn.net/u014683187/article/details/89395844

step4:对比step3 现在将日志功能加上去

package com.example.studyspringboot.studyboot.utils;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
//参考文档
//https://blog.csdn.net/qq_36582604/article/details/80502070?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-2
/**
 * web请求日志切面类---专门针对控制层,如谁被请求了,花了多少时间,请求发送的参数,返回得值等
 * @author lps
 */
@Aspect     // 表示一个切面bean
@Component  // bean容器的组件注解。虽然放在contrller包里,但它不是控制器。如果注入service,但我们又没有放在service包里
@Order(3)   // 有多个日志时,ORDER可以定义切面的执行顺序(数字越大,前置越后执行,后置越前执行)
public class WebLogAspect {

    //定义日志记录器--获取log4j包下提供的logger

    Logger logger = Logger.getLogger(Object.class);
    ThreadLocal<Long> startTime = new ThreadLocal<>();  //线程副本类去记录各个线程的开始时间


    //定义切入点
	/*1、execution 表达式主体
	  2、第1个* 表示返回值类型  *表示所有类型
	  3、包名  com.*.*.controller下
	  4、第4个* 类名,com.*.*.controller包下所有类
	  5、第5个* 方法名,com.*.*.controller包下所有类所有方法
	  6、(..) 表示方法参数,..表示任何参数
	  */
    @Pointcut("execution(public * com.*.*.*.controller.*.*(..))")
    public void weblog() {

    }

    @Before("weblog()")
    public void dobefore(JoinPoint joinPoint) {        //方法里面注入连接点
        System.out.println("---@Before---"); //info ,debug ,warn ,erro四种级别,这里我们注入info级别
        startTime.set(System.currentTimeMillis());
        //获取servlet请求对象---因为这不是控制器,这里不能注入HttpServletRequest,但springMVC本身提供ServletRequestAttributes可以拿到
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        logger.info("请求路径::" + request.getRequestURL().toString());         // 想那个url发的请求
        logger.info("请求方式:" + request.getMethod());
        logger.info("调用方法:" + joinPoint.getSignature().getDeclaringTypeName() + "."
                + joinPoint.getSignature().getName());                     // 请求的是哪个类,哪种方法
        logger.info("方法参数:" + Arrays.toString(joinPoint.getArgs()));     // 方法本传了哪些参数
    }
    //环绕通知,环绕增强,相当于MethodInterceptor
    @Around("weblog()")
    public Object arround(ProceedingJoinPoint pjp) {
        Object result=null;
        try {
            System.out.println("@Around before");
            result =  pjp.proceed();
            System.out.print("执行结果");
            System.out.println(result!=null?result:"");
            System.out.println("@Around return");//执行结果
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("@Around exception:"+e);
        }
        return result;
    }

    //方法的返回值注入给ret
    @AfterReturning(returning = "ret", pointcut = "weblog()")
    public void doafter(Object ret) {
        System.out.println("---@AfterReturning---");
        logger.info("相应内容:" + ret);       // 响应的内容---方法的返回值responseEntity
        logger.info("执行时间(ms):" + ( System.currentTimeMillis()-startTime.get() ));
    }
    //后置异常通知
    @AfterThrowing("weblog()")
    public void throwss(JoinPoint jp){
        System.out.println("---@AfterThrowing---");
    }

    //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
    @After("weblog()")
    public void after() throws Throwable {//JoinPoint jp
        //Object result  =  pjp.proceed();
        System.out.println("---@After---");
    }


}

Spring AOP的底层实现方式:一种是JDK动态代理,另一种是CGLib的方式。

JDK动态代理有一个限制,就是它只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,如何创建动态代理实例哪?答案就是CGLib。

CGLib采用底层的字节码技术,全称是:Code Generation Library,CGLib可以为一个类创建一个子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑

JDK动态代理是面向接口的。

CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败)。

使用注意:

如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);

如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理。

Spring boot 项目的默认代理方式。

参考文档:https://blog.csdn.net/weixin_43167418/article/details/103900670?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-3


 

  1. Spring 5.x 中 AOP 默认依旧使用 JDK 动态代理。

  2. SpringBoot 2.x 开始,为了解决使用 JDK 动态代理可能导致的类型转化异常而默认使用 CGLIB。

  3. 在 SpringBoot 2.x 中,如果需要默认使用 JDK 动态代理可以通过(properties配置文件)配置项spring.aop.proxy-target-class=false来进行修改,proxyTargetClass配置已无效(启动类上加@EnableAspectJAutoProxy(proxyTargetClass = false))。

  4. 查看当前spring boot 项目的 spring 和spring boot版本

    @Test
    public void method01() {
        String version = SpringVersion.getVersion();
        String version1 = SpringBootVersion.getVersion();
        System.out.println("SpringVersion==>"+version);
        System.out.println("SpringBootVersion==>"+version1);
    }
  5. spring boot 项目中如果通过配置文件设置spring.aop.proxy-target-class=false则会出现一些麻烦

    假设,我们有一个UserServiceImplUserService类,此时需要在UserContoller中使用UserService。在 Spring 中通常都习惯这样写代码:

    @Autowired
    UserService userService;

在这种情况下,无论是使用 JDK 动态代理,还是 CGLIB 都不会出现问题。

但是,如果你的代码是这样的呢:

@Autowired
UserServiceImpl userService;

 

这个时候,如果我们是使用 JDK 动态代理,那在启动时就会报错:

启动报错

因为 JDK 动态代理是基于接口的,代理生成的对象只能赋值给接口变量。

而 CGLIB 就不存在这个问题。因为 CGLIB 是通过生成子类来实现的,代理对象无论是赋值给接口还是实现类这两者都是代理对象的父类。

SpringBoot 正是出于这种考虑,于是在 2.x 版本中,将 AOP 默认实现改为了 CGLIB。

 

 

实际应用的思考

  

因此要解决几个方面的问题

   item1:需要了解切点的更多匹配方式:

   参考文档:https://blog.csdn.net/qq_23167527/article/details/78623639

    @Pointcut("execution(public * com.*.*.*.controller.*.*(..))")
    public void weblog() {

    }

   相关切点匹配的详细信息请参考:https://www.cnblogs.com/zhangxufeng/p/9160869.html

   item1.1 通配符

  • *通配符,该通配符主要用于匹配单个单词,或者是以某个词为前缀或后缀的单词。

       如下示例表示返回值为任意类型,在com.spring.service.BusinessObject类中,并且参数个数为零的方法:

       execution(* com.spring.service.BusinessObject.*())
 

       下述示例表示返回值为任意类型,在com.spring.service包中,以Business为前缀的类,并且是类中参数个数为零方法:

        execution(* com.spring.service.Business*.*())

  • .. 通配符,该通配符表示0个或多个项,主要用于declaring-type-pattern和param-pattern中,如果用于declaring-type-pattern中,则表示匹配当前包及其子包,如果用于param-pattern中,则表示匹配0个或多个参数。
  • +通配符:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。

     如下示例表示匹配返回值为任意类型,并且是com.spring.service包及其子包下的任意类的名称为businessService的方法,而且该方法不能有任何参数:

     execution(* com.spring.service..*.businessService())

       这里需要说明的是,包路径service..*.businessService()中的..应该理解为延续前面的service路径,表示到service路径为止,或者继续延续service路径,从而包括其子包路径;后面的*.businessService(),这里的*表示匹配一个单词,因为是在方法名前,因而表示匹配任意的类。

       如下示例是使用..表示任意个数的参数的示例,需要注意,表示参数的时候可以在括号中事先指定某些类型的参数,而其余的参数则由..进行匹配:

     execution(* com.spring.service.BusinessObject.businessService(java.lang.String,..))

 item1.2 组合切入点表达式
       AspectJ使用 且(&&)、或(||)、非(!)来组合切入点表达式。

 item1.3 切点匹配的实际使用

参考文档 :https://blog.csdn.net/qq_23167527/article/details/78623639

1.3.1 +通配符的使用
@Pointcut("within(com.example.studyspringboot.studyboot.service.impl.ProxyTestImpl+)")
private void weblog() {
}
接口为ProxyTest 实现类为ProxyTestImpl,另个类ProxyTestImpl1继承ProxyTestImpl。此时用两个类分别调用自己的方法都可被拦截,然后增强,包括ProxyTestImpl1的自己的非接口实现方法也可以被增强。

如果只是within(com.example.studyspringboot.studyboot.service.impl.ProxyTestImpl1) 则只会增强ProxyTestImpl1的自己的非接口实现方法,集成自ProxyTestImpl的方法不会增强。

如果只是within(com.example.studyspringboot.studyboot.service.impl.ProxyTestImpl)则只增强ProxyTestImpl的所有方法。而子类ProxyTestImpl1中的方法都不会增强。

注意:new 出来的对象不会被拦截(匹配),所以需要注解@Autowired或者@Resource.关于多个实现类的注入方法参考一下文档:
https://www.cnblogs.com/leeego-123/p/10882069.html

1.3.2within:使用“within(类型表达式)”匹配指定类型内的方法执行;

within(cn.javass..*)   cn.javass包及子包下的任何方法执行

within(cn.javass..IPointcutService+)    cn.javass包或所有子包下IPointcutService类型及子类型的任何方法

within(@cn.javass..Secure *)  持有cn.javass..Secure注解的任何类型的任何方法。必须是在目标对象上声明这个注解,在接口上声明的对它不起作用

1.3.3:@DeclareParents 想代理类中添加方法

 

@DeclareParents

       @DeclareParents也称为Introduction(引入),表示为指定的目标类引入新的属性和方法。非常类似让目标了去实现一个没有抽象方法,只有默认方法或属性的接口。

关于@DeclareParents的原理其实比较好理解,因为无论是Jdk代理还是Cglib代理,想要引入新的方法,只需要通过一定的方式将新声明的方法织入到代理类中即可,因为代理类都是新生成的类,因而织入过程也比较方便。如下是@DeclareParents的使用语法:

 step1:

@Aspect
@Component
public class IntroductionAspect {
    @DeclareParents(value = "com.example.studyspringboot.studyboot.service.impl.AspectServiceImpl", defaultImpl = DescriptImpl.class)
    private IDescript iDescript;
}
现在想把DescriptImpl类(属性,方法)植入到类AspectServiceImpl中去

step2:DescriptImpl的接口IDescript  (必要)
public interface IDescript {
    String name="xiaoming";
    String tell(String msg);
}

step3:DescriptImpl  (必要)

public class DescriptImpl implements IDescript {
    @Override
    public String tell(String msg) {
        String res = msg!=null? msg:"hello world";
        return res;
    }
}

step4.调用

@RequestMapping("introduction")
public String  intro(){
    IDescript iDescript =  (IDescript) aspectServiceImpl;
    String res = iDescript.tell("this is introduction");
    System.out.println(iDescript.name);
    return res;
}

引出的问题:如何为植入的方法进行增强? 使用this()切点表达式匹配

1.3.4  this(M)与args(M)没测试出来有什么区别?下面以this为例

  如 this(cn.javass.spring.chapter6.service.IPointcutService)

注意:M不能是通配符。

M可以使类,普通接口,引入接口(如IDescript)。

匹配的范围。

 比如接口AspectService    实现类AspectServiceImpl  继承类AspectServiceImpl1

   this(AspectServiceImpl) 则匹配了AspectServiceImpl 的对象和AspectServiceImpl1的对象(包括非实现方法)以及引入的aspectServiceImpl中的方法都会被增强。

   这说明this(M)匹配了M、M的子类,以及M的引入接口(对象)。

1.3.5 args(M)

args:使用“args(参数类型列表)”匹配当前执行的方法传入的参数为指定类型的执行方法;注意是匹配传入的参数类型,不是匹配方法签名的参数类型;参数类型列表中的参数必须是类型全限定名,通配符不支持;args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用;

args (java.io.Serializable,..)

任何一个以接受“传入参数类型为 java.io.Serializable” 开头,且其后可跟任意个任意类型的参数的方法执行,args指定的参数类型是在运行时动态匹配的。

 例如:

    类型M

@Component  交给spring容器
public class Apple {
    public String getMessage(String msg){
        return msg;
    }
}

 M为参数的方法所在的类

@Component 交给spring容器
public class OptionApple {
    public void optionApple(Apple apple,String op){
        System.out.println(apple.getMessage("xxyyzz"));
    }
    public void other(String str){
        System.out.println(str);
    }
}

调用

//测试args optionApple对象和apple对象 通过注解@Autowired 注入,不要去new 
@RequestMapping("args")
public Object argsTest(){
    optionApple.optionApple(apple,"abc");
    return "args";
}

切点

@Aspect
@Component
@Order(100)
public class CutPointTest02 {
 @Pointcut("args(com.example.studyspringboot.studyboot.utils.aspect.args.Apple,..)&&within(com..*)")
    private void advicedemo(){
    }
    @Before("advicedemo()")
    private void doBefore(){
        System.out.println("---this is before---");
    }
}

注意 @Pointcut("args(com.example.studyspringboot.studyboot.utils.aspect.args.Apple,..)&&within(com..*)")后面要加限定,不然启动报错。

1.3.6@within(M):使用“@within(注解类型)”匹配所以持有指定注解类型中的方法;

M是注解的全路径

注解类型也必须是全限定类型名;

@within cn.javass.spring.chapter6.Secure)

任何目标对象对应的类型持有Secure注解的类方法;

必须是在目标对象上声明这个注解,在接口上声明的对它不起作用.

step1自定义注解

//测试注解用于标记类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnoWithin {
}

step2标记目标类

@Component  //交给spring容器
@AnnoWithin
public class AnnotationWithin {
    public String getMessage(){
        return "test @Within";
    }
}

step3切点

@Pointcut("@within(com.example.studyspringboot.studyboot.utils.aspect.selfAnnotation.AnnoWithin)")

step4调用

//@within 测试
@RequestMapping("withiin")
public Object annotationWithin(){
    annotationWithin.getMessage();//annotationWithin 为@Autowired注入spring中AnnotationWithin的对象
    return "@withiin";
}

1.3.7 @target(M)和@within(M)一样也是通过注解匹配类对象,匹配表达式后要加限制,不然报错。

@Pointcut("@target(com.example.studyspringboot.studyboot.utils.aspect.selfAnnotation.AnnoWithin)&&within(com..*)")

注解类型M也必须是全限定类型名; 

任何目标对象持有AnnoWithin注解的类方法;

必须是在目标对象上声明这个注解,在接口上声明的对它不起作用

1.3.8@args(M):使用“@args(注解列表)”匹配当前执行的方法传入的参数持有指定注解的执行;

注解类型M也必须是全限定类型名;

args(M,..) 中的M是类型,@args(M,..)中的M是注解,但是这个注解M会去标记一些类,当某个方法中的参数对应含有该类型就会被匹配。例如,当M标注在类People上 ,则@args(M,..) 会匹配到method(People people,String sign)。

1.3.9@annotation(M):使用“@annotation(注解类型)”匹配当前执行方法持有指定注解的方法;

注解类型M也必须是全限定类型名;

@annotation(cn.javass.spring.chapter6.Secure ) 当前执行方法上持有注解 cn.javass.spring.chapter6.Secure将被匹配
用法很简单,自定义一个注解放在某个方法上,然后切点匹配,就可以实现增强。

1.3.10 bean(M)   根据Bean id 和名字通配符来匹配java bean 中的所有方法

使用“bean(Bean id或名字通配符)”匹配特定名称的Bean对象的执行方法;Spring ASP扩展的,在AspectJ中无相应概念;

模式

描述

bean(*Service)

匹配所有以Service命名(id或name)结尾的Bean

step1 普通的类

@Component("beanPeople")//不加的话默认peopleClass 即类名首字母小写
public class PeopleClass {
    public String getMessage(){
        return "test bean匹配";
    }
}

step2 切点

@Pointcut("bean(beanPeople)")

step3 调用

public class AspectController02 {

      @Qualifier("beanPeople")

      @Autowired private PeopleClass peopleClass;

   // bean 测试
   @RequestMapping("bean")
   public Object beanTest(){
      String res =  peopleClass.getMessage();
       return res;
      }   
}

item1.4  获取当前代理对象

有一个这样的问题? 

目标对象 Subje  被代理了,代理对象假设为ProxySubject,则目标对象中的方法A,和方法B都被增强,Controller曾可以通过注解注入Subje的代理对象,然后调用方法A,发现A被增强了。调用方法B,发现B也被增强了。但是如果,方法A中调用了方法B,则这个调用过程中只有A被增强,B的调用没有倍增强。

原因是:A调用B,其实B的前面有一个默认的this指向的是目标对象,而不是代理对象。有一种解决方案,就是此时A中B 的调用改为当前代理对象的调用,当然这种方式并不好。

怎样获取当前方法的代理对象?

step1.在启动类上,先暴露代理对象,通过加注解@EnableAspectJAutoProxy(exposeProxy = true)

step2.

public String showNumber(){

   ((AspectServiceImpl) AopContext.currentProxy()).calculate(12);
    return null;
}

其中calculate(12) 方法和showNumber()在同一个类中,并且该类的对象被代理。((AspectServiceImpl) AopContext.currentProxy())可以获取代理对象。AspectServiceImpl为当前类名。

关于log4j的相关知识参考:https://blog.csdn.net/qq_40331861/article/details/106815436

学习参考文档:

1.https://blog.csdn.net/qq_36582604/article/details/80502070?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-2

2.https://www.cnblogs.com/chenziyu/p/9547343.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_40331861/article/details/106845483

智能推荐

关于结构体里面的字符串指针输出出现乱码的问题与结构体数组作为参数传值的问题_指针指向字符型后面总是乱码怎么回事-程序员宅基地

文章浏览阅读8.4k次,点赞5次,收藏25次。首先,结构体里面的字符串指针输出出现乱码的问题:相信很多遇到过这种问题的人都跟我一样写过这样的代码(好吧,或许不一样。。。):#include#includetypedef struct{ int age; const char* name; const char* id;}person;person p();int main(){ person s[1]; s[0]=_指针指向字符型后面总是乱码怎么回事

python练手经典100例-10 个最值得 Python 新人练手的有趣项目-程序员宅基地

文章浏览阅读810次。原标题:10 个最值得 Python 新人练手的有趣项目 作者 | Claire D. Costa编译 | Wendy有很多 Python 新手留言问:"Python 入门很久了,但项目经验很少,有没有什么项目,可以让自己实践一下呢?”这是个很普遍的问题,首先你要想好做什么类别的项目,总体来说,项目分为三类:Web构建一个 Web 应用发布在网络上让用户访问使用。需要实现 Web 应用的..._python练习项目

踩坑: Uncaught (in promise) TypeError: Object(...) is not a function at eval_ncaught typeerror: object(...) is not a function a-程序员宅基地

文章浏览阅读3.3w次,点赞20次,收藏20次。前言: 在使用UI自动化测试平台时,有不顺手的功能,自己尝试新增functions时,踩到了这个坑里,花了几个小时(昨天还是没弄懂的情况)作者:变优秀的小白Github:关注YX-XiaoBai爱好:Americano More Ice !QQ学习交流群(new): 811792998啥坑?报错Error: Uncaught (in promise) TypeError: Object(...) is not a function at eval解决事出有因: 原来是 export ._ncaught typeerror: object(...) is not a function at eval

ES 向量检索 dense_vector 类型_华为云 es 不支持dense_vector]-程序员宅基地

文章浏览阅读332次。ES 向量检索 dense_vector 类型(官方文档没有这个介绍。)_es vector_佟印龙的博客-程序员宅基地_华为云 es 不支持dense_vector]

Python Cookbook学习记录_python cookbook 浮点数取整-程序员宅基地

文章浏览阅读119次。3.数字、日期和时间3.1对数值进行取整#简单的取整, 四舍五入print(round(1.23, 1))print(round(1.27, 1))print(round(-1.27, 1))print(round(1.25361, 3))#位数化为零a = 1627731#后一位取零 1627730print(round(a, -1))#后两位取零 1627700pri..._python cookbook 浮点数取整

php循环a-z字母表-程序员宅基地

文章浏览阅读327次。ord — 返回字符的 ASCII 码值说明int ord ( string $string )返回字符串 string 第一个字符的 ASCII 码值。该函数是 chr() 的互补函数。chr — 返回指定的字符说明string chr ( int $ascii )返回相对应于 ascii 所指定的单个字符。<?php for($..._php a-z子母包

随便推点

接口幂等性要怎么防止_怎么防止幂等-程序员宅基地

文章浏览阅读372次。接口的幂等性是什么?直接百度就可以出来,百度百科中是这么解释幂等的,在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。那么在我们平时开发的时候这种接口幂等性遇到的多么?我经历的这几家公司差不多每次都会遇到这种因为多次操作导致接口不幂等的情况,每次也很无奈的去直接操作数据库,把多余的数据删除了。前端时间,我所负责的一个业务就出现了接口频繁操作,导致数据重复的情况,而且持续了好长一段时间,测试大佬们也每天都在催着我解决这个问题。等我看完代码的时候我发现,我在核心的代码里面_怎么防止幂等

百度地图应用之将google坐标转成百度坐标_怎样把手机谷歌定位换成百度定位-程序员宅基地

文章浏览阅读817次。百度地图 将google坐标转成百度坐标_怎样把手机谷歌定位换成百度定位

Firefly单卡复刻Vicuna-13B,Open LLM榜单略高0.2分-程序员宅基地

文章浏览阅读157次。来自:YeungNLP进NLP群—>加入NLP交流群01前言在过去的几个月里,经过不断的迭代更新,Firefly项目已支持对LLaMA-2、Ziya、Baichuan、InternLM、LLaMA-1、Bloom等模型进行指令微调,并且开源了多个经过中文指令微调的模型权重,获得了很多同学的支持。目前本项目在Github上获得了1100+star️,感谢大家的关注和支持。此前,我们花了较多精...

lseek函数--Linux文件管理之文件IO(三)_lseek(fd, 0, seek_set);-程序员宅基地

文章浏览阅读369次。头文件#include<unistd.h>功能用来度量从文件开始出计算的字节数,通常读写都是从文件的当前文件偏移量处开始,并且会在读写完成之后更新偏移量的位置正常打开一个文件的时候偏移量都0,如果指定了参数O_APPEND,会将偏移量移动到最后函数原型off_t lseek(int filedes, off_t offset, int whence)参数说明1、filedes: 文件描述符2、whence:SEEK_SET:设置文件偏移量为offset,从_lseek(fd, 0, seek_set);

VMware安装银河麒麟高级服务器操作系统V10_虚拟机vmware安装非桌面银河麒麟系统-程序员宅基地

文章浏览阅读451次。近期,随着国产信创的持续发热和大众关注度的上升,我们公司决定与时俱进,针对国产技术趋势进行相应的策略调整。为确保我们的业务与技术发展能够紧密结合,我们选定了银河麒麟高级服务器操作系统V10版本作为部署和开发的首选环境。如果有意向了解或使用银河麒麟系统,使用银河麒麟系统需要到官网进行申请,官网地址:https://www.kylinos.cn/_虚拟机vmware安装非桌面银河麒麟系统

JAVA基础知识_在java中调用方法没放到方法体内,直接放到类里了。 记住执行语句只能存放在方法体-程序员宅基地

文章浏览阅读1.5k次,点赞3次,收藏5次。持续更新中……目录)JAVA基础知识笔记一、入门注释class和public class二、基本知识标识符关键字字面值变量作用域数据类型JAVA基础知识笔记一、入门注释注释:出现在源程序中,解释说明。分为:单行注释多行注释javadoc注释:被javadoc.exe工具提取形成帮助文档,比较专业的注释。类体中不能直接编写java语句,除了声明变量以外。class和public class一个java源文件中可以定义多个class,一个java源文件当中不一定有public class_在java中调用方法没放到方法体内,直接放到类里了。 记住执行语句只能存放在方法体