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