Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
mercyblitz committed Apr 22, 2024
1 parent 3af6528 commit 20dbcee
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.lang.NonNull;

import java.nio.charset.Charset;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@
*/
package io.microsphere.i18n.spring.annotation;

import io.microsphere.i18n.spring.context.I18nConfiguration;
import io.microsphere.i18n.ServiceMessageSource;
import io.microsphere.i18n.spring.beans.factory.ServiceMessageSourceFactoryBean;
import io.microsphere.i18n.spring.constants.I18nConstants;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.AbstractApplicationContext;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
Expand All @@ -26,17 +30,41 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static io.microsphere.i18n.ServiceMessageSource.COMMON_SOURCE;

/**
* Enables the extension for Spring Internationalisation
*
* @author <a href="mailto:[email protected]">Mercy<a/>
* @see I18nConfiguration
* @see I18nImportBeanDefinitionRegistrar
* @see ServiceMessageSourceFactoryBean
* @since 1.0.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Documented
@Inherited
@Import(I18nConfiguration.class)
@Import(I18nImportBeanDefinitionRegistrar.class)
public @interface EnableI18n {

/**
* Declares the sources of the {@link ServiceMessageSource} as the {@link ServiceMessageSourceFactoryBean} Spring Beans
* to be registered.
* <p>
* The attribute value will be merged from the Spring property whose name is {@link I18nConstants#SOURCES_PROPERTY_NAME}
*
* @return {@link ServiceMessageSource#COMMON_SOURCE} as the default
* @see I18nConstants#SOURCES_PROPERTY_NAME
*/
String[] sources() default {COMMON_SOURCE};

/**
* Whether to expose {@link I18nConstants#SERVICE_MESSAGE_SOURCE_BEAN_NAME the primary Spring Bean}
* {@link ServiceMessageSource} as the {@link MessageSource}
*
* @return <code>true</code> as default
* @see AbstractApplicationContext#MESSAGE_SOURCE_BEAN_NAME
* @see MessageSource
*/
boolean exposeMessageSource() default true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 io.microsphere.i18n.spring.annotation;

import io.microsphere.i18n.spring.DelegatingServiceMessageSource;
import io.microsphere.i18n.spring.beans.factory.ServiceMessageSourceFactoryBean;
import io.microsphere.i18n.spring.beans.factory.config.I18nBeanPostProcessor;
import io.microsphere.i18n.spring.beans.factory.support.ServiceMessageSourceBeanLifecyclePostProcessor;
import io.microsphere.i18n.spring.context.I18nApplicationListener;
import io.microsphere.i18n.spring.context.MessageSourceAdapter;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;

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

import static io.microsphere.i18n.spring.constants.I18nConstants.SERVICE_MESSAGE_SOURCE_BEAN_NAME;
import static io.microsphere.i18n.spring.constants.I18nConstants.SOURCES_PROPERTY_NAME;
import static io.microsphere.spring.util.BeanRegistrar.registerBeanDefinition;
import static io.microsphere.util.ArrayUtils.EMPTY_STRING_ARRAY;
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;
import static org.springframework.context.support.AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME;
import static org.springframework.core.annotation.AnnotationAttributes.fromMap;

/**
* I18n {@link ImportBeanDefinitionRegistrar}
*
* @author <a href="mailto:[email protected]">Mercy<a/>
* @see ImportBeanDefinitionRegistrar
* @see EnableI18n
* @since 1.0.0
*/
public class I18nImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {

private static final Class<? extends Annotation> ANNOTATION_TYPE = EnableI18n.class;

private Environment environment;

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AnnotationAttributes attributes = fromMap(metadata.getAnnotationAttributes(ANNOTATION_TYPE.getName()));
registerServiceMessageSourceBeanDefinitions(attributes, registry);
registerMessageSourceAdapterBeanDefinition(attributes, registry);
registerI18nApplicationListenerBeanDefinition(registry);
registerBeanPostProcessorBeanDefinitions(registry);
}

private void registerServiceMessageSourceBeanDefinitions(AnnotationAttributes attributes, BeanDefinitionRegistry registry) {
Set<String> sources = resolveSources(attributes);

for (String source : sources) {
String beanName = source + "ServiceMessageSource";
registerBeanDefinition(registry, beanName, ServiceMessageSourceFactoryBean.class, source);
}

// Register DelegatingServiceMessageSource as the Spring Primary Bean
BeanDefinition primaryBeanDefinition = rootBeanDefinition(DelegatingServiceMessageSource.class).setPrimary(true).getBeanDefinition();
registry.registerBeanDefinition(SERVICE_MESSAGE_SOURCE_BEAN_NAME, primaryBeanDefinition);
}

private Set<String> resolveSources(AnnotationAttributes attributes) {
Set<String> sources = new LinkedHashSet<>();
initSources(sources, () -> environment.getProperty(SOURCES_PROPERTY_NAME, String[].class, EMPTY_STRING_ARRAY));
initSources(sources, () -> attributes.getStringArray("sources"));
return sources;
}

private void initSources(Set<String> sources, Supplier<String[]> sourcesSupplier) {
for (String source : sourcesSupplier.get()) {
sources.add(environment.resolvePlaceholders(source));
}
}

private void registerMessageSourceAdapterBeanDefinition(AnnotationAttributes attributes, BeanDefinitionRegistry registry) {
boolean exposeMessageSource = attributes.getBoolean("exposeMessageSource");
if (exposeMessageSource) {
registerBeanDefinition(registry, MESSAGE_SOURCE_BEAN_NAME, MessageSourceAdapter.class);
}
}

private void registerI18nApplicationListenerBeanDefinition(BeanDefinitionRegistry registry) {
registerBeanDefinition(registry, I18nApplicationListener.class);
}

private void registerBeanPostProcessorBeanDefinitions(BeanDefinitionRegistry registry) {
registerBeanDefinition(registry, I18nBeanPostProcessor.class);
registerBeanDefinition(registry, ServiceMessageSourceBeanLifecyclePostProcessor.class);
}

@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
import io.microsphere.i18n.spring.context.MessageSourceAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.MessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

import static io.microsphere.util.ClassLoaderUtils.resolveClass;
import static org.springframework.aop.support.AopUtils.getTargetClass;


/**
* Internationalization {@link BeanPostProcessor}, Processing:
* <ul>
Expand All @@ -23,7 +26,11 @@ public class I18nBeanPostProcessor implements BeanPostProcessor {

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

private static final Class<?> LOCAL_VALIDATOR_FACTORY_BEAN_CLASS = LocalValidatorFactoryBean.class;
private static final ClassLoader classLoader = I18nBeanPostProcessor.class.getClassLoader();

private static final Class<?> VALIDATOR_FACTORY_CLASS = resolveClass("javax.validation.ValidatorFactory", classLoader);

private static final Class<?> LOCAL_VALIDATOR_FACTORY_BEAN_CLASS = resolveClass("org.springframework.validation.beanvalidation.LocalValidatorFactoryBean", classLoader);

private final ConfigurableApplicationContext context;

Expand All @@ -33,8 +40,11 @@ public I18nBeanPostProcessor(ConfigurableApplicationContext context) {

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (VALIDATOR_FACTORY_CLASS == null || LOCAL_VALIDATOR_FACTORY_BEAN_CLASS == null) {
return bean;
}

Class<?> beanType = AopUtils.getTargetClass(bean);
Class<?> beanType = getTargetClass(bean);
if (LOCAL_VALIDATOR_FACTORY_BEAN_CLASS.equals(beanType)) {
MessageSourceAdapter messageSourceAdapter = context.getBean(MessageSourceAdapter.class);
LocalValidatorFactoryBean localValidatorFactoryBean = (LocalValidatorFactoryBean) bean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ public interface I18nConstants {
*/
boolean DEFAULT_ENABLED = true;

/**
* The property name of the {@link ServiceMessageSource#getSource() sources} of {@link ServiceMessageSource}
*
* @see ServiceMessageSource#getSource()
*/
String SOURCES_PROPERTY_NAME = PROPERTY_NAME_PREFIX + "sources";

/**
* Default {@link Locale} property name
*/
Expand All @@ -45,7 +52,7 @@ public interface I18nConstants {
int COMMON_SERVICE_MESSAGE_SOURCE_ORDER = 500;

/**
* Primary {@link ServiceMessageSource} Bean
* The Primary {@link ServiceMessageSource} Bean Bean
*/
String SERVICE_MESSAGE_SOURCE_BEAN_NAME = "serviceMessageSource";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 io.microsphere.i18n.spring.context;

import io.microsphere.i18n.ServiceMessageSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SmartApplicationListener;

import static io.microsphere.i18n.spring.constants.I18nConstants.SERVICE_MESSAGE_SOURCE_BEAN_NAME;
import static io.microsphere.i18n.util.I18nUtils.destroyServiceMessageSource;
import static io.microsphere.i18n.util.I18nUtils.setServiceMessageSource;
import static org.springframework.util.ObjectUtils.containsElement;

/**
* Internationalization {@link ApplicationListener}
*
* @author <a href="mailto:[email protected]">Mercy<a/>
* @see SmartApplicationListener
* @since 1.0.0
*/
public class I18nApplicationListener implements SmartApplicationListener {

private static final Class<?>[] SUPPORTED_EVENT_TYPES = {
ContextRefreshedEvent.class,
ContextClosedEvent.class
};

@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return containsElement(SUPPORTED_EVENT_TYPES, eventType);
}

@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
onContextRefreshedEvent((ContextRefreshedEvent) event);
} else if (event instanceof ContextClosedEvent) {
onContextClosedEvent((ContextClosedEvent) event);
}
}

private void onContextRefreshedEvent(ContextRefreshedEvent event) {
ApplicationContext context = event.getApplicationContext();

ServiceMessageSource serviceMessageSource = context.getBean(SERVICE_MESSAGE_SOURCE_BEAN_NAME, ServiceMessageSource.class);
setServiceMessageSource(serviceMessageSource);
}


private void onContextClosedEvent(ContextClosedEvent event) {
destroyServiceMessageSource();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
* @author <a href="mailto:[email protected]">Mercy<a/>
* @see EnableI18n
* @since 1.0.0
* @deprecated use {@link EnableI18n}
*/
@Deprecated
@Import(value = {
ServiceMessageSourceBeanLifecyclePostProcessor.class
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package io.microsphere.i18n.spring.annotation;

import io.microsphere.i18n.AbstractSpringTest;
import io.microsphere.i18n.ServiceMessageSource;
import io.microsphere.i18n.spring.beans.TestServiceMessageSourceConfiguration;
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.SpringRunner;

import java.util.Locale;

import static io.microsphere.i18n.util.I18nUtils.serviceMessageSource;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;

/**
* {@link EnableI18n} Test
*
* @author <a href="mailto:[email protected]">Mercy<a/>
* @since 1.0.0
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
EnableI18nTest.class,
TestServiceMessageSourceConfiguration.class
})
@EnableI18n
public class EnableI18nTest extends AbstractSpringTest {

@Autowired
private ServiceMessageSource serviceMessageSource;

@Test
public void testGetMessage() {
// Testing Simplified Chinese
// If the Message Code is "a"
assertEquals("测试-a", serviceMessageSource.getMessage("a"));

// The same is true for overloaded methods with Message Pattern arguments
assertEquals("您好,World", serviceMessageSource.getMessage("hello", "World"));

// Returns null if code does not exist
assertNull(serviceMessageSource.getMessage("code-not-found"));

// Test English, because the English Message resource does not exist
assertEquals("Hello,World", serviceMessageSource.getMessage("hello", Locale.ENGLISH, "World"));

// Returns null if code does not exist
assertNull(serviceMessageSource.getMessage("code-not-found", Locale.US));
}

@Test
public void testCommonServiceMessageSource() {
assertSame(serviceMessageSource(), serviceMessageSource);
}
}

0 comments on commit 20dbcee

Please sign in to comment.