jdk的動態代理大家應該都聽說過,條件是必須要有接口;cglib不要求接口,那么它是怎么實現切面的呢?很簡單,通過繼承,它動態的創建出一個目標類的子類,復寫父類的方法,由此實現對方法的增強。看例子:
spring-core.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd "> <context:annotation-config /> <context:component-scan base-package="com.wulinfeng.test.testpilling" /> <bean class="com.wulinfeng.test.testpilling.util.PropertiesConfigUtil"> <property name="ignoreUnresolvablePlaceholders" value="true" /> <property name="locations"> <list> <value>classpath:global.properties</value> </list> </property> <property name="fileEncoding"> <value>UTF-8</value> </property> </bean> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="com.wulinfeng.test.testpilling.service.TestPillingService.init" /> </bean> <bean id="advice" class="com.wulinfeng.test.testpilling.util.TimeCostUtil" /> <aop:config> <aop:pointcut expression="execution(* com.wulinfeng.*.testpilling.service..*Service.*(..))" id="pointCut" /> <aop:advisor advice-ref="advice" pointcut-ref="pointCut" /> </aop:config> </beans>
通知類:
package com.wulinfeng.test.testpilling.util; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * 統計接口時延 * * @author wulinfeng * @version C10 2018年11月19日 * @since SDP V300R003C10 */ public class TimeCostUtil implements MethodInterceptor { private static Logger LOGGER = LogManager.getLogger(TimeCostUtil.class); @Override public Object invoke(MethodInvocation invocation) throws Throwable { // 獲取服務開始時間 long beginTime = System.currentTimeMillis(); // 獲取類名和方法名 String srcClassName = ""; String methodName = ""; if (invocation != null) { String className = invocation.getClass() != null ? invocation.getClass().getName() : ""; LOGGER.debug("The proxy class name is : " + className); if (invocation.getMethod() != null) { methodName = invocation.getMethod().getName(); } if (invocation.getThis() != null && invocation.getThis().getClass() != null) { srcClassName = invocation.getThis().getClass().getName(); } } // 調用原來的方法 Object result = invocation.proceed(); // 打印耗時 LOGGER.debug(srcClassName + "." + methodName + " cost time: " + (System.currentTimeMillis() - beginTime)); return result; } }
目標類:
package com.wulinfeng.test.testpilling.service; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.concurrent.Executors; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.stereotype.Service; /** * 監聽文件修改,打印到日志里 * * @author wulinfeng * @version C10 2018年11月20日 * @since SDP V300R003C10 */ @Service public class FileListenServiceImpl implements FileListenService { private static Logger LOGGER = LogManager.getLogger(FileListenServiceImpl.class); @Override public void updateOnListen(String filePath) throws IOException { LOGGER.debug("The file path is : " + filePath); // 監聽文件所在路徑 Path path = Paths.get(filePath); final WatchService ws = FileSystems.getDefault().newWatchService(); path.register(ws, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_CREATE); Executors.newCachedThreadPool().execute(new Runnable() { @Override public void run() { while (true) { try { WatchKey key = ws.take(); for (WatchEvent<?> event : key.pollEvents()) { System.out.println(event.kind().toString()); if (event.kind().equals(StandardWatchEventKinds.ENTRY_CREATE)) { Path createdPath = (Path)event.context(); createdPath = path.resolve(createdPath); long size = Files.size(createdPath); LOGGER.debug("create file : " + createdPath + "==>" + size); } else if (event.kind().equals(StandardWatchEventKinds.ENTRY_MODIFY)) { Path createdPath = (Path)event.context(); createdPath = path.resolve(createdPath); long size = Files.size(createdPath); LOGGER.debug("update file : " + createdPath + "==>" + size); } else if (event.kind().equals(StandardWatchEventKinds.ENTRY_DELETE)) { Path createdPath = (Path)event.context(); createdPath = path.resolve(createdPath); LOGGER.debug("delete file : " + createdPath); } } key.reset(); } catch (Exception e) { e.printStackTrace(); } } } }); } }
另一個TestPillingService沒有實現接口,不貼了,看下單測:
package com.wulinfeng.test.testpilling; import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.wulinfeng.test.testpilling.service.FileListenService; import com.wulinfeng.test.testpilling.service.TestPillingService; import com.wulinfeng.test.testpilling.util.PropertiesConfigUtil; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:spring-core.xml"}) public class TimeCostUtilTest { @Autowired TestPillingService tps; @Autowired FileListenService fls; @Test public void timeCostTest() throws IOException { String CLASS_PATH = TestPillingService.class.getResource("/").getPath().startsWith("/") ? TestPillingService.class.getResource("/").getPath().substring(1) : TestPillingService.class.getResource("/").getPath(); String filePath = CLASS_PATH + PropertiesConfigUtil.getProperty("filepath", "methods"); fls.updateOnListen(filePath); tps.editMethodContent("test", "hello world!"); } }
運行結果:
log4j:WARN No appenders could be found for logger (org.springframework.test.context.junit4.SpringJUnit4ClassRunner). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. ERROR StatusLogger Unable to locate appender "httpClient-log" for logger config "org.asynchttpclient" [2018-11-20 12:53:18] DEBUG TestPillingService:71 - Enter TestPillingService.init, filePath : methods, loginPath : login [2018-11-20 12:53:18] DEBUG TimeCostUtil:32 - The proxy class name is : org.springframework.aop.framework.ReflectiveMethodInvocation [2018-11-20 12:53:18] DEBUG FileListenServiceImpl:34 - The file path is : E:/workspace/Wireless-Router/test-pilling/target/test-classes/methods [2018-11-20 12:53:18] DEBUG TimeCostUtil:48 - com.wulinfeng.test.testpilling.service.FileListenServiceImpl.updateOnListen cost time: 6 [2018-11-20 12:53:18] DEBUG TimeCostUtil:32 - The proxy class name is : org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation [2018-11-20 12:53:18] DEBUG TimeCostUtil:48 - com.wulinfeng.test.testpilling.service.TestPillingService.editMethodContent cost time: 43 ENTRY_CREATE [2018-11-20 12:53:18] DEBUG FileListenServiceImpl:62 - create file : E:\workspace\Wireless-Router\test-pilling\target\test-classes\methods\test==>0 ENTRY_MODIFY [2018-11-20 12:53:18] DEBUG FileListenServiceImpl:69 - update file : E:\workspace\Wireless-Router\test-pilling\target\test-classes\methods\test==>14
我們看到jdk動態代理的實際實現類是ReflectiveMethodInvocation,它最終實現了MethodInterceptor接口的invoke方法和MethodInvocation接口的getMethod方法;而cglib動態代理實際實現類為CglibAopProxy的內部類CglibMethodInvocation(它繼承自ReflectiveMethodInvocation,復寫了invokeJoinpoint方法)。他們倆執行目標類的實際方法時都是通過ReflectiveMethodInvocation的proceed來進行的。
如果我們把<aop:config>改成這樣:
<aop:config proxy-target-class="true">
測試結果:
log4j:WARN No appenders could be found for logger (org.springframework.test.context.junit4.SpringJUnit4ClassRunner). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. ERROR StatusLogger Unable to locate appender "httpClient-log" for logger config "org.asynchttpclient" [2018-11-20 13:05:12] DEBUG TestPillingService:71 - Enter TestPillingService.init, filePath : methods, loginPath : login [2018-11-20 13:05:13] DEBUG TimeCostUtil:32 - The proxy class name is : org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation [2018-11-20 13:05:13] DEBUG FileListenServiceImpl:34 - The file path is : E:/workspace/Wireless-Router/test-pilling/target/test-classes/methods [2018-11-20 13:05:13] DEBUG TimeCostUtil:48 - com.wulinfeng.test.testpilling.service.FileListenServiceImpl.updateOnListen cost time: 50 [2018-11-20 13:05:13] DEBUG TimeCostUtil:32 - The proxy class name is : org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation [2018-11-20 13:05:13] DEBUG TimeCostUtil:48 - com.wulinfeng.test.testpilling.service.TestPillingService.editMethodContent cost time: 42 ENTRY_DELETE [2018-11-20 13:05:13] DEBUG FileListenServiceImpl:75 - delete file : E:\workspace\Wireless-Router\test-pilling\target\test-classes\methods\test ENTRY_CREATE [2018-11-20 13:05:13] DEBUG FileListenServiceImpl:62 - create file : E:\workspace\Wireless-Router\test-pilling\target\test-classes\methods\test==>0