您的位置:首页 > 编程语言 > Java开发

基于spring的可扩展性

2016-03-06 22:08 579 查看

从一个日志功能开始说起

1. v1 : spring aop 实现日志

代码示例:

@Aspect
@Deprecated
public class MysqlLogAspect {
private static final Logger log = LoggerFactory.getLogger(MysqlLogAspect.class);
@Around("execution(* *(..))")
public void aroundPerformance(ProceedingJoinPoint joinPoint, MysqlLog mysqlLog) throws Throwable {
// 方法的签名
Signature signature = joinPoint.getSignature();
log.info("start execute [{}]", signature);
long start = System.currentTimeMillis();
// 方法名
try {
joinPoint.proceed();
} catch (Throwable e) {
log.error("", e);
throw e;
}
long end = System.currentTimeMillis();
log.info("end execute [{}], cost {} ms", signature, (end - start));
}

}


可以看到通过spring的@Aspect、@Around、@Before、@After…等注解可以轻易的实现日志功能。

可是这样的就达到了要求了吗?

我希望更加精细一些的控制,类的某些方法需要日志,某些不需要,该怎么做?

2. v2 : 再精细一些的控制,添加注解支持

代码示例:

@Aspect
@Deprecated
public class MysqlLogAspect {
private static final Logger log = LoggerFactory.getLogger(MysqlLogAspect.class);
@Around("execution(* *(..)) && @annotation(mysqlLog)")
public void aroundPerformance(ProceedingJoinPoint joinPoint, MysqlLog mysqlLog) throws Throwable {
// 方法的签名
Signature signature = joinPoint.getSignature();
log.info("start execute [{}]", signature);
long start = System.currentTimeMillis();
// 方法名
try {
joinPoint.proceed();
} catch (Throwable e) {
log.error("", e);
throw e;
}
long end = System.currentTimeMillis();
log.info("end execute [{}], cost {} ms", signature, (end - start));
}

}


这样你就可以在类的方法的注解上添加日志注解,然后就可以精细到方法了。

这样就够了吗?

如果我希望,在类上使用注解,默认这个类的所有方法便开启日志功能,要怎么做呢?

aop的切入点表达式可以实现吗?反正我是不知道?除此之外,切入点需要指定范围,而我更想把日志功能做成一个通用spring插件,就想 @Async 一样。那应该怎么做?

3. v3 : 实现spring注解日志插件

代码示例:

package com.xjy.log.core;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 日志注解,默认的日志处理器只是简单的打印了耗时
* @author freeman.xu
*
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/** key,如果不指定则是'被标注类的全包名#方法签名' */
String key() default "";
/** 日志描述 */
String desc() default "";
/** 日志处理器 */
String logProcessor() default "";
}


package com.xjy.log.core;

import org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

@SuppressWarnings("serial")
public class LogAnnotationBeanPostProcessor extends AbstractAdvisingBeanPostProcessor implements BeanFactoryAware {

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
LogAnnotationAdvisor advisor = new LogAnnotationAdvisor(new DefaultLogProcessor());
advisor.setBeanFactory(beanFactory);
this.advisor = advisor;
}

}


package com.xjy.log.core;

import java.lang.annotation.Annotation;
import java.util.LinkedHashSet;
import java.util.Set;

import org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

@SuppressWarnings("serial")
public class LogAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {

private Advice advice;

private Pointcut pointcut;

public LogAnnotationAdvisor(LogProcessor logProcessor) {
Set<Class<? extends Annotation>> logAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>(2);
logAnnotationTypes.add(Log.class);
this.advice = buildAdvice(logProcessor);
this.pointcut = buildPointcut(logAnnotationTypes);
}

protected Advice buildAdvice(LogProcessor logProcessor) {
return new LogInterceptor(logProcessor);
}

protected Pointcut buildPointcut(Set<Class<? extends Annotation>>
20930
; logAnnotationTypes) {
ComposablePointcut result = null;
for (Class<? extends Annotation> logAnnotationType : logAnnotationTypes) {
Pointcut cpc = new AnnotationMatchingPointcut(logAnnotationType, true);
Pointcut mpc = AnnotationMatchingPointcut.forMethodAnnotation(logAnnotationType);
if (result == null) {
result = new ComposablePointcut(cpc).union(mpc);
}
else {
result.union(cpc).union(mpc);
}
}
return result;
}

@Override
public Pointcut getPointcut() {
return this.pointcut;
}

@Override
public Advice getAdvice() {
return this.advice;
}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (this.advice instanceof BeanFactoryAware) {
((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
}
}

}


package com.xjy.log.core;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

@Component
public class LogInterceptor implements MethodInterceptor, Ordered, BeanFactoryAware {

private final Map<Method, LogProcessor> logProcessors = new ConcurrentHashMap<Method, LogProcessor>(16);
private BeanFactory beanFactory;
private LogProcessor defaultLogProcessor;

public LogInterceptor() {
}

@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}

@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = getTargetClass(invocation);
Method specificMethod = getSpecificMethod(invocation);
String logDesc = getLogDesc(targetClass, specificMethod);
logger.info("start execute [{}] method [{}],desc is [{}]", targetClass, specificMethod, logDesc);
long start = System.currentTimeMillis();
Object result = null;
try {
result = invocation.proceed();
} catch (Throwable e) {
logger.error("execute [" + targetClass + "] method [" + specificMethod + "] error", e);
throw e;
}
long end = System.currentTimeMillis();
logger.info("end execute [{}] method [{}],desc is [{}],cost[{}] ms", targetClass, specificMethod, logDesc, (end - start));
return result;
}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}

}


4. 为什么是这样实现的呢

1.  spring源码解析


spring源码示例:

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();
}

catch (BeansException ex) {
logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}
}
}


我知道的步骤分析:

// Tell the subclass to refresh the internal bean factory.
// 这一步会加载你在spring.xml中定义的bean
// 其中又分普通的<bean>标签以及自定义的标签,比如<task:annoatation-driven/>
// 自定义的标签会有相应的NameHandler和BeanDefinitionParser
// NameHandler的定义在spring的jar包的META-INF下面,文件名是spring.handlers、spring.schemas、spring.tooling
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();


示例:

// 解析xml的类,org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
// DefaultElement,eg:http://www.springframework.org/schema/beans
parseDefaultElement(ele, delegate);
}
else {
// CustomElement, eg:http://www.springframework.org/schema/task等
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
// CustomElement的bean有NameHandler和BeanDefinitionParser, 以及NameHandler的定义在spring的jar包的META-INF下面,文件名是spring.handlers、spring.schemas
// org.springframework.scheduling.config.TaskNamespaceHandler
public class TaskNamespaceHandler extends NamespaceHandlerSupport {

public void init() {
this.registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
this.registerBeanDefinitionParser("executor", new ExecutorBeanDefinitionParser());
this.registerBeanDefinitionParser("scheduled-tasks", new ScheduledTasksBeanDefinitionParser());
this.registerBeanDefinitionParser("scheduler", new SchedulerBeanDefinitionParser());
}

}
// org.springframework.scheduling.config.AnnotationDrivenBeanDefinitionParser
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
// Register component for the surrounding <task:annotation-driven> element.
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
parserContext.pushContainingComponent(compDefinition);
// Nest the concrete post-processor bean in the surrounding component.
BeanDefinitionRegistry registry = parserContext.getRegistry();
String mode = element.getAttribute("mode");
if ("aspectj".equals(mode)) {
// mode="aspectj"
registerAsyncExecutionAspect(element, parserContext);
}
else {
// mode="proxy"
if (registry.containsBeanDefinition(AnnotationConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)) {
parserContext.getReaderContext().error(
"Only one AsyncAnnotationBeanPostProcessor may exist within the context.", source);
}
else {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor");
builder.getRawBeanDefinition().setSource(source);
String executor = element.getAttribute("executor");
if (StringUtils.hasText(executor)) {
builder.addPropertyReference("executor", executor);
}
if (Boolean.valueOf(element.getAttribute(AopNamespaceUtils.PROXY_TARGET_CLASS_ATTRIBUTE))) {
builder.addPropertyValue("proxyTargetClass", true);
}
registerPostProcessor(parserContext, builder, AnnotationConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME);
}
}
if (registry.containsBeanDefinition(AnnotationConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
parserContext.getReaderContext().error(
"Only one ScheduledAnnotationBeanPostProcessor may exist within the context.", source);
}
else {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor");
builder.getRawBeanDefinition().setSource(source);
String scheduler = element.getAttribute("scheduler");
if (StringUtils.hasText(scheduler)) {
builder.addPropertyReference("scheduler", scheduler);
}
registerPostProcessor(parserContext, builder, AnnotationConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME);
}
// Finally register the composite component.
parserContext.popAndRegisterContainingComponent();

return null;
}


// Register bean processors that intercept bean creation.
// 这一步会注册BeanPostProcessors
// 所谓的BeanPostProcessors就是在创建bean的时候,拦截bean的创建,有前置拦截和后置拦截
// 也是通过BeanPostProcessors对原始的bean进行动态代理,生成代理bean
registerBeanPostProcessors(beanFactory);


示例:

// 注册BeanPostProcessors,注册的时候有优先级顺序.org.springframework.context.supportAbstractApplicationContext#registerBeanPostProcessors
// BeanPostProcessors拦截bean的创建.org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {

Object result = existingBean;
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
result = beanProcessor.postProcessAfterInitialization(result, beanName);
if (result == null) {
return result;
}
}
return result;
}
// 动态代理拦截.org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof AopInfrastructureBean) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}

if (bean instanceof Advised) {
Advised advised = (Advised) bean;
if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
// Add our local Advisor to the existing proxy's Advisor chain...
if (this.beforeExistingAdvisors) {
advised.addAdvisor(0, this.advisor);
}
else {
advised.addAdvisor(this.advisor);
}
return bean;
}
}

if (isEligible(bean, beanName)) {
ProxyFactory proxyFactory = new ProxyFactory(bean);
// Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
proxyFactory.copyFrom(this);
proxyFactory.addAdvisor(this.advisor);
return proxyFactory.getProxy(this.beanClassLoader);
}

// No async proxy needed.
return bean;
}
// 动态代理的部分实现
// jdk
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
// cglib
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating CGLIB proxy: target source is " + this.advised.getTargetSource());
}

try {
Class<?> rootClass = this.advised.getTargetClass();
Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

Class<?> proxySuperClass = rootClass;
if (ClassUtils.isCglibProxyClass(rootClass)) {
proxySuperClass = rootClass.getSuperclass();
Class<?>[] additionalInterfaces = rootClass.getInterfaces();
for (Class<?> additionalInterface : additionalInterfaces) {
this.advised.addInterface(additionalInterface);
}
}

// Validate the class, writing log messages as necessary.
validateClassIfNecessary(proxySuperClass);

// Configure CGLIB Enhancer...
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new MemorySafeUndeclaredThrowableStrategy(UndeclaredThrowableException.class));
enhancer.setInterceptDuringConstruction(false);

Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);
enhancer.setCallbacks(callbacks);

// Generate the proxy class and create a proxy instance.
Object proxy;
if (this.constructorArgs != null) {
proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
}
else {
proxy = enhancer.create();
}

return proxy;
}
catch (CodeGenerationException ex) {
throw new AopConfigException("Could not generate CGLIB subclass of class [" +
this.advised.getTargetClass() + "]: " +
"Common causes of this problem include using a final class or a non-visible class",
ex);
}
catch (IllegalArgumentException ex) {
throw new AopConfigException("Could not generate CGLIB subclass of class [" +
this.advised.getTargetClass() + "]: " +
"Common causes of this problem include using a final class or a non-visible class",
ex);
}
catch (Exception ex) {
// TargetSource.getTarget() failed
throw new AopConfigException("Unexpected AOP exception", ex);
}
}


这是比较通用的代理bean的方法,当然有些BeanPostProcessors会重写这个方法,比如定时任务的BeanPostProcessors,它并不需要生成代理bean,而是启动调度线程。

从上面的代码可以很明显的看出代理bean的生成、代理链,以及代理链的顺序(有时候需要注意顺序)。

5. 再进一步,更加透明化,自定义标签

让人着迷的spring标签

xmlns:task="http://www.springframework.org/schema/task" http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd
<task:scheduler id="scheduler" pool-size="10"/>
<task:executor id="executor" pool-size="10"/>
<!-- 开启自动调度和异步方法 -->
<task:annotation-driven executor="executor" scheduler="scheduler"/>


自定义标签实现(未经过测试):

需要定义在META-INF下面定义spring.handlers和spring.schemas

spring.handlers
http\://www.springframework.org/schema/log=com.xjy.core.log.LogNamespaceHandler
spring.schemas
http\://www.springframework.org/schema/log/spring-log-3.2.xsd=com/xjy/core/log/config/spring-log-3.2.xsd


<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://www.springframework.org/schema/log"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
targetNamespace="http://www.springframework.org/schema/log"
elementFormDefault="qualified"
attributeFormDefault="unqualified">

<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the elements used in the Spring Framework's support for task execution and scheduling.
]]></xsd:documentation>
</xsd:annotation>

<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-3.2.xsd"/>
<xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-3.2.xsd"/>

<xsd:element name="annotation-driven">
<xsd:annotation>
<xsd:documentation><![CDATA[
Enables the detection of @Async and @Scheduled annotations on any Spring-managed
object. If present, a proxy will be generated for executing the annotated methods
asynchronously.

See Javadoc for the org.springframework.scheduling.annotation.EnableAsync and
org.springframework.scheduling.annotation.EnableScheduling annotations for information
on code-based alternatives to this XML element.
]]></xsd:documentation>
</xsd:annotation>
...其他
<!-- 定义log -->
<log:annotation-driven />


package com.xjy.log.core;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class LogNamespaceHandler extends NamespaceHandlerSupport {

@Override
public void init() {
this.registerBeanDefinitionParser("annotation-driven", new LogBeanDefinitionParser());
}

}


package com.xjy.log.core;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

public class LogBeanDefinitionParser implements BeanDefinitionParser {
private static final String logAnnotationProcessorBeanName = "com.xjy.core.log.internalLogAnnotationBeanPostProcessor";

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);

// Register component for the surrounding <task:annotation-driven> element.
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
parserContext.pushContainingComponent(compDefinition);

// Nest the concrete post-processor bean in the surrounding component.
BeanDefinitionRegistry registry = parserContext.getRegistry();

if (registry.containsBeanDefinition(logAnnotationProcessorBeanName)) {
parserContext.getReaderContext().error(
"Only one LogAnnotationProcessorBeanName may exist within the context.", source);
} else {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
"com.xjy.core.log.LogAnnotationBeanPostProcessor");
builder.getRawBeanDefinition().setSource(source);
registerPostProcessor(parserContext, builder, logAnnotationProcessorBeanName);
}
// Finally register the composite component.
parserContext.popAndRegisterContainingComponent();

return null;
}

private static void registerPostProcessor(
ParserContext parserContext, BeanDefinitionBuilder builder, String beanName) {

builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(beanName, builder.getBeanDefinition());
BeanDefinitionHolder holder = new BeanDefinitionHolder(builder.getBeanDefinition(), beanName);
parserContext.registerComponent(new BeanComponentDefinition(holder));
}

}


其实说白了,就是不用手动在去定义bean了,更方便,更透明了。

至此,一个spring插件就定义完了

6. 再进一步,抽象日志处理器,这一步和spring没啥关系

注解中可以选择使用哪个日志处理器:

package com.xjy.log.core;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 日志注解,默认的日志处理器只是简单的打印了耗时
* @author freeman.xu
*
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/** key,如果不指定则是'被标注类的全包名#方法签名' */
String key() default "";
/** 日志描述 */
String desc() default "";
/** 日志处理器 */
String logProcessor() default "";
}


代理方法执行的时候选择对应的日志处理器即:

private LogProcessor determineLogProcessor(Method method) {

LogProcessor logProcessor = this.logProcessors.get(method);
if (logProcessor == null) {
logProcessor = this.defaultLogProcessor;
String qualifier = getExecutorQualifier(method);
if (StringUtils.hasLength(qualifier)) {
Assert.notNull(this.beanFactory, "BeanFactory must be set on " + getClass().getSimpleName()
+ " to access qualified logProcessor '" + qualifier + "'");
logProcessor = BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, LogProcessor.class, qualifier);
} else if (logProcessor == null) {
return null;
}
this.logProcessors.put(method, logProcessor);
}
return logProcessor;
}

private String getExecutorQualifier(Method method) {
Log log = AnnotationUtils.findAnnotation(method, Log.class);
if (log == null) {
log = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Log.class);
}
return (log != null ? log.logProcessor() : null);
}


日志处理器:

package com.xjy.log.core;

import org.aopalliance.intercept.MethodInvocation;

/**
* 日志处理器
* @author freeman.xu
*
*/
public interface LogProcessor {

Object log(MethodInvocation invocation) throws Throwable;

}


package com.xjy.log.core;

import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DefaultLogProcessor extends AbstractLogProcessor {
private static final Logger logger = LoggerFactory.getLogger(DefaultLogProcessor.class);

@Override
public Object log(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = getTargetClass(invocation);
Method specificMethod = getSpecificMethod(invocation);
String logDesc = getLogDesc(targetClass, specificMethod);
logger.info("start execute [{}] method [{}],desc is [{}]", targetClass, specificMethod, logDesc);
long start = System.currentTimeMillis();
Object result = null;
try {
result = invocation.proceed();
} catch (Throwable e) {
logger.error("execute [" + targetClass + "] method [" + specificMethod + "] error", e);
throw e;
}
long end = System.currentTimeMillis();
logger.info("end execute [{}] method [{}],desc is [{}],cost[{}] ms", targetClass, specificMethod, logDesc, (end - start));
return result;
}

}


package com.xjy.web.log;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.servlet.http.HttpServletRequest;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.thrift.TException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ModelAttribute;

import com.xjy.log.core.AbstractLogProcessor;
import com.xjy.log.core.Log;
import com.xjy.log.service.ILogService;
import com.xjy.log.service.thrift.LogServiceThrift;
import com.xjy.log.service.thrift.LogThrift;

/**
* 用户处理web请求的日志处理器。<br>
* 一般来说,这类的日志建议把{@link Log}的key设置为访问的url,如果url是动态的。<br>
* 那么建议方法的参数传递一个HttpServletRequest,或者使用{@link ModelAttribute}初始化HttpServletRequest,便于处理器获取url。
* @author xujieyang
*
*/
@Component("webLogProcessor")
public class WebLogProcessor extends AbstractLogProcessor {

private static final Logger logger = LoggerFactory.getLogger(WebLogProcessor.class);

@Autowired(required = false)
@Qualifier("logServiceRmi")
private ILogService logServiceRmi;

@Autowired
@Qualifier("logExecutor")
private ThreadPoolTaskExecutor logExecutor;

@Override
public Object log(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = getTargetClass(invocation);
Method specificMethod = getSpecificMethod(invocation);
String url = getUrl(invocation, targetClass, specificMethod);
long start = System.currentTimeMillis();
logger.info("user start visis page [{}]", url);
Object result = null;
try {
result = invocation.proceed();
long end = System.currentTimeMillis();
Log log = this.getLog(targetClass, specificMethod);
addLog(url , log.desc(), (end -start), "xujieyang_blog", true);
logger.info("use end visit page [{}], cost {} ms", url, (end -start));
} catch (Throwable e) {
// 异步添加失败日志
Log log = this.getLog(targetClass, specificMethod);
addLog(url, log.desc(), -1L, "xujieyang_blog", false);
logger.error("execute [" + targetClass + "] method [" + specificMethod + "] error", e);
throw e;
}
return result;
}

private String getUrl(MethodInvocation invocation, Class<?> targetClass, Method specificMethod) {
Log log = this.getLog(targetClass, specificMethod);
String url = log.key();
if(StringUtils.isNotBlank(url)) {
return url;
}
HttpServletRequest request = this.getHttpServletRequest(invocation);
if(request != null) {
url = request.getRequestURI();
} else {
logger.warn("Can not get HttpServletRequest, but this is a web log processor, "
+ "so you should provide HttpServletRequest, then logProcessor can log url.");
url = getLogDesc(targetClass, specificMethod);
}
return url;
}

private HttpServletRequest getHttpServletRequest(MethodInvocation invocation) {
Object traget = invocation.getThis();
HttpServletRequest request = null;
try {
// 从class中获取
request = (HttpServletRequest) PropertyUtils.getProperty(traget, "request");
} catch (IllegalAccessException | InvocationTargetException
| NoSuchMethodException e) {
logger.warn("can not get request property");
}
if(request == null) {
// 从方法参数中获取
Object[] args = invocation.getArguments();
if(ArrayUtils.isEmpty(args)) {
return request;
}
for(Object arg : args) {
// Class1.isAssignableFrom(Class2)
// isAssignableFrom   是用来判断一个类Class1和另一个类Class2是否相同或是另一个类的超类或接口
if(HttpServletRequest.class.isAssignableFrom(arg.getClass())) {
return (HttpServletRequest) arg;
}
}
}
return request;
}

private void addLog(String key, String desc, long costMs, String systemKey, boolean success) {
if(logServiceRmi == null) {
logger.warn("logServiceRmi is null");
return;
}
logExecutor.execute(new Runnable() {

@Override
public void run() {
com.xjy.log.model.Log log = new com.xjy.log.model.Log();
log.setCostMs(costMs);
log.setDesc(desc);
log.setKey(key);
log.setSystemKey(systemKey);
log.setSuccess(success);
logServiceRmi.add(log);
}
});
}

}


7. 举一反三,锁实现

package com.xjy.core.lock;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 定时任务锁,也支持分布式锁,锁的默认实现为单机锁。<br>
* @author xujieyang
*
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock {
/** 锁的key */
String key();
/** 锁的value */
String value();
/** key过期的时间,使用秒为单位 */
long expireTime() default 0L;
String locker() default "";
}


package com.xjy.core.lock;

import org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

@SuppressWarnings("serial")
public class LockAnnotationBeanPostProcessor extends AbstractAdvisingBeanPostProcessor implements BeanFactoryAware {

public LockAnnotationBeanPostProcessor() {
this.beforeExistingAdvisors = true;
}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
LockAnnotationAdvisor advisor = new LockAnnotationAdvisor(new DefaultLocker());
advisor.setBeanFactory(beanFactory);
this.advisor = advisor;
}

@Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}

}


package com.xjy.core.lock;

import java.lang.annotation.Annotation;
import java.util.LinkedHashSet;
import java.util.Set;

import org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

@SuppressWarnings("serial")
public class LockAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {

private Advice advice;

private Pointcut pointcut;

public LockAnnotationAdvisor(Locker locker) {
Set<Class<? extends Annotation>> lockAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>(2);
lockAnnotationTypes.add(Lock.class);
this.advice = this.buildAdvice(locker);
this.pointcut = buildPointcut(lockAnnotationTypes);
}

protected Advice buildAdvice(Locker locker) {
return new LockInterceptor(locker);
}

protected Pointcut buildPointcut(Set<Class<? extends Annotation>> redisLockAnnotationTypes) {
ComposablePointcut result = null;
for (Class<? extends Annotation> redisLockAnnotationType : redisLockAnnotationTypes) {
Pointcut cpc = new AnnotationMatchingPointcut(redisLockAnnotationType, true);
Pointcut mpc = AnnotationMatchingPointcut.forMethodAnnotation(redisLockAnnotationType);
if (result == null) {
result = new ComposablePointcut(cpc).union(mpc);
}
else {
result.union(cpc).union(mpc);
}
}
return result;
}

@Override
public Pointcut getPointcut() {
return this.pointcut;
}

@Override
public Advice getAdvice() {
return this.advice;
}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (this.advice instanceof BeanFactoryAware) {
((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
}
}

}


package com.xjy.core.lock;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

public class LockInterceptor implements MethodInterceptor, Ordered, BeanFactoryAware {

private final Map<Method, Locker> lockers = new ConcurrentHashMap<Method, Locker>(16);
private BeanFactory beanFactory;
private Locker defaultLocker;

public LockInterceptor() {
}

public LockInterceptor(Locker defaultLocker) {
this.defaultLocker = defaultLocker;
}

@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}

@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
Locker locker = determineLocker(specificMethod);
return locker.lock(invocation);
}

private Locker determineLocker(Method method) {

Locker locker = this.lockers.get(method);
if (locker == null) {
locker = this.defaultLocker;
String qualifier = getExecutorQualifier(method);
if (StringUtils.hasLength(qualifier)) {
Assert.notNull(this.beanFactory, "BeanFactory must be set on " + getClass().getSimpleName()
+ " to access qualified logProcessor '" + qualifier + "'");
locker = BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, Locker.class, qualifier);
} else if (locker == null) {
return null;
}
this.lockers.put(method, locker);
}
return locker;
}

private String getExecutorQualifier(Method method) {
Lock lock = AnnotationUtils.findAnnotation(method, Lock.class);
if (lock == null) {
lock = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Lock.class);
}
return (lock != null ? lock.locker() : null);
}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}

}


8. spring之代理的代理

java rmi和spring

只需要配置就可以发布为rmi服务,示例:

<!-- 发布为rmi服务 -->
<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
<property name="port" value="${log.rmi.port}" />
</bean>
<bean class="org.springframework.remoting.rmi.RmiServiceExporter"
p:service-ref="logService"
p:serviceName="logServiceRmi"
p:serviceInterface="com.xjy.log.service.ILogService"
p:registry-ref="registry"
p:registryHost="${log.rmi.host}"/>


<!-- rmi客户端 -->
<bean id="logServiceRmi"
class="org.springframework.remoting.rmi.RmiProxyFactoryBean"
p:serviceUrl="${logServiceRmi}"
p:serviceInterface="com.xjy.log.service.ILogService"
p:cacheStub="false"
p:refreshStubOnConnectFailure="true" />


现在你只需要在需要rmi服务的地方注入logServiceRmi就可以了。这看起来很完美,但我现在用的是thrift呀,rmi已经没落了。

10. #### spring和thrift ####

实现类似的rmi服务:

package com.xjy.core.remoting.thrift;

import java.lang.reflect.InvocationTargetException;

import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.reflect.ConstructorUtils;
import org.apache.thrift.TProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;

// TODO 改造成一次可以发布多个server服务
public abstract class AbstractThriftServer implements InitializingBean {
private static final Logger log = LoggerFactory.getLogger(AbstractThriftServer.class);
/** thrift生成的service中Processor内部类的名称 */
private static final String processorInnerClassName = "Processor";

/** 发布service的端口号 */
protected Integer port;
/** thrift生成的service的实现类 */
protected Object service;
/** thrift生成的service的包名 */
protected String triftServicePackageName;

public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public Object getService() {
return service;
}
public void setService(Object service) {
this.service = service;
}
public String getTriftServicePackageName() {
return triftServicePackageName;
}
public void setTriftServicePackageName(String triftServicePackageName) {
this.triftServicePackageName = triftServicePackageName;
}

@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(port, "port property required");
Assert.notNull(service, "service property required");
Assert.notNull(triftServicePackageName, "triftServicePackageName property required");
TProcessor tProcessor = initTprocessor();
// 一个tProcessor对应一个线程
new Thread(new Runnable() {
@Override
public void run() {
startThriftServer(tProcessor);
}
}).start();
}

/**
* 初始化TProcessor
* @return
* @throws Exception
*/
protected TProcessor initTprocessor() throws Exception {
log.info("start initTprocessor, triftServicePackageName is {}", triftServicePackageName);
TProcessor tProcessor = null;
try {
Class<?> processorClass = ClassUtils.getClass(triftServicePackageName + "$" + processorInnerClassName);
//                  async ? ClassUtils.getClass(triftServicePackageName + "$" + asyncProcessorInnerClassName) :
//              ClassUtils.getClass(triftServicePackageName + "$" + processorInnerClassName);
// TODO 验证service的正确性
//          Class<?> ifaceClass = ClassUtils.getClass(genTriftServicePackName + "$Iface");
tProcessor = (TProcessor) ConstructorUtils.invokeConstructor(processorClass, service);
} catch (ClassNotFoundException
| NoSuchMethodException | IllegalAccessException
| InvocationTargetException | InstantiationException e) {
log.error("initTprocessor error, triftServicePackageName is " + triftServicePackageName, e);
throw e;
}
log.info("end initTprocessor, triftServicePackageName is {}", triftServicePackageName);
return tProcessor;
}

/**
* 启动服务,子类选择使用不同的方式启动thrift服务
* @param tProcessor
*/
protected abstract void startThriftServer(TProcessor tProcessor);

}


package com.xjy.core.remoting.thrift;

import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SimpleThriftServer extends AbstractThriftServer {
private static final Logger log = LoggerFactory.getLogger(SimpleThriftServer.class);

@Override
protected void startThriftServer(TProcessor tProcessor) {
log.info("startThriftServer type is simple, port is {}, triftServicePackageName is {}", port, triftServicePackageName);
TServerSocket serverTransport;
try {
serverTransport = new TServerSocket(port);
TServer.Args tArgs = new TServer.Args(serverTransport);
tArgs.processor(tProcessor);
tArgs.protocolFactory(new TBinaryProtocol.Factory());
// simple sever 用于测试
TServer server = new TSimpleServer(tArgs);
// 此处会阻塞
server.serve();
} catch (TTransportException e) {
log.error("startThriftServer error type is simple, port is " + port + "triftServicePackageName is " + triftServicePackageName, e);
}
}

}


package com.xjy.core.remoting.thrift;

import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ThreadPoolThriftServer extends AbstractThriftServer {
private static final Logger log = LoggerFactory.getLogger(ThreadPoolThriftServer.class);

@Override
protected void startThriftServer(TProcessor tProcessor) {
log.info("startThriftServer type is ThreadPool, port is {}, triftServicePackageName is {}", port, triftServicePackageName);
TServerSocket serverTransport;
try {
serverTransport = new TServerSocket(port);
TThreadPoolServer.Args ttpsArgs = new TThreadPoolServer.Args(serverTransport);
ttpsArgs.processor(tProcessor);
ttpsArgs.protocolFactory(new TBinaryProtocol.Factory());
// 线程池服务模型,使用标准的阻塞式IO,预先创建一组线程处理请求。
TServer server = new TThreadPoolServer(ttpsArgs);
// 此处会阻塞
server.serve();
} catch (TTransportException e) {
log.error("startThriftServer error type is ThreadPool, port is " + port + "triftServicePackageName is " + triftServicePackageName, e);
}
}

}


package com.xjy.core.remoting.thrift;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang.reflect.ConstructorUtils;
import org.apache.commons.lang.reflect.MethodUtils;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.ClassUtils;

public class ThriftClientProxyFactoryBean implements FactoryBean<Object>, InitializingBean, MethodInterceptor {
private static final Logger log = LoggerFactory.getLogger(ThriftClientProxyFactoryBean.class);
/** thrift生成的service中Iface内部类的名称 */
private static final String iFaceInnerClassName = "Iface";
/** thrift生成的service中Client内部类的名称 */
private static final String clientInnerClassName = "Client";

/** ThriftClientProxyFactoryBean产生的bean */
private Object serviceProxy;
/** 连接到thrift的端口号 */
private Integer port;
/** 连接到thrift的host */
private String host;
/** thrift生成的service的包名 */
private String triftServicePackageName;
/** 连接超时时间 */
private Integer timeout = 3000;

public String getTriftServicePackageName() {
return triftServicePackageName;
}

public void setTriftServicePackageName(String triftServicePackageName) {
this.triftServicePackageName = triftServicePackageName;
}

public String getHost() {
return host;
}

public void setHost(String host) {
this.host = host;
}

public Integer getPort() {
return port;
}

public void setPort(Integer port) {
this.port = port;
}

public Integer getTimeout() {
return timeout;
}

public void setTimeout(Integer timeout) {
this.timeout = timeout;
}

@Override
public Object getObject() throws Exception {
return this.serviceProxy;
}

@Override
public Class<?> getObjectType() {
if(triftServicePackageName == null) {
return null;
}
Class<?> objectType = null;
try {
objectType = org.apache.commons.lang.ClassUtils.getClass(triftServicePackageName + "$" + iFaceInnerClassName);
} catch (ClassNotFoundException e) {
log.error("getObjectType error", e);
}
return objectType;
}

@Override
public boolean isSingleton() {
return true;
}

@Override
public void afterPropertiesSet() throws Exception {
this.serviceProxy = new ProxyFactory(org.apache.commons.lang.ClassUtils.getClass(triftServicePackageName + "$" + iFaceInnerClassName), this).getProxy(ClassUtils.getDefaultClassLoader());
}

@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
TTransport transport = null;
if(timeout.equals(new Integer(-1))) {
transport = new TSocket(host, port);
} else {
transport = new TSocket(host, port, timeout);
}
Object result = null;
// 协议要和服务端一致
TProtocol protocol = new TBinaryProtocol(transport);
try {
Class<?> clientClass = org.apache.commons.lang.ClassUtils.getClass(triftServicePackageName + "$" + clientInnerClassName);
transport.open();
Method method = invocation.getMethod();
result = MethodUtils.invokeMethod(ConstructorUtils.invokeConstructor(clientClass, protocol),
method.getName(), invocation.getArguments());
} catch (ClassNotFoundException | TTransportException
| NoSuchMethodException | IllegalAccessException
| InvocationTargetException | InstantiationException e) {
if(e instanceof TTransportException) {
log.error("open transport error. host is " + host + " port is " + port, e);
} else {
log.error("MethodInvocation error", e);
}
throw e;
} catch (Exception e) {
log.error("error", e);
throw e;
// TODO 异常的处理
} finally {
if (null != transport && transport.isOpen()) {
transport.close();
}
}
return result;
}

}


好了,你现在可以像使用本地的bean一样,直接在需要的地方注入thrift的客户端了

@Autowired
@Qualifier("blogServiceThrift")
private BlogService.Iface blogServiceThrift;


核心的原理其实还是动态代理,server其实没什么说的,主要是客户端,客户端是个MethodInterceptor、FactoryBean,而且在产生bean的时候使用了动态代理,而你代理的bean可能已经是被动态代理的bean了,就是代理的代理了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring