SpringBoot是如何自动实现IOC和AOP的


一、概念解析(AOP & IOC/DI)

本文主要是通过代码实现Spring Boot中的IOC和AOP配置。一些概念性的东西可以去我的博客了解。这里的AOP和IOC是属于Spring容器框架的范畴。和SpringBoot关系不大,因为Spring Boot的初中是整合简化了Spring和Spring MVC的开发。

1、关于IOC/DI的概念分析(简单全面)。☞点击链接前往;

这一片文章是多年前在学习使用Spring的过程中整理的。里面包括了很多大牛的整理总结,是这一篇文章使我对这些生硬的概念有了深入的理解。

2、关于AOP的概念解析 ☞点击链接前往

这是来源于《简书》一个作者的文章分享。结合概念与实例的分析,让你比较生动形象的理解和使用,这里感谢作者的分享。

二、在项目中SpringBoot中式如何去配置IOC的

这里标题可能有点会产生歧义,这里旨在介绍在项目开发中是如何配置IOC的(注重的是功能使用,而不是Spring Boot底层的源码阅读)。

1、概念扩展:在Spring&Spring中我们式如何注册Bean的?

在使用Spring,Spring MVC中我们使用通常头两种方式:

1)、基于XML的配置方式;

打个比方: 假如使用Spring基于XML的配置的话,开发的基本流程就是: Java Bean实体编写——>XML(配置Bean)

<bean id="helloWorld" class="com.test.spring.beans.HelloWorld">
	        <property name="name" value="Spring"></property>
	</bean>

2)、基于注解的方式配置;

举个例子: 假如使用基于注解的配置方式的话,首先需要在Spring配置文件中开启注解扫描,当然也可以写Java配置文件,添加@Configuration用于开启注解扫描。这里方便起见就使用配置文件(XML)的方式做演示。然后开启注解扫描也有两种不同的配置方式:

而自动装配实现就需要注解扫描,这时发现了两种开启注解扫描的方式,即**context:annotation-config/和context:component-scan**

<context:annotation-config>:注解扫描是针对已经在Spring容器里注册过的Bean

<context:component-scan>:不仅具备<context:annotation-config>的所有功能,还可以在指定的package下面扫描对应的bean

在使用的过程中通常用: context:component-scan,可以定义扫描的具体位置。

<context:component-scan base-package="com.test"/>

在以上配置文件中开启注解扫描机制之后呢,以后的Bean可开发就可以直接使用注解的方式定义Bean了(@Service)。使用@Autowired注入已经注册的Bean。前提条件就是要保证添加注解的package在XML配置的包内,否则可能导致Bean未注册的情况。

2、SpringBoot默认扫描路径问题:

一般来说spring boot默认的扫描路径是启动类当前的包和子包。不在自动扫描路径下,需要修改自定义扫描包路径。

@SpringBootApplication
@EnableTransactionManagement(proxyTargetClass = true)
@MapperScan(basePackages = {"com.frame.springboot.dao", "com.frame.springboot.base"})
public class SpringbootApplication {
 
 
    public static void main(String[] args) {
 
        SpringApplication app = new SpringApplication(SpringbootApplication.class);
        app.addListeners(new MyApplicationStartedEventListener());
        app.run(args);
 
    }
 
    static class MyApplicationStartedEventListener implements ApplicationListener<ApplicationStartedEvent> {
        private Logger logger = LoggerFactory.getLogger(MyApplicationStartedEventListener.class);
 
        @Override
        public void onApplicationEvent(ApplicationStartedEvent event) {
            SpringApplication app = event.getSpringApplication();
            app.setBannerMode(Banner.Mode.OFF);// 不显示banner信息
            logger.info("==MyApplicationStartedEventListener==");
        }
    }
 
}

例如以上这个类的包和子类。

3、自定义SpringBoot包扫描路径问题:

在Spring中默认Bean注解扫描路径是当前启动类所在的包下和同级目录下。但是当我们需要自定义包扫描路径的时候可以使用下面的注解配置basePackages属性指定。

@ComponentScan(basePackages = {"com.xxx.service1.*","com.xxx.service2.**"})

如下是一个自己写的Demo的例子:
package com.xcy;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;

/**
 * Maven命令行创建Spring Boot项目启动类
 *
 */
@Configuration
@SpringBootApplication
@ComponentScan(basePackages = {"com.xcy.**"})
public class App 
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!" );
        SpringApplication.run(App.class,args);
    }
}

三、在项目中SpringBoot中式如何去配置AOP的

针对AOP的配置方案有两种;

1、第一种: 包路径扫描

第一种方式是针对包的位置进行代理,编写切点,切面,通知等一步一步的操作。

具体实现方式可以参照这一篇博文:

execution(public * com.xcy..*.*(..))

2、第二种: 请求方式(格式)

针对请求方式,比如RequestMapping,PostMapping,GetMapping。以下的代码就是针对该种方式进行操作写的一个环绕通知,一步解决AOP代码问题。

主要实现代码如下:

package com.xcy.config;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

@Aspect
@Component
@Slf4j
public class BaseAspect {
    @Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)"
            + " || @annotation(org.springframework.web.bind.annotation.PostMapping)"
            + " || @annotation(org.springframework.web.bind.annotation.GetMapping)"
            + "")
    public Object api(ProceedingJoinPoint joinPoint) {
        long startTime = System.currentTimeMillis();
        String method = joinPoint.getTarget().getClass().getSimpleName() + "." + joinPoint.getSignature().getName();
        // 调用接口方法
        Object response = null;
        try {
            Object body = joinPoint.getArgs().length > 0 ? joinPoint.getArgs()[0] : null;
            String jsonBody = StringUtils.isEmpty(body)?"请求参数为空":body.toString();
            log.info("Api接口Start:{},输入参数:{}",method,jsonBody);
            response = joinPoint.proceed();
            log.info("Api接口End:{},返回参数:{}",method,JSON.toJSON(response));
            long endTime = System.currentTimeMillis();
            log.info("程序运行时间:{}",(endTime - startTime) + "ms");
        } catch (Throwable e) {
            e.printStackTrace();
            log.info("Api接口Error:{},Cause:{}",method,e);
            return e.toString();
        } finally {

        }
        return response;
    }
}