Spring基础篇-快速面试笔记(速成版)

news2024/10/7 16:22:36

文章目录

  • 1. Spring概述
  • 2. 控制反转(IoC)
    • 2.1 Spring声明Bean对象的方式
    • 2.2 Spring的Bean容器:BeanFactory
    • 2.3 Spring的Bean生命周期
    • 2.4 Spring的Bean的注入方式
  • 3. Spring的事件监听器(Event Listener)
    • 3.1 Spring内置事件监听器
    • 3.2 自主发布事件与监听
  • 4. Spring的面向切面编程(AOP)
    • 4.1 AOP简介
    • 4.2 静态代理与动态代理
    • 4.3 使用Spring AOP
    • 4.4 Pointcut的声明
  • 参考资料

1. Spring概述

Spring的主要目的是为了简化Java EE的企业级应用开发

Spring框架主要提供了两个基础功能:

  • 控制反转(Ioc):也称为依赖注入(Dependency Injection, DI)
  • 面向切面编程(Aspect Oriented Programming, AOP)

2. 控制反转(IoC)

控制反转(Inversion of Control, IoC):将对象的控制权交由Spring管理,而不是自己管理。体验上就是:在需要用到对象(称为Bean对象)时(例如某个业务处理对象,连接池对象等),不需要自己去new,而是从Spring的容器中直接拿就行了。对于Spring如何初始化对象,可以由开发者指定,也可以使用Spring提供的默认初始化方式。

依赖注入(Dependency Injection):控制反转功能也可以称为依赖注入。即当我们要使用某个对象时,直接通过Spring注入即可,不需要显示的获取。体验上就是:在需要注入的对象变量上增加@Autowired注解

2.1 Spring声明Bean对象的方式

要进行依赖注入bean对象,首先要声明Bean,即告诉Spring如何初始化该Bean。

Spring声明Bean有如下几种方式:

  1. XML 配置方式:通过在 XML 配置文件中使用 <bean> 元素声明对象。(Spring Boot出现后,该方式基本淘汰)

    <bean id="myBean" class="com.example.MyBean"/>
    
  2. 基于注解的方式:使用注解来标记类,告诉 Spring 在运行时将其作为 Bean 进行管理。常用的注解包括 @Component@Service@Repository@Controller

    @Component
    public class MyBean { ... }
    
    • @Component:最通用的注解,用于标记任何类作为 Spring Bean,如工具类或者没有明确分类的类。
    • @Repository:通常用于标记数据访问层(DAO 层)的类。
    • @Service:通常用于标记业务逻辑层(Service 层)的类。
    • @Controller:通常用于标记控制器层的类(如 Spring MVC 中的控制器)。

    这些注解名字不同的原因:①(主要原因)用于语义区分;② Spring会进行一些特殊处理。例如:@Repository将底层的数据访问异常转换为 Spring 的 DataAccessException。

  3. JavaConfig 配置方式:通过 Java 类来配置 Spring Bean。可以使用 @Configuration 注解标记配置类,并在方法上使用 @Bean 注解声明 Bean。

    @Configuration
    public class AppConfig {
        @Bean
        public MyBean myBean() {
            return new MyBean();
        }
        // 可以配置多个Bean。通常将一组相关的Bean配置在一个Config类下
    }
    

    对于@Bean的配置方式,需要通过@ComponentScan注解来启用扫描,这样才能被Spring管理。例如:

    @ComponentScan("com.example")  // 通常加在启动类上
    

2.2 Spring的Bean容器:BeanFactory

当Spring将Bean初始化后之后,需要一个容器来存储这些Bean。这个容器就是BeanFactory

BeanFactory是一个接口,里面定义了几个重要的getBean(...)方法,让用户可以方便的通过名字、XXX.class等方式获取Bean对象。

在SpringBoot的Web程序中,使用的BeanFactory的实现类为AnnotationConfigServletWebServerApplicationContext。它的继承关系如下图:

在这里插入图片描述

从上到下,比较重要的类/接口如下:

  • BeanFactory:最基本的容器接口,提供了最基本的 IoC(Inverse of Control)功能。
  • ApplicationContext:BeanFactory 的子接口,扩展了 BeanFactory 的功能,提供了更完整的国际化、事件传播、资源访问、AOP 支持等功能。
  • WebApplicationContext:专门用于 Web 应用程序的 ApplicationContext 接口。在ApplicationContext的基础上,增加了一些针对 Web 开发的功能,例如 Servlet 上下文的生命周期管理、Servlet 属性的访问、Web 应用中 Servlet 和 Filter 的注册等。
  • ConfigurableWebApplicationContext: WebApplicationContext 的子接口,提供了一些额外的配置和定制能力。
  • ServletWebServerApplicationContext:用于 Web 应用程序的 ApplicationContext 的特定实现类,它与 Servlet 容器集成(例如 Tomcat),负责配置和启动 Servlet 容器,并提供了一些特定于 Servlet 容器的功能,例如设置上下文路径、配置 SSL、处理错误页面等
  • AnnotationConfigServletWebServerApplicationContext:ServletWebServerApplicationContext的子类,使用基于 Java 注解的配置来管理 Web 应用程序的 Bean,通过扫描指定的包(@ComponentScan注解)来查找和注册带有特定注解的 Bean 定义,然后将它们装配到容器中。

在非SpringBoot或非Web程序中,也会用到一些其他的BeanFactory的实现。例如:XmlBeanFactoryFileSystemXmlApplicationContextClassPathXmlApplicationContext

2.3 Spring的Bean生命周期

Spring的Bean从出生到死亡一共会经历以下三个主要阶段:

  1. 实例化(Instantiation):当容器启动时,根据配置文件或注解等方式,Spring会实例化Bean,也就是执行Bean的构造方法
  2. 依赖注入(Dependency Injection):完成实例化后,若该Bean依赖其他的Bean,会将其他的Bean注入进来,因为后续的初始化过程可能会用。
  3. 初始化(Initialization):执行用户定义的初始化方法。有如下方式:① 实现InitializingBean接口并实现其中的afterPropertiesSet()方法;② 在Bean的初始化方法上添加@PostConstruct注解;③ @Bean注解上指定初始化方法
  4. 使用(In Use):初始化结束后,Bean就会存储在Bean容器中,运行期间就可以获取并使用。
  5. 销毁(Destruction):销毁阶段是在Bean不再需要时执行的(通常为正常退出程序时执行)。Spring提供了两种方式来定义销毁方法:① 实现DisposableBean接口并实现其中的destroy()方法;② 在Bean的销毁方法上添加@PreDestroy注解。

示例1(@Component的Bean各个阶段使用注解):

@Component
public class MyBean1 {

    @Autowired
    private RestTemplate restTemplate;

    public MyBean1() {
        System.out.println("MyBean1实例化!restTemplate:" + restTemplate);  // restTemplate为null
    }

    @PostConstruct  // post construct(在构造之后)
    public void init() {
        System.out.println("MyBean1初始化!restTemplate:" + restTemplate);  // restTemplate已经被注入
    }

    @PreDestroy  // pre destroy(在销毁之前)
    public void destroy() {
        System.out.println("MyBean1销毁");
    }

}

输出:

// 启动SpringBoot程序
MyBean1实例化!restTemplate:null
MyBean1初始化!restTemplate:org.springframework.web.client.RestTemplate@5effc15d
// 关闭SpringBoot程序
MyBean1销毁

示例2(@Component的Bean各个阶段使用实现接口):

@Component
public class MyBean2 implements InitializingBean, DisposableBean {

    @Autowired
    private RestTemplate restTemplate;

    public MyBean2() {
        System.out.println("MyBean2实例化!restTemplate:" + restTemplate);  // restTemplate为null
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("MyBean2初始化!restTemplate:" + restTemplate);  // restTemplate已经被注入
    }

    @Override
    public void destroy() {
        System.out.println("MyBean2销毁");
    }
}

输出:

// 启动SpringBoot程序
MyBean2实例化!restTemplate:null
MyBean2初始化!restTemplate:org.springframework.web.client.RestTemplate@5effc15d
// 关闭SpringBoot程序
MyBean2销毁

示例3(@Bean注解):

class MyBean3 {

    @Autowired
    public RestTemplate restTemplate;

    public MyBean3() {
        System.out.println("MyBean3实例化!restTemplate:" + restTemplate);  // restTemplate为null
    }

    private void init() {
        System.out.println("MyBean3初始化!restTemplate:" + restTemplate);  // restTemplate已经被注入
    }

    private void destroy() {
        System.out.println("MyBean3销毁");
    }
}

@Configuration
public class MyBeanConfiguration {

    @Bean(initMethod="init", destroyMethod="destroy")
    public MyBean3 myBean() {
        return new MyBean3();
    }
}

输出:

// 启动SpringBoot程序
MyBean3实例化!restTemplate:null
MyBean3初始化!restTemplate:org.springframework.web.client.RestTemplate@5effc15d
// 关闭SpringBoot程序
MyBean3销毁

对于Bean的生命周期,可能大部分人在网上看到的是这张图:在这里插入图片描述
这张图其实就是把上面的几种方式揉到一起复杂化了。但开发者其实不用关注哪种方式谁先谁后,正常也不会一起用。

2.4 Spring的Bean的注入方式

当我们声明好Bean后,就可以在需要它们的地方进行注入了。Spring为我们提供了以下注入方式:

假设我们有一个配置类配置了如下Bean:

@Configuration
public class TestConfiguration {

    @Bean(name = "restTemplate1")  // 有两个RestTemplate的Bean,名字不同
    public RestTemplate restTemplate1() {
        return new RestTemplate();
    }

    @Bean(name = "restTemplate2")
    public RestTemplate restTemplate2() {
        return new RestTemplate();
    }

    @Bean // 有一个Random类的Bean
    public Random random() {
        return new Random();
    }
}
  1. 使用注解方式注入:通过在代码中使用注解来实现依赖注入,包括 @Autowired@Resource等注解。

    • @Autowired:Spring提供的注解,自动根据类型装配。若需要根据名称,则需要配合@Qualifier注解。
    • @Resource:Java EE提供的注解。可以通过参数来指定根据名称或类型进行注入。
    @Component
    public class MyBean1 {
    
        @Autowired  // 查找Random类型的Bean,必须唯一
        private Random random1;
    
        @Resource(type = Random.class)  // 根据类型注入
        private Random random2;
    
        // @Autowired  // 加入这个启动失败,因为有两个RestTemplate类型的Bean
        // private RestTemplate restTemplate;
    
        @Autowired
        @Qualifier("restTemplate1")  // 根据名称注入
        private RestTemplate restTemplate1;
    
        @Resource(name = "restTemplate2")  // 根据类型注入
        private RestTemplate restTemplate2;
    
        @PostConstruct
        public void init() {
            System.out.println("random1:" + random1);
            System.out.println("random2:" + random2);
            System.out.println("restTemplate1:" + restTemplate1);
            System.out.println("restTemplate2:" + restTemplate2);
        }
    }
    
  2. 使用构造方法注入:直接将你需要注入的Bean配在构造方法上

    @Component
    public class MyBean1 {
    
        private Random random1;
        private Random random2;
        private RestTemplate restTemplate1;
        private RestTemplate restTemplate2;
    
        private MyBean1(Random random1,
                        Random random2,
                        @Qualifier("restTemplate1") RestTemplate restTemplate1,  // 使用@Qualifier指定要注入bean的名称
                        RestTemplate restTemplate2  // 这里居然不报错,且能正常注入。我的版本是spring-beans5.3.9
        ) {
            this.random1 = random1;
            this.random2 = random2;
            this.restTemplate1 = restTemplate1;
            this.restTemplate2 = restTemplate2;
        }
    
        @PostConstruct
        public void init() {
            System.out.println("random1:" + random1);
            System.out.println("random2:" + random2);
            System.out.println("restTemplate1:" + restTemplate1);
            System.out.println("restTemplate2:" + restTemplate2);
        }
    }
    

    输出:

    random1:java.util.Random@738a1324
    random2:java.util.Random@738a1324  // 因为只有一个Random,所以两个是一个Bean
    restTemplate1:org.springframework.web.client.RestTemplate@65a86de0  // restTemplate1
    restTemplate2:org.springframework.web.client.RestTemplate@745e1fb7 // restTemplate2
    
  3. 使用Aware接口注入:Spring提供了一些XxxxxAware的接口,通过这些接口可以注入ApplicationContextBeanFactory等系统Bean。(不过也可以用@Autowired注入)

    @Component
    public class MyBean1 implements ApplicationContextAware, ResourceLoaderAware {
    
        private ApplicationContext applicationContext;
        private ResourceLoader resourceLoader;
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    
        @Override
        public void setResourceLoader(ResourceLoader resourceLoader) {
            this.resourceLoader = resourceLoader;
        }
    
        @Autowired  // 也可以使用@Autowired注入
        private ApplicationContext applicationContext2;
    
        @Autowired
        private ResourceLoader resourceLoader2;
    
        @PostConstruct
        public void init() {
            System.out.println("applicationContext: " + applicationContext);
            System.out.println("resourceLoader: " + resourceLoader);
            System.out.println("applicationContext2: " + applicationContext2);
            System.out.println("resourceLoader2: " + resourceLoader2);
        }
    }
    

    输出:

    // 这里输出的四个对象是同一个,因为在SpringBoot的wen程序中,这ResourceLoader和Application就是同一个实现,即AnnotationConfigServletWebServerApplicationContext
    applicationContext: ... AnnotationConfigServletWebServerApplicationContext@1dc76fa1 ...
    resourceLoader: ... AnnotationConfigServletWebServerApplicationContext@1dc76fa1 ...
    applicationContext2: ... AnnotationConfigServletWebServerApplicationContext@1dc76fa1 ...
    resourceLoader2: ... AnnotationConfigServletWebServerApplicationContext@1dc76fa1 ...
    

3. Spring的事件监听器(Event Listener)

Spring框架为用户提供了一套基于观察者模式的事件发布与监听机制。

事件监听器包含三个部分:

  • 事件(Event):对应观察者模式中的消息。事件发布者发布事件,事件监听者来监听消息。
  • 事件发布者(Event Publisher):对应观察者模式中的主体(被观察者)。通常,Spring会在自己的上下文状态变更的时候发布事件。用户也可以发布自己的事件。
  • 事件监听者(Event Listener):对应观察者模式中的观察者。一般由用户实现,来监听Spring发布的上下文变更事件。

用户想要监听事件,只需要写一个类,继承ApplicationListener<E extends ApplicationEvent>类,其中泛型为要监听的实践。例如:

@Component
public class MyListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("onApplicationEvent:" + event.getSource());
    }
}

3.1 Spring内置事件监听器

Spring为用户内置许多事件监听器,当Spring的上下文等的状态发生变化时,Spring会发布事件。如果用户注册了相应的监听器,就可以进行相应的处理。

你也可以把它理解成Spring生命周期中的钩子函数。

Spring提供的常用内置监听器事件有(ApplicationListener类的):

  • ContextRefreshedEvent监听器:当ApplicationContext被初始化或刷新时触发。这个事件通常用于执行应用程序初始化或者加载缓存等操作。
  • ContextStartedEvent监听器:当ApplicationContext启动时触发。可以在这个事件中执行一些需要在应用程序启动时完成的任务。
  • ContextStoppedEvent监听器:当ApplicationContext停止时触发。可以在这个事件中执行一些清理工作或者资源释放操作。
  • ContextClosedEvent监听器:当ApplicationContext关闭时触发。这个事件通常用于执行一些清理工作或者释放资源的操作。
  • RequestHandledEvent监听器:在Spring MVC中,每次处理HTTP请求完成时都会触发该事件。这个事件通常用于收集请求处理的统计信息或者日志记录。

此外,在SpringBoot等中,还有一些其他的监听器类,例如:

  • ServletContextListener:监听ServletContext的生命周期事件。
  • HttpSessionListener:监听HTTP会话的生命周期
  • ServletRequestListener:监听HTTP请求的生命周期事件,如请求创建和销毁事件。
  • SpringApplicationRunListener:监听Spring Boot应用程序的的生命周期事件。

示例1(ApplicationListener事件):

@Component
public class MyListener1 implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("ContextRefreshedEvent");
    }
}
@Component
public class MyListener2 implements ApplicationListener<ContextClosedEvent> {

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        System.out.println("ContextClosedEvent");
    }
}
@Component
public class MyListener3 implements ApplicationListener<ContextStartedEvent> {

    @Override
    public void onApplicationEvent(ContextStartedEvent event) {
        System.out.println("ContextStartedEvent");
    }
}

输出:

ContextRefreshedEvent
...
ContextRefreshedEvent
// 这里关闭SpringBoot程序
ContextClosedEvent

不同的Spring容器监听的事件不同。例如,在我的SpringBoot程序中,ContextStartedEventContextStoppedEvent是不会被触发的。需要使用ApplicationStartedEventApplicationStoppedEvent事件

示例2(SpringApplicationRunListener):

public class MySpringBootListener implements SpringApplicationRunListener {

	// 构造函数必须这么写
    public MySpringBootListener(SpringApplication application, String[] args) {
        System.out.println("SpringApplicationRunListener constructor");
    }

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        System.out.println("SpringApplicationRunListener starting:" + bootstrapContext);
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        System.out.println("SpringApplicationRunListener environmentPrepared:" + bootstrapContext);
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("SpringApplicationRunListener contextPrepared:" + context);
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("SpringApplicationRunListener contextLoaded:" + context);
    }

    @Override
    public void started(ConfigurableApplicationContext context) {
        System.out.println("SpringApplicationRunListener started:" + context);
    }

    @Override
    public void running(ConfigurableApplicationContext context) {
        System.out.println("SpringApplicationRunListener running:" + context);
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("SpringApplicationRunListener failed:" + context);
    }
}

src/resource/META-INF/spring.factories文件中增加一条配置:

# 写对应路径
org.springframework.boot.SpringApplicationRunListener=com.example.MySpringBootListener

输出:

SpringApplicationRunListener constructor
SpringApplicationRunListener starting:org.springframework.boot.DefaultBootstrapContext@2a898881
SpringApplicationRunListener environmentPrepared:org.springframework.boot.DefaultBootstrapContext@2a898881
SpringApplicationRunListener contextPrepared:org.springframework.context.annotation.AnnotationConfigApplicationContext@585811a4
SpringApplicationRunListener contextLoaded:org.springframework.context.annotation.AnnotationConfigApplicationContext@585811a4
SpringApplicationRunListener started:org.springframework.context.annotation.AnnotationConfigApplicationContext@585811a4
SpringApplicationRunListener running:org.springframework.context.annotation.AnnotationConfigApplicationContext@585811a4

3.2 自主发布事件与监听

除了使用内置的监听器外,我们也可以自己发布事件,然后监听自己的事件。

自己发布事件需要做如下三件事:

  1. 定义事件类:需要继承ApplicationEvent
  2. 在需要的地方发布事件:通过ApplicationEventPublisher.publishEvent(...)方法可以发布事件
  3. 定义监听器监听事件:定义监听类,实现ApplicationListener<你的事件类>

示例:

  1. 定义事件类:

    public class CustomEvent extends ApplicationEvent {
    
        public String anyArg;
    
        public CustomEvent(Object source, String anyArg) {
            super(source);
            this.anyArg = anyArg;
        }
    }
    
  2. 定义监听器:

    @Component
    public class CustomEventListener implements ApplicationListener<CustomEvent> {
        @Override
        public void onApplicationEvent(CustomEvent event) {
            System.out.println("CustomEvent:" + event.anyArg);
        }
    }
    
  3. 在合适的地方发布事件。这里我使用Controller接口进行测试:

    @RestController
    @RequestMapping("test")
    public class TestController {
    
        @Autowired
        private ApplicationEventPublisher eventPublisher;
    
        @GetMapping("/eventTest")
        public void eventTest() {
            eventPublisher.publishEvent(new CustomEvent(this, "myEvent"));
        }
    }
    

当我调用eventTest接口后,控制台就会执行监听器方法,输出:

CustomEvent:myEvent

4. Spring的面向切面编程(AOP)

4.1 AOP简介

面向对象处理的痛点:在面向对象编程中,我们会有许多业务处理模块(例如:创建订单、查询订单、派发工单等)。对于这些模块,需要一些公共的处理逻辑(例如:日志记录、权限校验等),如果为每一个方法都在前后增加额外的代码进行这些逻辑的处理,对业务系统的侵入性太高了,会造成代码的严重耦合。

面向切面编程(Aspect-Oriented Programming,AOP)可以解决上述痛点,其主要思想是通过将横切关注点从业务逻辑中分离出来,然后在需要的时候将其插入到应用程序的特定点上,而不是将其分散在整个应用程序代码中。这样做可以使得应用程序的核心逻辑更加清晰,同时也提高了代码的可维护性和可重用性。

AOP的实现其实就是代理模式,通过一个代理类来调用业务逻辑的方法,从而在其前后增加逻辑处理。

如图所示:

在这里插入图片描述

注意,代理模式会实打实的生成一个代理类。例如,我们有一个XxxxService.class类,若使用了AOP,就会生成一个XxxxService$Proxy.class类。而在调用时,你实际调用的是代理类。

4.2 静态代理与动态代理

Java中要实现AOP(或者说代理模式),通常有两种方式,即静态代理动态代理

  • 静态代理:在编译期就已经确定下来代理类和代理方式。代理类可以是用户自己编写的,也可以使用框架(例如AspectJ)在编译期生成的。
    • 优点:性能高。编译期生成代理类,JVM直接加载就能用。
    • 缺点:不够灵活。无法在运行期更改切入点。
  • 动态代理(Spring AOP采用)利用反射和动态字节码技术(CGLIB库),在运行期动态构建字节码的class文件,以此来生成代理类
    • 优点:灵活。可以运行期随时调整代理类,切入点等。
    • 缺点:性能差(其实还好)。由于是动态生成class类,相比静态代理JVM直接载入类,性能要差一下。不过Spring会在启动时将代理类都生成好,运行时就直接用,所以还好。这也是为什么Spring项目启动慢的原因之一。

4.3 使用Spring AOP

在Spring中使用AOP只需要做以下三点即可:

  1. 定义切面类:新建一个类,作为切面类,用于处理一些事务(例如:日志打印)。需要在类上增加@Component@Aspect注解。
  2. 定义切点(Pointcut):定义该切面需要给“哪些方法”前后增加逻辑。需要使用@Pointcut注解来声明。
  3. 定义通知(Advice):就是定义你要增加的前后逻辑的方法。需要使用@Before@After@Around注解来标注通知方式。

示例:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect  // 告诉Spring这个类是切面类
@Component  // 告诉Spring这个类要被你管理起来
public class LogAspect {

	// 定义切入点,即告诉Spring哪些方法要增加切面。
	// 切点定义支持不同的方式,详见:https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/pointcuts.html
	// 也可以不定义Pointcut,而是直接写到@Before等注解上
    @Pointcut("execution(* com.*.MyService.*(..))")
    public void logPointcut() {
    }
	
	// 前置通知。在方法执行前被调用
    @Before("logPointcut()")  // 指定Pointcut
    public void beforeAdviceLog(JoinPoint joinPoint) {
        System.out.println("beforeAdviceLog");
    }

	// 后置通知。在方法执行后被调用
    @After("logPointcut()")
    public void afterAdviceLog(JoinPoint joinPoint) {
        System.out.println("afterAdviceLog");
    }

	// 环绕通知。自主决定在哪执行方法。
    @Around("logPointcut()")
    public Object aroundAdviceLog(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Around beforeAdviceLog");
        Object result = null;
        try {
            result = joinPoint.proceed();  // 执行业务方法,并保存返回值
        } catch (Throwable throwable) {
            // 切点方法报错的处理。一般会处理后重新抛出去
            System.out.println("Around Exception");
            throw throwable;
        }

        System.out.println("Around afterAdviceLog");
        return result;  // 返回方法执行的返回值
    }

}

业务类如下:

@Service
public class MyService {

    public void doSomething() {
        System.out.println("do something...");
    }

}

当执行业务类的doSomething后的输出:

Around beforeAdviceLog
beforeAdviceLog
do something...
afterAdviceLog
Around afterAdviceLog

通常只需要使用Around就行了

4.4 Pointcut的声明

Pointcut提供了不同的声明方式(称为表达式),几乎覆盖了人们的应用场景。

主要有以下几种(官方链接):

  • execution:按方法签名进行模糊匹配。表达式为:execution(modifiers-pattern? return-type-pattern declaring-type-pattern? method-name-pattern(param-pattern) throws-pattern?)
    • 样例1:execution(public * com.example.service.*.*(..))。匹配com.example.service包下的所有public方法。
    • 样例2:execution(* com.example.service.UserService.*(..))。匹配UserService类的所有方法
    • 样例3:execution(* com.example.service.*.*(java.lang.String, int))。根据方法参数匹配service包下所有匹配该参数的方法。
    • 样例4:execution(* com.example.service.*.*() throws java.io.IOException)。匹配service包下,所有抛出IOException异常的方法。
    • 样例5:execution(public static * com.example.service.*.*(..))。匹配service包下的所有public的静态方法。
  • within:按类进行匹配。表达式为:within(type-pattern)。粗粒度相比execution较粗,建议使用execution。
  • annotation:按注解进行匹配。表达式为:@annotation(annotation-type)
    • 样例1:@annotation(org.springframework.transaction.annotation.Transactional)。匹配加了注解@Transactional注解的方法。
  • bean:按bean名称,匹配该bean下的所有方法。
    • 样例1:bean(accountService*)。匹配bean名称以accountService类下的所有方法。若我们有一个AccountServiceImpl,那么该类下面的所有方法都会被切面。

此外,Pointcut可以使用&&||!表达式。

示例1:

@Pointcut("execution(* com.example.service.*.*(..)) && within(com.example.controller.*)")
@Pointcut("execution(* com.example.service.*.*(..)) || execution(* com.example.dao.*.*(..))")
@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* com.example.service.internal.*.*(..))")





参考资料

  • Spring解密(王福强 著)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1601050.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

开源项目|使用go语言搭建高效的环信 IM Rest接口(附源码)

项目背景 环信 Server SDK 是对环信 IM REST API 的封装&#xff0c; 可以节省服务器端开发者对接环信 API 的时间&#xff0c;只需要配置自己的 App Key 相关信息即可使用。 环信目前提供java和PHP版本的Server SDK&#xff0c;此项目使用go语言对环信 IM REST API 进行封装…

stm32实现hid鼠标

启动CubelMX 选择芯片&#xff08;直接输入stm32f103zet6) 设置时钟 如下图 usb设置 配置usb设备 调试端口设置 配置时钟 项目输出设置 打开工程&#xff08;后记&#xff1a;此工程含有中文不能编译通过) 配置项目 配置调试器 编译无法通过 删除路径中的中文&#xff0c;以及…

facenet人脸检测+人脸识别+性别识别+表情识别+年龄识别的C++部署

文章目录 一. 人脸检测二.人脸识别facenet2.1 训练人脸识别模型2.2 导出ONNX2.3 测试 三.人脸属性&#xff08;性别、年龄、表情、是否戴口罩&#xff09;3.1 训练3.2 导出ONNX3.3 测试 四. 集成应用五、Jetson 部署5.1 NX5.2 NANO 一. 人脸检测 代码位置&#xff1a;1.detect …

Java代码执行顺序

Java代码的执行顺序 后面大量的涉及到了static&#xff0c;我曾经写过一篇static的博客&#xff0c;可以看一眼 我上次写了static的加载顺序&#xff0c;没看过的可以进去看一眼 JavaSE&#xff1a;static关键字详解 ---------------------分割线-------------------------…

✌粤嵌—2024/4/3—合并K个升序链表

代码实现&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ struct ListNode* merge(struct ListNode *l1, struct ListNode *l2) {if (l1 NULL) {return l2;}if (l2 NULL) {return l1;}struct Lis…

安装指定版本的ant-design-vue和指定版本的@ant-design/icons-vue 图标组件包

前言&#xff1a; 最近在完成公司的项目时&#xff0c;为了兼容其他的版本&#xff0c;需要安装指定版本的ant-design-vue和ant-design/icons-vue 图标组件包&#xff0c;安装成功之后&#xff0c;分享如下&#xff1a; 安装命令&#xff1a; ant-design-vue&#xff1a; 不…

[移动通讯]【无线感知-P1】[从菲涅尔区模型到CSI模型-2]

前言&#xff1a; 前面我们学习了&#xff1a; 1 只 基于机器学习做无线感知问题&#xff1a;泛化性能差&#xff0c;可解释性差 无法解释为什么能做好,什么时候能做好,什么时候做不好. 可行性 到 可用性&#xff0c;泛化性问题&#xff0c;具体深入的研究。 2 无线感知的理论…

解决npm run dev跑项目,发现node版本不匹配,怎么跑起来?【已解决】

首先问题点就是我们npm run dev 运行项目的时候发现出错&#xff0c;跑不起来&#xff0c;类型下面这种 这里的出错的原因在于我们的node版本跟项目的版本不匹配 解决办法 我这里的问题是我的版本是node14的&#xff0c;然后项目需要node20的&#xff0c;执行下面的就可以正…

【面试八股总结】排序算法(二)

参考资料 &#xff1a;阿秀 一、堆排序 堆排序基本思想是先把数组构造成一个大顶堆(父亲节点大于其子节点)&#xff0c;然后把堆顶(数组最大值&#xff0c;数组第一个元素)和数组最后一个元素交换&#xff0c;这样就把最大值放到了数组最后边。把数组长度n-1,再进行构造堆把剩…

HackMyVM-Connection

目录 信息收集 arp nmap WEB web信息收集 dirsearch smbclient put shell 提权 系统信息收集 suid gdb提权 信息收集 arp ┌─[rootparrot]─[~/HackMyVM] └──╼ #arp-scan -l Interface: enp0s3, type: EN10MB, MAC: 08:00:27:16:3d:f8, IPv4: 192.168.9.115 S…

【PostgreSQL里insert on conflict do操作时的冲突报错分析】

最近在巡检PostgreSQL的数据库的时候&#xff0c;发现部分数据库里存在大量的如下报错 ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained val…

LRTimelapse for Mac:专业延时摄影视频制作利器

LRTimelapse for Mac是一款专为Mac用户设计的延时摄影视频制作软件&#xff0c;它以其出色的性能和丰富的功能&#xff0c;成为摄影爱好者和专业摄影师的得力助手。 LRTimelapse for Mac v6.5.4中文激活版下载 这款软件提供了直观易用的界面&#xff0c;用户可以轻松上手&#…

第十五届蓝桥杯题解-好数

题目大意&#xff1a;一个数的低位为奇数&#xff0c;次低位为偶数&#xff0c;以此类推的数成为好数&#xff0c;例如&#xff1a;1&#xff0c;3&#xff0c;5&#xff0c;7&#xff0c;9 给定一个n&#xff0c;求1-n所有好数的个数&#xff0c;n<1e7 思路&#xff1a;一…

5_vscode+valgrind+gdb调试程序

需求 项目程序, 读取串口数据, 出现程序崩溃问题valgrind 可以调试定位内存问题: 内存泄漏,非法地址访问,越界访问等内存问题vscode gdb 可视化调试效果, 比命令行简单快捷很多期望使用vscode valgrind gdb 调试程序内存异常, 崩溃退出的问题 环境准备 sudo apt install v…

windows Webrtc +VS2019 (M124)下载编译以及调通测试demo

下载depot tools 设置梯子 git config --global http.proxy 127.0.0.1:10000 git config --global https.proxy 127.0.0.1:10000 下载 $ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 设置depot_tools目录为环境变量 下载webrtc # 设置系统代…

SCADA系统通过巨控GRM模块实现OPC协议远程监控PLC

SCADA系统和PLC不在同一个地方&#xff0c;需要远程监控和控制PLC&#xff0c;可以通过巨控GRM模块来实现&#xff0c;通过OPC协议转巨控服务器远程读写PLC寄存器&#xff0c;从而完成远程监控PLC。 要实现SCAKDA系统远程监控PLC&#xff0c;关键是要实现SKADA能通过互联网访问…

【静态分析】软件分析课程实验-前置准备

课程&#xff1a;南京大学的《软件分析》课程 平台&#xff1a;Tai-e&#xff08;太阿&#xff09;实验作业平台 1. 实验概述 Tai-e 是一个分析 Java 程序的静态程序分析框架&#xff0c;相比于已有的知名静态程序分析框架&#xff08;如 Soot、Wala 等&#xff09;&#xf…

艾体宝方案 | ITT-Profitap IOTA——铁路运输的远程网络捕获和故障排除方案

在移动互联时代&#xff0c;铁路运输的数字化转型已成不可逆转的趋势。然而&#xff0c;随之而来的是对网络连接质量和故障排查的更高要求。本文将探讨如何利用艾体宝Profitap IOTA技术&#xff0c;在火车上实现远程网络捕获和故障排查&#xff0c;助力铁路运输行业迈向智能化未…

OpenStack:开源云计算的崛起与发展

目录 一&#xff0c;引言 二&#xff0c;OpenStack的起源 三&#xff0c;OpenStack的版本演进 四&#xff0c;OpenStack跟虚拟化的区别 五&#xff0c;OpenStack组件介绍 1&#xff09;Horizon介绍 2&#xff09;KeyStone介绍 Keystone 功能概览 Keystone 架构详解 3&a…

51单片机之DS1302实时时钟

1.DS1302时钟芯片介绍 DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时&#xff0c;且具有闰年补偿等多种功能RTC(Real Time Clock)&#xff1a;实时时钟&#xff0c;是一种集成电路&#xff0c;通常称…