/* * Copyright (c) 2019 Baidu, Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.baidu.brpc.spring.boot.autoconfigure; import com.baidu.bjf.remoting.protobuf.utils.JDKCompilerHelper; import com.baidu.bjf.remoting.protobuf.utils.compiler.Compiler; import com.baidu.brpc.client.RpcClientOptions; import com.baidu.brpc.interceptor.Interceptor; import com.baidu.brpc.naming.NamingServiceFactory; import com.baidu.brpc.spring.RpcProxyFactoryBean; import com.baidu.brpc.spring.RpcServiceExporter; import com.baidu.brpc.spring.annotation.AbstractAnnotationParserCallback; import com.baidu.brpc.spring.annotation.RpcAnnotationResolverListener; import com.baidu.brpc.spring.annotation.RpcExporter; import com.baidu.brpc.spring.annotation.RpcProxy; import com.baidu.brpc.spring.boot.autoconfigure.config.BrpcConfig; import com.baidu.brpc.spring.boot.autoconfigure.config.BrpcProperties; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.springframework.aop.support.AopUtils; import org.springframework.beans.BeansException; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; import org.springframework.util.ClassUtils; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; /** * Supports annotation resolver for {@link RpcProxy} and {@link RpcExporter} under springboot. * * @author huwenwei */ @Setter @Getter @Slf4j public class SpringBootAnnotationResolver extends AbstractAnnotationParserCallback implements InitializingBean, PriorityOrdered { /** * properties from spring boot application.yml */ private BrpcProperties brpcProperties; /** * The rpc clients. */ private List<RpcProxyFactoryBean> rpcClients = new ArrayList<RpcProxyFactoryBean>(); /** * The port mapping exporters. */ private Map<Integer, RpcServiceExporter> portMappingExporters = new HashMap<Integer, RpcServiceExporter>(); /** * The compiler. */ private Compiler compiler; /** * status to control start only once. */ private AtomicBoolean started = new AtomicBoolean(false); /* the default naming service url */ private String namingServiceUrl; /** * The default registry center service for all service */ private NamingServiceFactory namingServiceFactory; /** * The default interceptors for all service */ // private List<Interceptor> interceptors; /** * The protobuf rpc annotation resolver listener. */ private RpcAnnotationResolverListener protobufRpcAnnotationResolverListener; private int order = Ordered.LOWEST_PRECEDENCE - 3; @Override public Object annotationAtField(Annotation t, Object value, String beanName, PropertyValues pvs, DefaultListableBeanFactory beanFactory, Field field) throws BeansException { if (t instanceof RpcProxy) { try { log.info("Annotation 'BrpcProxy' on field '" + field.getName() + "' for target '" + beanName + "' created"); return parseRpcProxyAnnotation((RpcProxy) t, field.getType(), beanFactory); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } return value; } @Override public Object annotationAtMethod(Annotation t, Object bean, String beanName, PropertyValues pvs, DefaultListableBeanFactory beanFactory, Method method) throws BeansException { if (t instanceof RpcProxy) { try { log.info("Annotation 'BrpcProxy' on method '" + method.getName() + "' for target '" + beanName + "' created"); return parseRpcProxyAnnotation((RpcProxy) t, method.getParameterTypes()[0], beanFactory); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } return null; } @Override public Object annotationAtType(Annotation t, Object bean, String beanName, ConfigurableListableBeanFactory beanFactory) throws BeansException { if (t instanceof RpcExporter) { log.info("Annotation 'RpcExporter' for target '" + beanName + "' created"); parseRpcExporterAnnotation((RpcExporter) t, beanFactory, beanFactory.getBean(beanName)); } return bean; } @Override public void annotationAtTypeAfterStarted(Annotation t, Object bean, String beanName, ConfigurableListableBeanFactory beanFactory) throws BeansException { if (started.compareAndSet(false, true)) { // do export service here Collection<RpcServiceExporter> values = portMappingExporters.values(); for (RpcServiceExporter rpcServiceExporter : values) { try { rpcServiceExporter.afterPropertiesSet(); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } } } @Override public void destroy() throws Exception { if (rpcClients != null) { for (RpcProxyFactoryBean bean : rpcClients) { try { bean.destroy(); } catch (Exception e) { log.error(e.getMessage(), e.getCause()); } } } if (portMappingExporters != null) { Collection<RpcServiceExporter> exporters = portMappingExporters.values(); for (RpcServiceExporter rpcServiceExporter : exporters) { try { rpcServiceExporter.destroy(); } catch (Exception e) { log.error(e.getMessage(), e.getCause()); } } } if (protobufRpcAnnotationResolverListener != null) { protobufRpcAnnotationResolverListener.destroy(); } } @Override public Class<? extends Annotation> getTypeAnnotation() { return RpcExporter.class; } @Override public List<Class<? extends Annotation>> getMethodFieldAnnotation() { List<Class<? extends Annotation>> list = new ArrayList<Class<? extends Annotation>>(); list.add(RpcProxy.class); return list; } @Override public void afterPropertiesSet() throws Exception { if (compiler != null) { JDKCompilerHelper.setCompiler(compiler); } } @Override public int getOrder() { return order; } /** * Sets the compiler. * * @param compiler the new compiler */ public void setCompiler(Compiler compiler) { this.compiler = compiler; } /** * Parses the {@link RpcExporter} annotation. * * @param rpcExporter the rpc exporter * @param beanFactory the bean factory * @param bean the bean */ private void parseRpcExporterAnnotation(RpcExporter rpcExporter, ConfigurableListableBeanFactory beanFactory, Object bean) { Class<?> serviceClass = AopUtils.getTargetClass(bean); Class<?>[] interfaces = ClassUtils.getAllInterfacesForClass(serviceClass); if (interfaces.length != 1) { throw new RuntimeException("service interface num must equal 1, " + serviceClass.getName()); } Class<?> serviceInterface = interfaces[0]; BrpcConfig brpcConfig = getServiceConfig(beanFactory, serviceInterface); // if there are multi service on one port, the first service configs effect only. Integer port = brpcConfig.getServer().getPort(); RpcServiceExporter rpcServiceExporter = portMappingExporters.get(port); if (rpcServiceExporter == null) { rpcServiceExporter = new RpcServiceExporter(); portMappingExporters.put(port, rpcServiceExporter); rpcServiceExporter.setServicePort(port); rpcServiceExporter.copyFrom(brpcConfig.getServer()); if (brpcConfig.getNaming() != null) { rpcServiceExporter.setNamingServiceUrl(brpcConfig.getNaming().getNamingServiceUrl()); } } // interceptor if (brpcConfig.getServer() != null && StringUtils.isNoneBlank(brpcConfig.getServer().getInterceptorBeanNames())) { String[] interceptorNameArray = brpcConfig.getServer().getInterceptorBeanNames().trim().split(","); for (String interceptorBeanName : interceptorNameArray) { Interceptor interceptor = beanFactory.getBean(interceptorBeanName.trim(), Interceptor.class); if (!rpcServiceExporter.getInterceptors().contains(interceptor)) { rpcServiceExporter.getInterceptors().add(interceptor); } } } // naming options rpcServiceExporter.getServiceNamingOptions().put(bean, brpcConfig.getNaming()); if (brpcConfig.getServer() != null && brpcConfig.getServer().isUseSharedThreadPool()) { rpcServiceExporter.getCustomOptionsServiceMap().put(brpcConfig.getServer(), bean); } else { rpcServiceExporter.getRegisterServices().add(bean); } if (protobufRpcAnnotationResolverListener != null) { protobufRpcAnnotationResolverListener.onRpcExporterAnnotationParsered( rpcExporter, port, bean, rpcServiceExporter.getRegisterServices()); } } /** * Parses the rpc proxy annotation. * * @param rpcProxy the rpc proxy * @param beanFactory the bean factory * @return the object * @throws Exception the exception */ private Object parseRpcProxyAnnotation(RpcProxy rpcProxy, Class serviceInterface, DefaultListableBeanFactory beanFactory) throws Exception { RpcProxyFactoryBean rpcProxyFactoryBean; String factoryBeanName = "&" + serviceInterface.getSimpleName(); try { rpcProxyFactoryBean = beanFactory.getBean(factoryBeanName, RpcProxyFactoryBean.class); if (rpcProxyFactoryBean != null) { return rpcProxyFactoryBean.getObject(); } } catch (NoSuchBeanDefinitionException ex) { // continue the following logic to create new factory bean } rpcProxyFactoryBean = createRpcProxyFactoryBean(rpcProxy, beanFactory, serviceInterface); rpcClients.add(rpcProxyFactoryBean); Object object = rpcProxyFactoryBean.getObject(); if (protobufRpcAnnotationResolverListener != null) { protobufRpcAnnotationResolverListener.onRpcProxyAnnotationParsed(rpcProxy, rpcProxyFactoryBean, rpcProxyFactoryBean.getObject()); } return object; } /** * Creates the rpc proxy factory bean. * * @return the rpc proxy factory bean */ private RpcProxyFactoryBean createRpcProxyFactoryBean(RpcProxy rpcProxy, DefaultListableBeanFactory beanFactory, Class serviceInterface) { GenericBeanDefinition beanDef = new GenericBeanDefinition(); beanDef.setBeanClass(RpcProxyFactoryBean.class); beanDef.setDependsOn("brpcApplicationContextUtils"); MutablePropertyValues values = new MutablePropertyValues(); BrpcConfig brpcConfig = getServiceConfig(beanFactory, serviceInterface); for (Field field : RpcClientOptions.class.getDeclaredFields()) { try { if (field.getType().equals(Logger.class)) { // ignore properties of org.slf4j.Logger class continue; } field.setAccessible(true); values.addPropertyValue(field.getName(), field.get(brpcConfig.getClient())); } catch (Exception ex) { log.warn("field not exist:", ex); } } values.addPropertyValue("serviceInterface", serviceInterface); values.addPropertyValue("serviceId", rpcProxy.name()); if (brpcConfig.getNaming() != null) { values.addPropertyValue("namingServiceUrl", brpcConfig.getNaming().getNamingServiceUrl()); values.addPropertyValue("group", brpcConfig.getNaming().getGroup()); values.addPropertyValue("version", brpcConfig.getNaming().getVersion()); values.addPropertyValue("ignoreFailOfNamingService", brpcConfig.getNaming().isIgnoreFailOfNamingService()); } // interceptor String interceptorNames = brpcConfig.getClient().getInterceptorBeanNames(); if (!StringUtils.isBlank(interceptorNames)) { List<Interceptor> customInterceptors = new ArrayList<>(); String[] interceptorNameArray = interceptorNames.split(","); for (String interceptorBeanName : interceptorNameArray) { Interceptor interceptor = beanFactory.getBean(interceptorBeanName, Interceptor.class); customInterceptors.add(interceptor); } values.addPropertyValue("interceptors", Arrays.asList(customInterceptors)); } beanDef.setPropertyValues(values); String serviceInterfaceBeanName = serviceInterface.getSimpleName(); beanFactory.registerBeanDefinition(serviceInterfaceBeanName, beanDef); return beanFactory.getBean("&" + serviceInterfaceBeanName, RpcProxyFactoryBean.class); } private BrpcConfig getServiceConfig(ListableBeanFactory beanFactory, Class<?> serviceInterface) { if (brpcProperties == null) { brpcProperties = beanFactory.getBean(BrpcProperties.class); if (brpcProperties == null) { throw new RuntimeException("bean of BrpcProperties is null"); } } return brpcProperties.getServiceConfig(serviceInterface); } }