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;
}
}