在面試的時候,有些面試官會問注解相關的問題, 注解最典型的代表架構就是Spring了,特别是Spring Boot出來之後,用注解代替了XML的配置,非常友善,今天我們就來聊聊注解相關的面試回答。
面試官的問法可能千奇百怪,我在這邊總結幾個常見的問題:
注解是什麼?
注解(Annotation),也叫中繼資料。一種代碼級别的說明。它是JDK1.5及以後版本引入的一個特性,與類、接口、枚舉是在同一個層次。它可以聲明在包、類、字段、方法、局部變量、方法參數等的前面,用來對這些元素進行說明,注釋。
簡單來說注解其實就是代碼中的特殊标記,這些标記可以在編譯、類加載、運作時被讀取,并執行相對應的處理。
JDK内置了哪些注解?
Overried
Overried是告訴編譯器要檢查該方法是實作父類的方法。
Deprecated
Deprecated用于标記一些過時的代碼。
SuppressWarnings
SuppressWarnings用于消除一些警告資訊,使用集合的時候,如果沒有指定泛型,IDE會提示安全檢查的警告。
FunctionalInterface
FunctionalInterface是JDK8中的注解,用來指定該接口是函數式接口。
SafeVarargs
SafeVarargs是JDK 7中的注解,主要目的是處理可變長參數中的泛型,此注解告訴編譯器:在可變長參數中的泛型是類型安全的。
怎麼自定義一個注解?
在Java中,類使用class定義,接口使用interface定義,注解和接口的定義差不多,增加了一個@符号,即@interface,代碼如下:
public@interfaceEnableAuth{
}
注解中可以定義成員變量,用于資訊的描述,跟接口中方法的定義類似,代碼如下:
Stringname();
還可以添加預設值:
Stringname()default"猿天地";
上面的介紹隻是完成了自定義注解的第一步,開發中日常使用注解大部分是用在類上,方法上,字段上,示列代碼如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public@interfaceEnableAuth {
Target
用于指定被修飾的注解修飾哪些程式單元,也就是上面說的類,方法,字段
Retention
用于指定被修飾的注解被保留多長時間,分别SOURCE(注解僅存在于源碼中,在class位元組碼檔案中不包含),CLASS(預設的保留政策,注解會在class位元組碼檔案中存在,但運作時無法擷取),RUNTIME(注解會在class位元組碼檔案中存在,在運作時可以通過反射擷取到)三種類型,如果想要在程式運作過程中通過反射來擷取注解的資訊需要将Retention設定為RUNTIME
Documented
用于指定被修飾的注解類将被javadoc工具提取成文檔
Inherited
用于指定被修飾的注解類将具有繼承性
如何擷取注解中的值?
可以通過反射來判斷類,方法,字段上是否有某個注解以及擷取注解中的值, 擷取某個類中方法上的注解代碼示例如下:
Class clz = bean.getClass();
Method[] methods = clz.getMethods();
for(Method method : methods) {
if(method.isAnnotationPresent(EnableAuth.class)) {
String name = method.getAnnotation(EnableAuth.class).name();
通過isAnnotationPresent判斷是否存在某個注解,通過getAnnotation擷取注解對象,然後擷取值
工作中經常接觸的注解有哪些?
注解在很多架構中都應用非常多,這個問題你可以說下Spring中的即可,大概的解釋下每個注解的含義和用途,除了Spring還有很多别的架構中也有使用注解,比如Swagger, Lombok,JPA,Spring Data等
Component
Controller
Repository
Service
RequestMapping
RequestParam
RequestAttribute
RequestBody
ResponseBody
……
注解的使用場景?
生成文檔
Swagger中就是通過注解對接口,實體類中的字段進行描述生成可視化的文檔
代替配置檔案
Spring中Bean的裝載注入
導出資料
可以寫一個統一的導出工具類,傳入一個List<實體類>進去即可導出Excel檔案,Excel的表頭可以用注解加載字段上
架構層面的統一處理
注解在底層架構中用的比較多,在架構中需要考慮到通用性,能用注解做很多事情,比如對API進行權限控制,限流等操作都可以通過自定義注解來辨別是否需要進行認證,限流等,還有資料的緩存,典型的就是@Cacheable,還有異步方法的調用@Async,ORM架構中的使用,可以用注解辨別表名,字段名,JPA中,Spring Data架構中都有使用
權限控制詳細講解
比如我們有的接口需要認證才能調用,有的不需要,簡單的做法就是用配置的方式,将需要認證的接口配置好,然後進行攔截過濾,缺點是需要經常維護配置資訊,用注解可以避免這個情況。
可以自定義一個注解,隻要加了這個注解我們就對這個接口進行認證攔截操作,接下裡詳細的講解下這個功能實作。
定義開啟認證的注解,作用在方法上,運作時可擷取注解資訊
/**
- 開啟API權限認證
*@authoryinjihuan
*
*/
在需要認證的接口上增加注解
@EnableAuth
@GetMapping("/userCollectCityInfo")
@ApiOperation(value="擷取登入使用者關注的城市資訊", notes="擷取登入使用者關注的城市資訊", produces ="application/json")
@ApiResponses(
@ApiResponse(response = UserCollectCityInfoDto.class, code = 200, message ="")
)
publicResponse getUserCollectCityInfos(HttpServletRequest request) {
try{
Longuid = UserInfoUtils.getLoginUserId(request);
List citys = cityCollectService.findAllByUid(uid);
List results = citys.stream().map(this::ofCityInfo)
.sorted((l1, l2) -> l1.getRangeLevel().compareTo(l2.getRangeLevel()))
.collect(Collectors.toList());
returnResponse.ok(results);
}catch(Exception e) {
logger.error("擷取登入使用者關注的城市資訊異常", e);
returnResponse.fail("擷取登入使用者關注的城市資訊異常");
在攔截器中進行攔截,攔截需要知道目前請求的接口是不是需要攔截的,我們可以在啟動時将所有增加了@EnableAuth的接口資訊儲存起來,這樣在攔截器中就知道哪個接口是需要認證。
初始化需要認證的接口資訊代碼如下:
- API 驗證資料初始化
@Component
@Configuration
publicclassApiAuthDataInitimplementsApplicationContextAware{
publicstatic List checkApis = new ArrayList();
@Override
publicvoid setApplicationContext(ApplicationContext ctx) throws BeansException {
Map beanMap = ctx.getBeansWithAnnotation(RestController.class);
if(beanMap !=null) {
for(Object bean : beanMap.values()) {
String uri = getApiUri(clz, method);
checkApis.add(uri);
privateString getApiUri(Class clz, Method method) {
StringBuilder uri = new StringBuilder();
uri.append(clz.getAnnotation(RequestMapping.class).value()[0]);
if(method.isAnnotationPresent(GetMapping.class)) {
uri.append(method.getAnnotation(GetMapping.class).value()[0]);
}elseif(method.isAnnotationPresent(PostMapping.class)) {
uri.append(method.getAnnotation(PostMapping.class).value()[0]);
}elseif(method.isAnnotationPresent(RequestMapping.class)) {
uri.append(method.getAnnotation(RequestMapping.class).value()[0]);
returnuri.toString();
實作ApplicationContextAware接口,然後通過getBeansWithAnnotation擷取所有接口的bean資訊,通過RestController注解來擷取,也就是說隻要class上增加了RestController注解,這邊就都能擷取到。
然後通過反射擷取bean中所有的方法,如果有增加EnableAuth的話就擷取接口的uri存儲到map中,這樣過濾器中就可以根據map中的值來判斷是不是需要進行權限認證了。