2.3.1 與Spring的關(guān)系
這是Spring AOP的兩種實(shí)現(xiàn)方式榴都。根據(jù)官方文檔:
默認(rèn)使用JdkProxy
對(duì)于被代理對(duì)象沒有實(shí)現(xiàn)任何接口汛闸,使用Cglib
可以強(qiáng)制指定使用Cglib氓扛。
這樣就可以解釋為什么有的bean實(shí)現(xiàn)了接口辖所,有的沒有琼梆,但是在同一個(gè)工程中可以并存了撕蔼。
2.3.2 示例代碼
本節(jié)代碼改寫自參考文獻(xiàn)[5]。
//用戶管理接口
public interface UserManager {
? ? //新增用戶抽象方法
? ? void addUser(String userName,String password);
? ? //刪除用戶抽象方法
? ? void delUser(String userName);
}
//用戶管理實(shí)現(xiàn)類,實(shí)現(xiàn)用戶管理接口
public class UserManagerImpl implements UserManager{
? ? @Override
? ? public void addUser(String userName) {
? ? ? ? System.out.println("調(diào)用了新增的方法或链!");
? ? ? ? System.out.println("傳入?yún)?shù)為 userName: "+userName+" password: "+password);
? ? }
? ? @Override
? ? public void delUser(String userName) {
? ? ? ? System.out.println("調(diào)用了刪除的方法惫恼!");
? ? ? ? System.out.println("傳入?yún)?shù)為 userName: "+userName);
? ? }?
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.lf.shejimoshi.proxy.entity.UserManager;
import com.lf.shejimoshi.proxy.entity.UserManagerImpl;
//JDK動(dòng)態(tài)代理實(shí)現(xiàn)InvocationHandler接口
public class JdkProxy implements InvocationHandler {
? ? private Object target ;//需要代理的目標(biāo)對(duì)象
? ? @Override
? ? public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
? ? ? ? System.out.println("JDK動(dòng)態(tài)代理,監(jiān)聽開始澳盐!");
? ? ? ? Object result = method.invoke(target, args);
? ? ? ? System.out.println("JDK動(dòng)態(tài)代理祈纯,監(jiān)聽結(jié)束!");
? ? ? ? return result;
? ? }
? ? //定義獲取代理對(duì)象方法
? ? // 因?yàn)橹皇窃趍ain()里測試叼耙,聲明為private了
? ? private Object getJDKProxy(Object targetObject){
? ? ? ? this.target = targetObject;
? ? ? ? return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
? ? }
? ? public static void main(String[] args) {
? ? ? ? JdkProxy jdkProxy = new JdkProxy();
? ? ? ? UserManager user = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl());//獲取代理對(duì)象
? ? ? ? user.addUser("admin");
? ? }?
}
import java.lang.reflect.Method;
import com.lf.shejimoshi.proxy.entity.UserManager;
import com.lf.shejimoshi.proxy.entity.UserManagerImpl;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
//Cglib動(dòng)態(tài)代理腕窥,實(shí)現(xiàn)MethodInterceptor接口
public class CglibProxy implements MethodInterceptor {
? ? private Object target;//需要代理的目標(biāo)對(duì)象
? ? //重寫攔截方法
? ? @Override
? ? public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {
? ? ? ? System.out.println("Cglib動(dòng)態(tài)代理,監(jiān)聽開始筛婉!");
? ? ? ? Object invoke = method.invoke(target, arr);//方法執(zhí)行簇爆,參數(shù):target 目標(biāo)對(duì)象 arr參數(shù)數(shù)組
? ? ? ? System.out.println("Cglib動(dòng)態(tài)代理,監(jiān)聽結(jié)束!");
? ? ? ? return invoke;
? ? }
? ? //定義獲取代理對(duì)象方法
? ? public Object getCglibProxy(Object objectTarget){
? ? ? ? //為目標(biāo)對(duì)象target賦值
? ? ? ? this.target = objectTarget;
? ? ? ? Enhancer enhancer = new Enhancer();
? ? ? ? //設(shè)置父類,因?yàn)镃glib是針對(duì)指定的類生成一個(gè)子類入蛆,所以需要指定父類
? ? ? ? enhancer.setSuperclass(objectTarget.getClass());
? ? ? ? enhancer.setCallback(this);// 設(shè)置回調(diào)
? ? ? ? Object result = enhancer.create();//創(chuàng)建并返回代理對(duì)象
? ? ? ? return result;
? ? }
? ? public static void main(String[] args) {
? ? ? ? CglibProxy cglib = new CglibProxy();
? ? ? ? UserManager user =? (UserManager) cglib.getCglibProxy(new UserManagerImpl());
? ? ? ? user.delUser("admin");
? ? }
}
2.3.3 比較
比較一下兩者的區(qū)別响蓉,這也是常見的面試問題。
JdkProxy Cglib
依賴 被代理對(duì)象實(shí)現(xiàn)了接口(所有接口的方法數(shù)總和必須>0[4]) 引入asm哨毁、cglib jar 枫甲;不能是final類和方法
原理 反射,實(shí)現(xiàn)被代理對(duì)象接口的匿名內(nèi)部類扼褪,通過InvocationHandler.invoke()包圍被代理對(duì)象的方法 引入asm言秸、cglib jar,代理類實(shí)現(xiàn)MethodInterceptor迎捺,通過底層重寫字節(jié)碼來實(shí)現(xiàn)
效率 創(chuàng)建快举畸,運(yùn)行慢(見下方說明2) 創(chuàng)建慢,運(yùn)行快
說明
Cglib是如何修改字節(jié)碼凳枝,從代碼上是看不出來的抄沮。使用的是ASM技術(shù),修改class文件岖瑰,可以自行查閱叛买。
JDK1.8及以后,JdkProxy的運(yùn)行速度已經(jīng)比Cglib快了(之前則是慢于Cglib)蹋订,測試代碼可見參考文獻(xiàn)[6]率挣。
2.3.4 關(guān)于org.apoalliance.intercept.MethodInterceptor
我讀了一下之前所用的日志攔截器源碼,發(fā)現(xiàn)其實(shí)現(xiàn)的是這節(jié)標(biāo)題的接口:
class CommonInterceptor implements MethodInterceptor {
? ? ? @Override
? ? ? public Object invoke(MethodInvocation invocation) throws Throwable {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? // 攔截器內(nèi)部邏輯
? ? ? ? ? ? ? ? ? result = invoication.proceed();
? ? ? ? ? ? catch(Throwable e) {
? ? ? ? ? ? ? ? ? // 異常處理
? ? ? ? ? ? }
? ? ? ? ? ? return result;
? ? ? }
}
聲明代理鏈
@Configuration
public class InterceptorConfig {
? ? ? @Bean
? ? ? public CommonInterceptor serviceInterceptor() {
? ? ? ? ? ? CommonInterceptor bean = new CommonInterceptor();
? ? ? ? ? ? return bean;
? ? ? }
? ? ? // 代理名稱后綴為servie的實(shí)現(xiàn)類
? ? ? @Bean
? ? ? public BeanNameAutoProxyCreator servieBeanNameAutoProxyCreator() {
? ? ? ? ? ? BeanNameAutoProxyCreator creator = new BeanNameAutoProxyCreator();
? ? ? ? ? ? creator.setName("*ServieImpl");
? ? ? ? ? ? creator.setInterceptorNames("serviceInterceptor");
? ? ? ? ? ? creator.setProxyTargetClass(true);
? ? ? ? ? ? return creator;
? ? ? }
}
查了一些資料露戒,apoalliance包下只是aop的接口規(guī)范椒功,不是具體的實(shí)現(xiàn),不要把這里的MethodInterceptor和cglib的MethodInterceptor搞混智什。
2.4 構(gòu)造方法注入和設(shè)值注入的區(qū)別
注:設(shè)值注入指的是通過setter注入动漾。
之前看參考文獻(xiàn)[7],感覺很難懂荠锭,試著改換了一種說法:
如果只設(shè)置基本類型(int旱眯、long等)的值,建議設(shè)置默認(rèn)值而不是通過任何一種注入完成
構(gòu)造注入不支持大部分的依賴注入证九。構(gòu)造注入僅在創(chuàng)建時(shí)執(zhí)行删豺,設(shè)值注入的值在后續(xù)也可以變化。
設(shè)值注入可以支持尚未完整的被依賴的對(duì)象愧怜,構(gòu)造注入則不行呀页。可以通過構(gòu)造注入決定依賴關(guān)系叫搁,因此如果依賴關(guān)系不會(huì)發(fā)生變更也可以選擇依賴注入赔桌。
2.5 ApplicationContext事件
可以通過實(shí)現(xiàn)ApplicationEvent類和ApplicationListener接口,進(jìn)行ApplicationContext的事件處理渴逻。這是標(biāo)準(zhǔn)的發(fā)送者-監(jiān)聽者的模型疾党,可以用來處理業(yè)務(wù)邏輯,將代碼解耦惨奕。
但是雪位,發(fā)送和接收實(shí)際上是同步的,如果有事務(wù)梨撞,會(huì)在同一個(gè)事務(wù)內(nèi)雹洗,并不能作為異步處理機(jī)制[8]。
示例代碼見參考文獻(xiàn)[9]卧波。
3. SpringBoot
注:工作中我對(duì)SpringBoot是偏應(yīng)用的时肿,研究并不是很深入。
3.1 如何快速搭建一個(gè)SpringBoot項(xiàng)目
見參考文獻(xiàn)[10]港粱。實(shí)際的過程是借助Spring Initializer這個(gè)網(wǎng)絡(luò)應(yīng)用程序來生成SpringBoot項(xiàng)目螃成。
3.2 SpringBoot的關(guān)鍵注解
所謂核心注解,這里指的是相對(duì)Spring本身新增的一些注解查坪,來看看它們有什么作用寸宏。
恰好這里提到的注解,都可以打在SpringBoot的啟動(dòng)類(不限于啟動(dòng)類)偿曙,用下面的代碼片段來進(jìn)行說明氮凝。
3.2.1 代碼示例
package com.example.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
@PropertySource(value = "classpath:application.properties")
@MapperScan("com.example.demo.dal")
@SpringBootApplication(scanBasePackages = {"com.example.demo"})
@Import({DemoConfig1.class, DemoConfig2.class,})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3.2.2 @PropertySource
從指定的文件讀取變量。示例代碼會(huì)從resource目錄下讀取application.properties變量的值望忆。文件的格式如下罩阵,既可以用常量,也可以用變量替換启摄,來對(duì)不同環(huán)境的值作區(qū)分永脓。
name=value
env.name=$env.value
如何使用這個(gè)值?在要使用的地方獲取即可鞋仍。
@PropertySource(value = "classpath:application.properties")
class TestClass {
@Resource
private Environment environment;
? ? ? @Test
? ? ? public void test() {
? ? ? ? ? ? String value = environment.getProperty("name"));
? ? ? }
}
3.2.2.1 與@Value配合使用
使用@Value可以把配置文件的值直接注入到成員變量中常摧。
@PropertySource("classpath:application.properties")
public class PropertyConfig {
? ? @Value("${name}")
? ? private String value;
? ? ...
}
3.2.2.2 通過@Import引用
3.2.1的示例代碼中,如果類上沒有@PropertySource威创,但DemoConfig1或DemoConfig2中有@PropertySource落午,通過@Import可以將它們加載的變量也讀出來。
@Import的作用在下文會(huì)繼續(xù)介紹肚豺。
3.2.2.3 .properties和.yml配置文件
@PropertySource只能導(dǎo)入.properties配置文件里的內(nèi)容溃斋,對(duì)于.yml是不支持的∥辏看了一些文章梗劫,得出結(jié)論是yml文件是不需要注解就能導(dǎo)入享甸,但是需要路徑。
Springboot有兩種核心配置文件梳侨,application和bootstrap蛉威,都可以用properties或yml格式。區(qū)別在于bootstrap比application優(yōu)先加載走哺,并且不可覆蓋蚯嫌。
3.2.3 @MapperScan
這實(shí)際上是一個(gè)mybatis注解,作用是為指定路徑下的DAO接口丙躏,通過sqlmapping.xml文件择示,生成實(shí)現(xiàn)類。
3.2.4 @SpringBootApplication
@SpringBootApplication是由多個(gè)注解組合成的晒旅。源碼如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
? ? ? // 略
}
簡單介紹一下這些注解栅盲。
#### 3.2.4.1 元注解
最上面四行都是元注解》狭担回憶一下它們的作用<sup>[12]</sup>:
* @Target 注解可以用在哪剪菱。TYPE表示類型,如類拴签、接口孝常、枚舉
* @Retention 注解的保留時(shí)間期限。只有RUNTIME類型可以在運(yùn)行時(shí)通過反射獲取其值
* @Documented 該注解在生成javadoc文檔時(shí)是否保留
* @Inherited 被注解的元素蚓哩,是否具有繼承性构灸,如子類可以繼承父類的注解而不必顯式的寫下來。
#### 3.2.4.2 @SpringBootConfiguration
標(biāo)注這是一個(gè)SpringBoot的配置類岸梨,和@Configuration功能是相通的喜颁,從源碼也可以看出它直接使用了@Configuration。
#### 3.2.4.3 @EnableAutoConfiguration
這個(gè)注解是實(shí)現(xiàn)自動(dòng)化配置的核心注解曹阔,定義如下
```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
? ? ? // 略
}
龍華大道1號(hào)?www.kinghill.cn/LongHuaDaDao1Hao/index.html