
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