咱們先認識幾個咱們都耳熟能詳的註解java
AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進行掃描,並用於構建bean定義,初始化Spring容器。面試
/** * 至關於一個beans */ @Configuration @ComponentScan(basePackages = "xiaodao.spring") public class SpringConfiguration { public SpringConfiguration() { System.out.println("spring 容器啓動"); } /** * bean id 默認是bean的方法名 * @return */ /* @Bean public UserService userService(){ return new UserServiceImpl(); }*/ }
public class SpringConfigurationTest { @Test public void test1(){ //建立純註解方式的spring 容器 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class); UserService userService = (UserService) applicationContext.getBean("userService"); userService.save(); } }
@import 註解的使用方法 spring
咱們在spring中有各類各樣的配置文件.寫在一個類中,確定是 不合適的.咱們須要分門分類編程
@Configuration @ComponentScan(basePackages = "com.kkb.spring") @Import({ JdbcConfig.class}) public class SpringConfiguration { } @Configuration @PropertySource("classpath:jdbc.properties") public class JdbcConfig{ }
AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程api
AOP最先是AOP聯盟的組織提出的,指定的一套規範,spring將AOP的思想引入框架之中,經過預編譯方式和運行期間動態代理實現程序的統一維護的一種技術,緩存
預編譯方式在spring中沒有使用,由於要結合其餘編譯方式,和spring形成強耦合,預編譯方式就是在程序沒有運行前編譯.安全
AOP是OOP的延續,,AOP解決的是從橫向解決代碼重複的問題 ,OOP是從縱向解決代碼的重複問題app
AOP採起橫向抽取機制,取代了傳統縱向繼承體系重複性代碼(性能監視、事務管理、安全檢查、緩存)講業務邏輯和系統處理的代碼進行解耦 如如:關閉鏈接 事物管理,操做日誌的記錄框架
1. Joinpoint(鏈接點) -- 所謂鏈接點是指那些被攔截到的點。在spring中,這些點指的是方法,由於spring只支持方法類型的鏈接點ide
2. Pointcut(切入點) -- 所謂切入點是指咱們要對哪些Joinpoint進行攔截的定義
3. Advice(通知/加強) -- 所謂通知是指攔截到Joinpoint以後所要作的事情就是通知.通知分爲前置通知,後置通知,異常通知,最終通知,環繞通知(切面要完成的功能)
4. Introduction(引介) -- 引介是一種特殊的通知在不修改類代碼的前提下, Introduction能夠在運行期爲類動態地添加一些方法或Field
5. Target(目標對象) -- 代理的目標對象
6. Weaving(織入) -- 是指把加強應用到目標對象來建立新的代理對象的過程
7.Proxy(代理) -- 一個類被AOP織入加強後,就產生一個結果代理類
8. Aspect(切面) -- 是切入點和通知的結合,之後我們本身來編寫和配置的
***AspectJ是一個java實現的AOP框架,它可以對java代碼進行AOP編譯(通常在編譯期進行),讓java代碼具備AspectJ的AOP功能(固然須要特殊的編譯器)
***能夠這樣說AspectJ是目前實現AOP框架中最成熟,功能最豐富的語言,更幸運的是,AspectJ與java程序徹底兼容,幾乎是無縫關聯,所以對於有java編程基礎的工程師,上手和使用都很是容易。
***瞭解AspectJ應用到java代碼的過程(這個過程稱爲織入),對於織入這個概念,能夠簡單理解爲aspect(切面)應用到目標函數(類)的過程。
***對於這個過程,通常分爲動態織入和靜態織入,動態織入的方式是在運行時動態將要加強的代碼織入到目標類中,這樣每每是經過動態代理技術完成的,如Java JDK的動態代理(Proxy,底層經過反射實現)或者CGLIB的動態代理(底層經過繼承實現),Spring AOP採用的就是基於運行時加強的代理技術
***ApectJ採用的就是靜態織入的方式。ApectJ主要採用的是編譯期織入,在這個期間使用AspectJ的acj編譯器(相似javac)把aspect類編譯成class字節碼後,在java目標類編譯時織入,即先編譯aspect類再編譯目標類。
spring AOP 是經過動態代理技術實現的
而動態代理的技術是經過反射來實現的
動態代理技術的實現方式有兩種:基於接口的JDK動態代理和基於繼承的CGLib動態代理。
咱們要代理的對象
public interface UserService { public void save(); } @Service("userService") public class UserServiceImpl implements UserService { public void save() { System.out.println("userserviceimpl save方法"); } }
代理的實現
public class MyProxyUtils { /** * 使用JDK動態代理類 * @param userServiceInterface * @return */ public static UserService getProxy(final UserService userServiceInterface){ /** * proxy是jdk中的代理類 * 1.目標對象的類加載器 * 2.目標對接的接口 * 3.代理對象的執行處理器 */ UserService userService = (UserService) Proxy.newProxyInstance(userServiceInterface.getClass().getClassLoader(), userServiceInterface.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("記錄日誌-開始"); //下面的代碼是,反射中的api用法 //方法 //2.參數 //這行代碼實際仍是調用目標對象的方法 Object invoke = method.invoke(userServiceInterface, args); System.out.println("記錄日誌結束"); return invoke; } }); return userService; } /** * 使用cglib的方式動態代理技術實現 * 它是基於繼承的方式實現的 * @param userService * @return */ public static UserService getProxyByCglib(UserService userService){ //建立加強器 Enhancer enhancer =new Enhancer(); //這裏設置加強類的類對象-實現類的類對象 enhancer.setSuperclass(UserServiceImpl.class); //設置回調函數 enhancer.setCallback(new MethodInterceptor() { /** * * @param o * @param method * @param args 方法參數 * @param methodProxy 代理以後的對象的方法 * @return * @throws Throwable */ public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { long start = System.currentTimeMillis(); System.out.println("cglib 記錄開始時間: "+start); //代理對象是目標的對象的子類 //這行代碼實際仍是調用目標對象的方法 //o 是代理對象 Object object = methodProxy.invokeSuper(o, args); long end = System.currentTimeMillis(); System.out.println("cglib 記錄結束時間: "+end); return object; } }); // 獲取加強以後的代理對象. return (UserService) enhancer.create(); } }
咱們的測試類
public class MyProxyUtilsTest { @Test public void testjdkProxy(){ UserService userService = new UserServiceImpl(); UserService proxy = MyProxyUtils.getProxy(userService); userService.save(); System.out.println("==========="); proxy.save(); } @Test public void testCglibProcy(){ UserService userService = new UserServiceImpl(); UserService proxy = MyProxyUtils.getProxyByCglib(userService); userService.save(); System.out.println("==========="); proxy.save(); } } userserviceimpl save方法 =========== cglib 記錄開始時間: 1556369092690 userserviceimpl save方法 cglib 記錄結束時間: 1556369092700
切入點表達式
execution:必需要([修飾符] 返回值類型 包名.類名.方法名(參數))
aop總共有5中通知
前置通知
後置通知
異常通知
後置通知
環繞通知
xml 配置文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/aop 8 http://www.springframework.org/schema/aop/spring-aop.xsd"> 9 10 11 12 <bean class="xiaodao.spring.service.UserServiceImpl"></bean> 13 <!--加強類--> 14 <bean id="myAdvice" class="xiaodao.spring.MyAdvice"></bean> 15 16 <!--aop 配置--> 17 <aop:config> 18 <!--配置aop切面,切面是由通知和切入點組成的--> 19 <aop:aspect ref="myAdvice"> 20 <!--指定切入點.須要經過表達式來指定 method加強類的方法--> 21 <!--<aop:before method="log" pointcut="execution(void xiaodao.spring.service.UserServiceImpl.save())"/>--> 22 <!--<aop:after method="log2" pointcut="execution(void xiaodao.spring.service.UserServiceImpl.save())"/>--> 23 <!--<aop:after-returning method="log3" pointcut="execution(* xiaodao.spring.*.UserServiceImpl.save())"/>--> 24 25 <!--<!–異常通知 –>--> 26 <!--<aop:after-throwing method="log4" pointcut="execution(* xiaodao.spring.*.UserServiceImpl.save())"/>--> 27 28 <aop:around method="log5" pointcut="execution(* xiaodao.spring.*.UserServiceImpl.save())"/> 29 30 </aop:aspect> 31 </aop:config> 32 </beans>
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:application.xml") public class TestAOP { @Autowired private UserService userService; @Test public void test(){ userService.save(); } }
加強類:
public class MyAdvice { public void log(){ System.out.println("記錄日誌....."); } public void log2(){ System.out.println("記錄日誌 後置通知....."); } public void log3(){ System.out.println("記錄日誌..最終通知.."); } public void log4(){ System.out.println("記錄日誌..異常..."); } public void log5(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("環繞通知前"); //調用目標對象 try { joinPoint.proceed(); System.out.println("後置通知"); } catch (Throwable throwable) { System.out.println("環境通知..異常"); throwable.printStackTrace(); }finally { System.out.println("最終通知"); } } }
userservice 仍是剛剛那個userservice
@Service("userService") public class UserServiceImpl implements UserService { public void save() { System.out.println("userserviceimpl save方法"); System.out.println(1/0); } }
@Aspect @Component("myaspect") //至關於<aop:aspectj-proxy> @EnableAspectJAutoProxy public class MyAspect { //定義該方法是一個前置通知 @Before(value = "execution(* xiaodao.spring.service.*.*())") public void before(){ System.out.println("註解前置通知"); } @After(value = "execution(* xiaodao.spring.service.*.*())") public void after(){ System.out.println("註解後置通知"); } @AfterReturning(value = "func()") public void end(){ System.out.println("最終通知"); } @Pointcut(value = "execution(* xiaodao.spring.service.*.*())") public void func(){ } }
原本準備全註解實現,目前還不能夠junit仍是須要加載application.xml 配置文件,暫時不知道junit如何使用我自定義的註解配置類
JDK 動態代理和 CGLIB 動態代理均是實現 Spring AOP 的基礎。對於這一塊內容,面試官問的比較多,他們每每更想聽聽面試者是怎麼回答的,有沒有看過這一塊的源碼等等。
針對於這一塊內容,咱們看一下 Spring 5 中對應的源碼是怎麼說的。
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } // 判斷目標類是不是接口或者目標類是否Proxy類型,如果則使用JDK動態代理 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } // 配置了使用CGLIB進行動態代理或者目標類沒有接口,那麼使用CGLIB的方式建立代理對象 return new ObjenesisCglibAopProxy(config); } else { // 上面的三個方法沒有一個爲true,那使用JDK的提供的代理方式生成代理對象 return new JdkDynamicAopProxy(config); } } //其餘方法略…… }
從上述源碼片斷能夠看出,是否使用 CGLIB 是在代碼中進行判斷的,判斷條件是 config.isOptimize()、config.isProxyTargetClass() 和 hasNoUserSuppliedProxyInterfaces(config)。
其中,config.isOptimize() 與 config.isProxyTargetClass() 默認返回都是 false,這種狀況下判斷結果就由 hasNoUserSuppliedProxyInterfaces(config) 的結果決定了。
public class ProxyConfig implements Serializable { /** use serialVersionUID from Spring 1.2 for interoperability */ private static final long serialVersionUID = -8409359707199703185L; private boolean proxyTargetClass = false; private boolean optimize = false; boolean opaque = false; boolean exposeProxy = false; private boolean frozen = false; ......... }
簡單來講,hasNoUserSuppliedProxyInterfaces(config) 就是在判斷代理的對象是否有實現接口,有實現接口的話直接走 JDK 分支,即便用 JDK 的動態代理。
因此基本上能夠總結出 Spring AOP 中的代理使用邏輯了:如果目標對象實現了接口,默認狀況下會採用 JDK 的動態代理實現 AOP;若是目標對象沒有實現了接口,則採用 CGLIB 庫,Spring 會自動在 JDK 動態代理和 CGLIB 動態代理之間轉換。