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:workspaceWireless-Router est-pilling arget est-classesmethods est==>0 ENTRY_MODIFY [2018-11-20 12:53:18] DEBUG FileListenServiceImpl:69 - update file : E:workspaceWireless-Router est-pilling arget est-classesmethods est==>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:workspaceWireless-Router est-pilling arget est-classesmethods est ENTRY_CREATE [2018-11-20 13:05:13] DEBUG FileListenServiceImpl:62 - create file : E:workspaceWireless-Router est-pilling arget est-classesmethods est==>0