Skip to content

关于聊天软件中群聊消息提醒方式的想法

现在很多聊天软件中都有群聊,每条发言前会显示头像或用户名称用于区分不同的人发言,当聊天人数比较少时,发言频率不高时,所有消息内容都还能比较好的区分和识别,也都会一条条看,认真的回复。
当聊天室人数太多,大家都积极发言,消息铺天盖地,很多发言可能和自己没多大关系,或自己并不太关注的内容,可能很多只关心某个人的发言或者有@自己的发言。
一开始想到的办法是提供屏蔽群中某些人的发言的功能,但细想不对!如果屏蔽了,他们的发言你看不到,你的发言他们能看到,这就会造成对牛弹琴的状况,破坏了群成员之间交流的感情,故这个方法行不通!

整理想了一套方案:
1. 群自动将所有人的发言以不同浅色背景颜色进行显示。例如,你关心某人的发言背景是浅蓝色,之后你自己只要看浅蓝色背景的消息即可。 另外可以提供每个客户端设定某一个人消息显示颜色,提供多种浅色方案选择,其他未指定的则系统自动分配。
2. 对于@自己的消息,采用深色背景显示,深色颜色提供多种选择;另可选择是否加上消息边框以加重提醒标记。
3. 当处于离开状态时,对于@自己但还未看到已被消息刷屏顶上去的消息,均在窗口右上角浮动排列显示,点击某条可让主聊天界面回到发布那条消息的时候,以便了解当时聊天上下文环境。同时将这条消息之前所有@自己的消息从右上角浮动窗口去掉,直到所有@消息都看过。当然这个浮动窗口还是要让用户选择直接关掉,也可以设置不要弹出。
4. 聊天记录查询,增加@某人的消息查询方式,可以查询所有@某人的消息;

如果这样设计,聊天体验一定大大提高!

用Windows作为压力测试客户端需注意的环境设定

使用jmeter进行压力测试,定义100个thread,每个thread会发起三个http请求,总共就是30000个请求。因client请求server时会随机创建port和server建立连接,但如果client允许创建的port不够,则会报错。Windows默认允许创建的port最大为5000,故30000个client请求则可能会发生端口不足报错的情况。可通过修改windows注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters目录下的名称为MaxUserPort的REG_DWORD值来调整允许创建的最大端口,建议将其设置为60000(十进制),这样client允许创建的port数就达到将近60000个。

大量压测会使client产生TCP/IP连接端口,关闭连接后,这些端口并不会立刻释放,而是出于TIME_WAIT状态,默认情况需要240秒后才会释放。
可以修改windows注册表HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/ Services/TCPIP/Parameters目录下的名称为TcpTimedWaitDelay的REG_DWORD值,将其改为最小值30,以加快端口的释放速度。其取之范围为0-300,默认240(4分钟)。

Javadoc翻译——Spring注解@Bean

package org.springframework.context.annotation;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
    String[] name() default {};
    Autowire autowire() default Autowire.NO;
    String initMethod() default "";
    String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}

该注解表明一个方法返回一个bean类并被spring容器管理.

1. 概览

此注解的相关属性同xml <bean/>配置类似,例如:

@Bean
public MyBean myBean() {
 // instantiate and configure MyBean obj
 return obj;
}

2. 指定bean名称

当注解指定了name属性则会使用其指定的名称。这样很方便和直观!如果要求明确的指定bean,name属性是很有用的。
注意,其name可以指定一个数组,多个名称指向同一个bean实例。

@Bean(name={"b1","b2"}) // bean available as 'b1' and 'b2', but not 'myBean'
public MyBean myBean() {
 // instantiate and configure MyBean obj
 return obj;
}

3. scope, depends-on, primary, or lazy

注意,@Bean注解没有提供scope, depends-on, primary, or lazy属性。如果要使用这些属性有单独的注解@Scope, @DependsOn, @Primary, and @Lazy。例如:

@Bean
@Scope("prototype")
public MyBean myBean() {
 // instantiate and configure MyBean obj
 return obj;
}

4. Inter-bean引用

一般情况,@Bean注解在定义了@Configuration的类中使用。这种情况下,bean方法可以直接引用其他在同一个class中@Bean注解的方法。这种bean之间的引用是明确清晰、可追溯的,称之为“Inter-bean引用”,保证了符合Scope和AOP特性,就像getBean()方法查找一样。还有从基础‘Spring JavaConfig’项目特性要求在运行时将这些配置CGLIB子类化。因此,@Configuration类和其中的工厂类方法不能标记为final或者private。例如:

@Configuration
public class AppConfig {
 @Bean
 public FooService fooService() {
     return new FooService(fooRepository());
 }
 @Bean
 public FooRepository fooRepository() {
     return new JdbcFooRepository(dataSource());
 }
 // ...
}

5. @Bean Lite模式

没有被@Configuration注解的类也可以用@Bean注解其中的方法。例如用@Componet注解或POJO类。这种情况下,会通过一种叫做lite的模式处理@Bean注解的方法。
lite模式的bean方法只会被容器当作原始工厂方法(像xml中配置工厂类方法一样),其scope和lifecycle只在调用引用范围。class类也不会被修改保持原始状态,针对class类和工厂方法也不会有其他特别的约束。和@Configuration类中的bean方法对比,lite模式的bean方法不支持“Inter-bean引用”,lite模式下的方法调用只是一般的java方法调用。 Spring不会通过CBLIB代理进行注入。这和代理模式下inter-@Transactional方法的调用类似,spring不会拦截调用——spring只有在AspectJ模式下才会。
例如:

@Component
public class Calculator {
 public int sum(int a, int b) {
     return a+b;
 }

 @Bean
 public MyBean myBean() {
     return new MyBean();
 }
}

6. Bootstrapping(引导启动)

查看@Configuration Javadoc了解容器使用AnnotationConfigApplicationContext和其他相关类实现引导启动。

7. 返回BeanFactoryPostProcessor类型的@Bean方法

特别要注意,返回BeanFactoryPostProcessor (BFPP)类型的@Bean方法。因为在container的lifecycle中BFPP对象需要提前初始化,它们会干扰@Configuration类中的@Autowired, @Value, and @PostConstruct这些注解的处理。为了解决这个lifecycle的问题,需将返回BFPP的@Bean方法标记为static的。例如:

@Bean
public static PropertyPlaceholderConfigurer ppc() {
 // instantiate, configure and return ppc ...
}

标记为static后就无需@Configuration类初始化就可以进行调用,避免以上lifecycle冲突。但是static标记的@Bean方法则不会有scope和AOP特性了。这种方式在其他情况也是有用的,因为这些方法一般也不会被其他@Bean方法调用。另外提醒,任何非static @Bean方法返回一个BeanFactoryPostProcessor类型将会产生一个WARN警告日志。

Javadoc翻译——Spring @Configuration注解

org.springframework.context.annotation.Configuration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration

此注解表明该类中定义了@Bean注解的方法,并在运行时由spring容器对这些bean生成bean定义和服务请求。

 @Configuration
 public class AppConfig {
     @Bean
     public MyBean myBean() {
         // instantiate, configure and return bean ...
     }
 }

1. Bootstrapping @Configuration classes (引导加载@Configuration类)

1.1 使用AnnotationConfigApplicationContext的方式:

@Configuration类一般通过AnnotationConfigApplicationContext或子类AnnotationConfigWebApplicationContext引导加载,例如:

 AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
 ctx.register(AppConfig.class);
 ctx.refresh();
 MyBean myBean = ctx.getBean(MyBean.class);
 // use myBean ...

查看AnnotationConfigApplicationContext Javadoc了解更多,或查看AnnotationConfigWebApplicationContext了解web.xml配置方式。

1.2 通过Spring XML 配置:

不同于直接在AnnotationConfigApplicationContext中注册,另一种选择是通过在xml中定义bean,如下:

 <beans>
    <context:annotation-config/>
    <bean class="com.acme.AppConfig"/>
 </beans>

上例中<context:annotation-config/>是必要的,用于启用ConfigurationClassPostProcessor和其他注解相关的post processors来处理@Configuration注解类。

1.3 通过component scanning组件扫描的方式:

@Configuration注解本身是被@Component注解的,因此@Configuration类是可以被组件扫描到的(一般使用<context:component-scan/> xml配置),同时也可以在方法和变量上使用@Autowired/@Inject。
@Configuration类并不能仅仅使用组件扫描引导加载,需要其再使用@ComponentScan注解配置组件扫描:

 @Configuration
 @ComponentScan("com.acme.app.services")
 public class AppConfig {
     // various @Bean definitions ...
 }

查看@ComponentScan Javadoc了解更多。

2. Working with externalized values(使用外部值)

2.1 Using the Environment API(使用 Environment API)

可以使用@Autowired或@Inject注解将Environment注入到@Configuration类中以便获得外部配置值:

 @Configuration
 public class AppConfig {
     @Inject Environment env;

     @Bean
     public MyBean myBean() {
         MyBean myBean = new MyBean();
         myBean.setName(env.getProperty("bean.name"));
         return myBean;
     }
 }

通过Environment获得属性,其内部实际包含一个或多个属性资源对象,@Configuration类可以使用@PropertySources注解添加属性资源到Environment对象中。

 @Configuration
 @PropertySource("classpath:/com/acme/app.properties")
 public class AppConfig {
     @Inject Environment env;

     @Bean
     public MyBean myBean() {
         return new MyBean(env.getProperty("bean.name"));
     }
 }

查看Environment和@PropertySource Javadoc了解更多。

2.2 Using the @Value annotation(使用@Value注解)

通过@Value注解将外部值注入到@Configuration类中。

 @Configuration
 @PropertySource("classpath:/com/acme/app.properties")
 public class AppConfig {
     @Value("${bean.name}") String beanName;

     @Bean
     public MyBean myBean() {
         return new MyBean(beanName);
     }
 }

当使用PropertySourcesPlaceholderConfigurer时(xml中通过<context:property-placeholder/>启用),这种方式很有用。查看以下通过xml @ImportResource的方式创建@Configuration类,查看@Value、@Bean Javadoc 了解如何和BeanFactoryPostProcessor一起工作。

3. Composing @Configuration classes(构建@Configuration类)

3.1 With the @Import annotation(使用@Import注解)

可以用@Import注解构建@Configuration类,但不像xml中的。因为@Configuration对象本身是作为bean实例被容器控管,引用的配置可以用@Autowired或@Inject注解注入。

 @Configuration
 public class DatabaseConfig {
     @Bean
     public DataSource dataSource() {
         // instantiate, configure and return DataSource
     }
 }

 @Configuration
 @Import(DatabaseConfig.class)
 public class AppConfig {
     @Inject DatabaseConfig dataConfig;

     @Bean
     public MyBean myBean() {
         // reference the dataSource() bean method
         return new MyBean(dataConfig.dataSource());
     }
 }

那么只需在context中注册AppConfig就可以引导加载AppConfig和被引用的DatabaseConfig。
new AnnotationConfigApplicationContext(AppConfig.class);

3.2 With the @Profile annotation(使用@Profile注解)

使用@Profile注解标记@Configuration类,则只有对应的情景模式是激活状态时才会被加载处理。

 @Profile("embedded")
 @Configuration
 public class EmbeddedDatabaseConfig {
     @Bean
     public DataSource dataSource() {
         // instantiate, configure and return embedded DataSource
     }
 }

 @Profile("production")
 @Configuration
 public class ProductionDatabaseConfig {
     @Bean
     public DataSource dataSource() {
         // instantiate, configure and return production DataSource
     }
 }

查看@Profile and Environment Javadoc了解更多。

3.3 With Spring XML using the @ImportResource annotation(使用@ImportResource加载xml配置)

如上,@Configuration类可以以的方式定义在xml文件中,也可以用@ImportResource加载xml的配置,xml中定义的bena也可以用@Autowired或@Import注入。

 @Configuration
 @ImportResource("classpath:/com/acme/database-config.xml")
 public class AppConfig {
     @Inject DataSource dataSource; // from XML

     @Bean
     public MyBean myBean() {
         // inject the XML-defined dataSource bean
         return new MyBean(this.dataSource);
     }
 }

3.4 With nested @Configuration classes(使用内嵌的@Configuration类)

@Configuration类可以按照如下方式内嵌到其他配置类中:

 @Configuration
 public class AppConfig {
     @Inject DataSource dataSource;

     @Bean
     public MyBean myBean() {
         return new MyBean(dataSource);
     }

     @Configuration
     static class DatabaseConfig {
         @Bean
         DataSource dataSource() {
             return new EmbeddedDatabaseBuilder().build();
         }
     }
 }

这种方式只需注册AppConfig类到context中。因DatabaseConfig是内部类,故会自动被注册。这样就不用@Import再引入这层关系。
注意,还可以在同一个@Configuration类中使用@Profile注解声明两个不同情景模式的内部@Configuration类。

4. Configuring lazy initialization(配置懒加载)

默认@Bean方法会在容器加载的时候立即初始化,可以使用@Lazy注解类表明此类的所有@Bean方法都默认懒加载。当然,@Lazy也可以单独用在@Bean方法上。

5. Testing support for @Configuration classes(测试支持)

spring测试模块提供@ContextConfiguration注解引入TestContext框架,注解接收一个@Configuration类数组对象:

 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(classes={AppConfig.class, DatabaseConfig.class})
 public class MyTests {

     @Autowired MyBean myBean;

     @Autowired DataSource dataSource;

     @Test
     public void test() {
         // assertions against myBean ...
     }
 }

查看TestContext框架参考文档了解更多。

6. Enabling built-in Spring features using @Enable annotations(使用@Enable注解启用spring内部特性)

Spring特性包括异步方法执行、排程任务执行、注解驱动事务管理,甚至Spring MVC,都可以在@Configuration类中通过@Enable相关的注解配置启用。查看相关注解了解更多@EnableAsync, @EnableScheduling, @EnableTransactionManagement, @EnableAspectJAutoProxy, and @EnableWebMvc。

7. Constraints when authoring @Configuration classes(约束条件)

  • @Configuration类不是final的(non-final)
  • @Configuration类不能定义在方法内部(non-local)
  • @Configuration类需有一个无参默认构造函数,不能有@Autowired注解参数的构造函数。所有内部@Configuration类必须是static的。

Spring项目参数配置方案

了解了spring框架PropertySource、PropertyResolver、Environment的设计架构,如果将Environment引入到项目参数配置方案中,将带来极大的方便。仅需通过@Inject将其注入Environment实例即可获得相关配置属性。

1. 获得项目配置信息资源:

项目中常常会有多个配置文件,如jdbc.properties, site.properites, version.perperties等,每个配置文件配置的内容并不一样,一般不会出现重复的情况,故可将所有这些配置文件内容合并的一个properties对象中统一作一个属性资源。
Spring框架的PathMatchingResourcePatternResolver提供了读取资源的相关方法,而PropertiesLoaderSupport类的mergeProperties()方法将多个properties进行合并。 故利用这两个类可以获得所有配置信息,即可创建一个配置属性资源对象。

public class AppPropertiesLoader extends PropertiesLoaderSupport {
    public Properties loadProperties() throws IOException {
        return this.mergeProperties();
    }
}

public PropertiesPropertySource getAppConfigurationPropertySource() throws IOException {
    AppPropertiesLoader propertiesLoader = new AppPropertiesLoader();
    propertiesLoader.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:*.properties"));
    return new PropertiesPropertySource("properties", propertiesLoader.loadProperties());
}

2. 添加项目配置资源到Environment资源链表中:

按照Spring Javadoc,有两种方式将资源加入到Environment:

  1. 客制化一个Environment类(Web项目可以继承StandardServletEnvironment),然后覆盖customizePropertySources方法,通过方法参数变量MutablePropertySources添加资源。然后再调用context的setEnvironment方法替换默认环境资源为此新创建子类。
  2. 调用Environment类的getPropertySources()方法,获得属性资源链表,调用其添加方法添加资源。

第一种方法比较繁琐且涉及修改Spring框架类实例内部变量,不建议。故选择第二种方式。

如果使用Web相关的ApplicationContext,则会使用StandardServletEnvironment,而其已经有添加了ServletConfig、ServletContext、JNDI、环境属性和系统变量这些资源。
按照内部优先原则,项目配置资源应该作为优先考虑,应将应将项目资源作为最高优先级(addFirst)添加到资源链表中。

public void initialAppConfiguration(ConfigurableApplicationContext applicationContext) throws IOException {
    ConfigurableEnvironment environment = applicationContext.getEnvironment();
    environment.getPropertySources().addFirst(getAppConfigurationPropertySource());
}

3. 使用Environment:

以下示例从Environment中读取DB连接配置并实例化数据源:

@Inject
private Environment env;

@Bean
public DataSource dataSource() {
    BasicDataSource dataSource = new BasicDataSource();
    dataSource.setDriverClassName(env.getRequiredProperty("default.jdbc.driver"));
    dataSource.setUrl(env.getRequiredProperty("default.jdbc.url"));
    dataSource.setUsername(env.getRequiredProperty("default.jdbc.username"));
    dataSource.setPassword(env.getRequiredProperty("default.jdbc.password"));
    dataSource.setValidationQuery("select 1");
    dataSource.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);

    dataSource.setMaxActive(env.getRequiredProperty("default.pool.max.active", Integer.class));
    dataSource.setMaxIdle(env.getRequiredProperty("default.pool.max.idle", Integer.class));
    dataSource.setMinIdle(env.getRequiredProperty("default.pool.min.idle", Integer.class));
    return dataSource;
}

Spring框架PropertySource、PropertyResolver、Environment的设计架构

1. 属性资源(PropertySource)

1) public abstract class PropertySource

属性资源,主要包括以下两个方法:

public boolean containsProperty(String name);
public abstract Object getProperty(String name);

2) public abstract class EnumerablePropertySource extends PropertySource

可枚举属性资源,增加可返回属性名称数组的方法:

public abstract String[] getPropertyNames();

3) public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>>

属性资源实现类,资源类型是一个Map对象。

4) public class SystemEnvironmentPropertySource extends MapPropertySource

MapPropertySource的子类,用于获取系统环境变量。弥补了在Bash或其他shells中变量不允许包含period character(.)的不足。允许一般shell中变量是大写的情况。

例如,调用getProperty(“foo.bar”)将按照以下顺序去找变量值:
foo.bar – the original name (原始变量)
foo_bar – with underscores for periods (if any) (下划线)
FOO.BAR – original, with upper case (大写)
FOO_BAR – with underscores and upper case (下划线、大写)

调用containsProperty(String)也是按照同样的顺序,如果找到了在返回true,否则false。这种方法在通过环境变量声明激活或默认的情景模式是很有用。以下方式再Bash中是不被允许的:

spring.profiles.active=p1 java -classpath ... MyApp

但是以下语法则是被允许的也是常见的用法:

SPRING_PROFILES_ACTIVE=p1 java -classpath ... MyApp

启用debug或trace层级的log时,就会有类似’property name resolutions’这样的日志出现。
此属性资源默认包含在StandardEnvironment和其子类中。

5) 其他属性资源其对应的资源类型:

PropertiesPropertySource:java.util.Properties
ResourcePropertySource:java.util.Properties
JOptCommandLinePropertySource:joptsimple.OptionSet
ServletConfigPropertySource:javax.servlet.ServletConfig
ServletContextPropertySource:javax.servlet.ServletContext
JndiPropertySource:org.springframework.jndi.JndiLocatorDelegate

6) public class MutablePropertySources implements PropertySources

其实际上是一个属性资源链表,用于包含其他实际的属性资源,并实现了资源的顺序访问。

2. 属性解析器(PropertyResolver)

1) public interface PropertyResolver

定义了获取属性的相关方法:

String getProperty(String key);
String getProperty(String key, String defaultValue);
T getProperty(String key, Class targetType);
T getProperty(String key, Class targetType, T defaultValue);
String getRequiredProperty(String key) throws IllegalStateException;
T getRequiredProperty(String key, Class targetType) throws IllegalStateException;
String resolvePlaceholders(String text);
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;

2) public interface ConfigurablePropertyResolver extends PropertyResolver

定义了设置属性获取条件的相关方法:

ConfigurableConversionService getConversionService();
void setConversionService(ConfigurableConversionService conversionService);
void setPlaceholderPrefix(String placeholderPrefix);
void setPlaceholderSuffix(String placeholderSuffix);
void setValueSeparator(String valueSeparator);
void setRequiredProperties(String... requiredProperties);
void validateRequiredProperties() throws MissingRequiredPropertiesException;
void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);

3) public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver

属性解析器虚类,实现了大多属性设置和获取的相关方法

4) public class PropertySourcesPropertyResolver extends AbstractPropertyResolver

属性解析器实现类,AbstractEnvironment中相关属性处理方法都是适配调用此类的方法。

3. 环境信息(Environment )

1) public interface Environment extends PropertyResolver:

只定义了获取情景模式的方法,属性获取的方法已在父接口定义:

String[] getActiveProfiles();
String[] getDefaultProfiles();
//此方法在DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(xml配置)
//和ProfileCondition.matches(annotation注解)两个方法中被调用,如果bean有定义profile但不是active的则不会被初始化。
boolean acceptsProfiles(String... profiles);

该接口用于表示当前应用程序正在运行的环境信息。主要包括两个应用模型:profiles(情景模式)和properties(属性)。访问属性的相关方法则是由父接口PropertyResolver定义的。
Profile(情景模式)指一个指定名称Bean定义逻辑组,只有当其处于active激活状态时,这些Bean才会注册到容器中。Beans可以指定属于某一个Profile,不管是XML还是注解配置(详细定义语法请查看spring-beans 3.1 schema定义或者@Profile注解说明)。由Environment对象决定决定哪一个是默认的Profile,以及当前使用哪一个激活的Profile。
属性定义对大多应用都很重要,一般可能有多种定义来源,如properties文件、JVM系统参数、系统变量、JNDI、Servlet容器参数、AD-HOC属性对象、Maps等等。Environment提供一个便捷的service接口配置读取各种属性资源。

Beans可以继承EnvironmentAware接口或者添加注解@Inject注入Environment对象,以便查询当前profiles(情景模式)状态以及获取属性定义。

但是,大多情况下应用层面的bean不需要直接和Environment对象直接交互,而是配置属性placeholder ${…}}来获得属性值,比如PropertySourcesPlaceholderConfigurer类,其继承了EnvironmentAware,从Spring 3.1开始如果使用了则会自动注册加载此类。
AbstractApplicationContext继承ConfigurableEnvironment接口,所有其子类都可以通过getEnvironment()方法获得environment对象。可以通过ConfigurableEnvironment的Javadoc查看在调用application context的refresh()方法前读取多种属性资源的例子。

2) public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver

定义了设置情景模式、以及获取特定系统属性资源、合并环境信息的相关方法:

void setActiveProfiles(String... profiles);
void addActiveProfile(String profile);
void setDefaultProfiles(String... profiles);
MutablePropertySources getPropertySources();
Map<String, Object> getSystemEnvironment();
Map<String, Object> getSystemProperties();
void merge(ConfigurableEnvironment parent);

ConfigurableEnvironment是一个配置接口,大多数Environment对象都是实现于此接口。提供设置激活或默认profiles(情景模式)的功能,也可以管理各种属性资源。允许用户设置和验证必要的属性、客制化转换服务、以及更多父类接口ConfigurablePropertyResolver定义的功能。

管理属性资源
属性资源可以删除、排序或替换;还可以通过getPropertySources()方法返回的MutablePropertySources实例添加额外的属性资源。下面针对ConfigurableEnvironment的标准实现类StandardEnvironment列出几个例子,尽管对于某些特殊属性资源处理方式稍有差异,但仍适用于所有实现的情况。

Example: 添加一个具有最高查询优先级的属性资源

ConfigurableEnvironment environment = new StandardEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
Map myMap = new HashMap();
myMap.put("xyz", "myValue");
propertySources.addFirst(new MapPropertySource("MY_MAP", myMap));

Example: 删除默认的系统属性资源

MutablePropertySources propertySources = environment.getPropertySources();
propertySources.remove(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)

Example: 测试情况下替换模拟系统属性资源

MutablePropertySources propertySources = environment.getPropertySources();
MockPropertySource mockEnvVars = new MockPropertySource().withProperty("xyz", "myValue");
propertySources.replace(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, mockEnvVars);

需要注意,但ApplicationContext使用Environment时,所有属性资源应该在调用context的refresh()方法之前加载处理完成,以便确保再容器启动以及placeholder配置时候所有属性资源都是可获取的。

3) public abstract class AbstractEnvironment implements ConfigurableEnvironment

Environment实现虚类。支持默认保留的情景模式名称,可通过spring.profiles.default和spring.profiles.default属性指定激活和默认的情景模式。
子类主要的区别是默认添加的PropertySource属性资源不同。AbstractEnvironment不会添加。子类需要覆盖customizePropertySources(MutablePropertySources)方法提供属性资源,这样用户可以使用ConfigurableEnvironment.getPropertySources()客制化要求,同时也能和MutablePropertySources API并存使用。具体查看ConfigurableEnvironment的javadoc查看使用案例。
另外此类还实现了大多ConfigurableEnvironment相关方法,如获取系统属性和系统变量,设置情景模式等。

其定义了一下两个变量:

private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);//存储各种属性资源
private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);//属性资源相关方法适配类

关于customizePropertySources方法,其主要用于客制化PropertySource属性资源对象集合,以便Environment调用getProperty(String)及相关方法时获取属性。
建议子类覆盖此方法时,使用MutablePropertySources.addLast(PropertySource)添加属性资源,这样子类的子类就可以调用super.customizePropertySources()添加父类指定的资源。例如:

public class Level1Environment extends AbstractEnvironment {
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        super.customizePropertySources(propertySources); // no-op from base class
        propertySources.addLast(new PropertySourceA(...));
        propertySources.addLast(new PropertySourceB(...));
    }
}

public class Level2Environment extends Level1Environment {
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        super.customizePropertySources(propertySources); // add all from superclass
        propertySources.addLast(new PropertySourceC(...));
        propertySources.addLast(new PropertySourceD(...));
    }
}

按照这样的结构,属性资源的顺序是A,B,C,D。这就是说A有优先于D。如果Level2Environment喜欢C,D的优先级比A,B高,可以在方法最后调用super.customizePropertySources,如:

public class Level2Environment extends Level1Environment {
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new PropertySourceC(...));
        propertySources.addLast(new PropertySourceD(...));
        super.customizePropertySources(propertySources); // add all from superclass
    }
}

这样顺序就变为了C,D,A,B.
还需说明,子类可以调用MutablePropertySources提供的add、remove、replace方法来调整组织资源顺序。AbstractEnvironment中未注册任何资源。
需要注意,任何ConfigurableEnvironment对象的调用者还可以通过getPropertySources()方法来调整属性资源,一般是在ApplicationContextInitializer对象中。例如:

ConfigurableEnvironment env = new StandardEnvironment();
env.getPropertySources().addLast(new PropertySourceX(...));

关于访问实例变量的警告:
定义在子类中的实例变量可能会有其初始化值,不要在customizePropertySources方法中访问这些变量。因为Java对象创建生命周期的限制,任何变量不要在AbstractEnvironment()的构造方法调用此callback方法customizePropertySources的时候设置值初始化值,不然会造成空指针异常或其他错误。如果你的确需要获取实例变量的默认值,则不用覆盖此方法,在资料的构造方法中直接设置属性资源和访问实例变量。注意给实例变量赋值是没有问题的,而是应尽量避免在此方法中访问实例变量默认值。

4) public class StandardEnvironment extends AbstractEnvironment

该类Environment的实现类,用于“标准”(非web)应用。除了ConfigurableEnvironment的常用功能,如获取属性、情景模式相关操作,该实现类配置了两个默认属性资源,他们的查找顺序如下:
system properties (系统属性)
system environment variables(系统环境变量)

例如,如果“xyz”在JVM系统属性和当前进程的环境变量都存在,environment.getProperty(“xyz”)则会返回jvm系统属性的值。这是默认被采用的顺序,因为系统属性是针对没有jvm的,而系统变量则可能多个jvm都是一样的值。系统属性优先允许再JVM设置属性以覆盖系统变量。

默认属性资源可以被删除、重新排序、或替换。额外的属性资源可以通过getPropertySources()方法返回的MutablePropertySources实例方法添加。

以下实现方法默认添加了系统属性和环境变量两种属性资源:

protected void customizePropertySources(MutablePropertySources propertySources) {
    propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
    propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

5) public interface ConfigurableWebEnvironment extends ConfigurableEnvironment

定义了Web应用特有的接收属性资源ServletContext和ServletConfig对象并进行初始化的方法:

void initPropertySources(ServletContext servletContext, ServletConfig servletConfig);

6) public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment

针对servlet web应用的Environment实现。所有web相关(servlet)的ApplicationContext都会初始化这样一个实例。
其包含ServletConfig, ServletContext, 和 JNDI-based的属性资源实例。更多查看customizePropertySources方法描述。

protected void customizePropertySources(MutablePropertySources propertySources) {
    propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME)); //添加ServletConfig属性资源
    propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)); //添加ServletContext属性资源
    if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
        propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME)); //添加JNDI属性资源
    }
    super.customizePropertySources(propertySources); //再调用父类方法添加系统属性和环境变量
}

    @Override
    public void initPropertySources(ServletContext servletContext, ServletConfig servletConfig) {
        //初始化替换customizePropertySources方法创建的Stub类型的ServletConfig、ServletContext属性资源
        WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
    }

7) public interface EnvironmentCapable:

该接口定义了方法getEnvironment(),表明一个类包含了一个Environment引用。

所有的spring application contexts都实现了EnvironmentCapable接口,该接口主要用于框架的instanceof判断,框架方法判断接收的BeanFactory实例(可能是也可能不是ApplicationContext实例)是否EnvironmentCapable接口的实现类,如果是且存在environment,则可以和environment对象交互。
因ApplicationContext继承于EnvironmentCapable,故拥有getEnvironment()方法。ConfigurableApplicationContext重新定义了getEnvironment()指定方法返回一个ConfigurableEnvironment。这样的效果是使ConfigurableApplicationContext开始访问Environment对象前其状态只是只读的,ConfigurableApplicationContext中则可以再修改。

8) public interface EnvironmentAware extends Aware

此接口主要用于注入environment对象。

void setEnvironment(Environment environment);

Web应用异常日志记录优化方案

Java Web应用常用的日志管理框架如log4j,logback等,一般生产环境不会设置为DEBUG模式,这样会导致日志量大增,影响系统性能,也影响正常日志的分析。正常设置为INFO级别,警告(WARN)或异常(ERROR)的日志也会输出,发生异常时,需要查看发生异常前后上下文的相关日志进行分析,比如分析一个HTTP请求异常,需要分析请求参数、发生异常前后的逻辑处理日志,但日志中记录了所有请求的日志信息,如果同一时间有多个请求,只能根据请求线程ID在日志文件中上下查找,很是麻烦!

另外,有时仅仅看INFO、WARN、ERROR的日志还不能分析问题,还需要一些DEBUG级别的日志。此时只能停止应用,修改LOG日志级别为DEBUG,再启动应用,重新模拟异常发生的情况。但这可能会遇到以下几个问题:

  1. 模拟发生异常的状况非常麻烦,费时费力!
  2. 异常发生时的状况无法知晓,难以模拟!
  3. 测试环境不会发生,但生产环境有不能模拟测试!

为了解决以上问题,需要一个日志记录方案满足这样的功能:发生异常时将同一个线程内包括DEBUG级别的日志全部输出到单个请求异常日志文件中。

设计方案描述:
1. 定义一个TraceLoggingEventProcessor类,此类中接收ILoggingEvent实例,但并不立即写到日志文件中,而是将event记录到一个list变量中;当遇到ERROR层级的日志时,才将list中的event都写到一个单独的日志文件中,而后续的日志也直接写到文件; 此线程从头到尾没有ERROR层级日志,则将event list清空即可。
2. 新增日志Appender类TraceAppender,在Root logger设置为DEBUG级别并添加TraceAppender。Root logger中同时包含正常日志Appender,但在正常日志Appender的配置中将其设置为INFO级别。
3. 覆盖TraceAppender的append方法,其从一个范型类型为TraceLoggingEventProcessor的ThreadLocal类变量中取出实例,将日志event交给此TraceLoggingEventProcessor进行记录。

Jboss应用管理命令

1. 获得命令帮助
jboss-cli.bat help
jboss-cli.bat –command=”deploy –help”
jboss-cli.bat –command=”undeploy –help”

2. 完整命令格式
jboss-cli.bat -c –controller=[JBOSS_SERVER]:[SERVER_PORT] –user=[DEPLOY_USER] –password=[DEPLOY_PASSWORD] –command=”[COMMAND]”

说明:

  • [JBOSS_SERVER]:jboss server的ip或机器名
  • [SERVER_PORT]:Jboss native管理端口,默认9999
  • [DEPLOY_USER]:管理帐号
  • [DEPLOY_PASSWORD]:管理帐号密码
  • [COMMAND]:具体执行指令(用“指令”表述来区别于“命令”)

3. 部署war并制定context-path名称的指令:
deploy f:\maven_repository\com\example\myapp\1.0.0\myapp-1.0.0.war –name=myapp.war

如果只是部署但不启动,可以加’–disabled’参数指定disabled状态:
deploy f:\maven_repository\com\example\myapp\1.0.0\myapp-1.0.0.war –name=myapp.war –disabled

注意:

  • 如果没有提供’–name’参数声明部署应用名称(默认也会被用于context path), 则使用war文件的名称.
  • 部署应用名称’–name’参数需以”.war”结尾

4. 卸载应用的指令:
undeploy myapp.war

5. 停止(Stop/Disable)应用的指令:
undeploy myapp.war –keep-content

6. 启动(Start/Enable)应用的指令:
deploy –name=myapp.war

日志归档bash脚本

#!/bin/sh

# gelnyang@163.com , 2014/12/03
#
#=================================================================
# myapp系统会产生多个log文件备份,但并无日志归档的功能,超过指定个数的log文件会被刪除.
# 但系统要求日志需长期保存,故写此脚步循环检查log文件目录,将备份目录下格式为*.log.?的日志拷贝到备份目录并压缩归档.
#
# configure system schedule
#   1) copy myapp_logbackup.sh to /etc/init.d/
#   2) chmod +x /etc/init.d/myapp_logbackup.sh
#   3) vi /etc/crontab:
#        ------------------
#        */10 * * * * root /etc/init.d/myapp_logbackup.sh >> /tmp/myapp_log_backup.log
#=================================================================

# configuration format:
# [LOG_DIR]:[BACKUP_DIR],[LOG_DIR]:[BACKUP_DIR]
BACKUP_DIR_CONFIG=/opt/myapp/logs1:/opt/logs_backup/log1,/opt/myapp/logs2:/opt/logs_backup/log2

function backup_log_files(){   
    echo ---------------------------------------
    echo $(date +%Y/%m/%d' '%H:%M:%S)
   
    today=$(date +%Y%m%d)
    timenow=$(date +%Y%m%d%H%M%S)
    log_dir=$1
    backup_dir=$2  
    today_backup_dir=$backup_dir/$today
   
    mkdir -p $today_backup_dir
   
    echo check directory:$log_dir
    for file_path in ` ls $log_dir/*.log.? `
    do
        file_name=${file_path##/*/}
        target_file_path=$today_backup_dir/${file_name/.log./.}.$timenow.log
        echo mv $file_path $target_file_path
        mv $file_path $target_file_path
    done
   
    echo try to zip log files under directory:$today_backup_dir
    cd $today_backup_dir
    for file_path in ` ls *.log `
    do
        file_name=${file_path##/*/}
        echo zip $file_path.zip $file_name
        zip $file_path.zip $file_name
    done
   
    echo rm -rf $today_backup_dir/*.log
    rm -rf $today_backup_dir/*.log
}

config_arr=(${BACKUP_DIR_CONFIG//,/ })  
for config_item in ${config_arr[@]}  
do  
    item_arr=(${config_item//:/ })
    backup_log_files ${item_arr[0]} ${item_arr[1]}
done

基于注解的权限验证方案

基本实体设计:用户(user)、角色(Role)、功能(Function)。 一个用户可以有多个角色,一个角色可以有多个功能。

功能的可以通过url地址来进行定义,一个要访问的url定义一个功能(function)。但实际项目中一个页面功能可能对应多个url,比如ajax加载、验证、提交等功能的url,那么就需要配置多个Function。而一般情况要给角色分配的是一个页面所有的访问功能,而不是某一个url的访问功能。 故用统一的功能代码来表示页面权限更为恰当。只要将这个页面相关的url的权限都配置为这个功能代码即可。系统管理要做的就是维护角色和功能代码之间的关系。

另外,考虑到多个功能代码对应功能都涉及到对某一个url的访问,故一个url需要能够配置多个功能代码。

那么是否要在db配置url和功能代码的关系呢?在开发代码的时候,这层关系已经决定,配置也是定死的,配置在db中没有意义。直接在代码中写死,需要做的是定义好功能代码的格式和标准,或者在一开始项目架构的时候定义好,让攻城师遵守按照规定代码配置即可。而注解是一个绝好的配置方案。


以下以Spring MVC为例进行说明,首先设计登陆验证:
1. 定义@RequireLogin注解:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireLogin {
}

2. 对需要登录访问的Controller类或者方法添加此注解;

@Controller
@RequireLogin
@RequestMapping(value = "/role")
public class RoleController {
}

3. 增加Spring HandlerInterceptor拦截器,对要访问的Controller类或方法判断其有无添加@RequireLogin注解,如果有则进行登陆判断;

设计权限验证:
1. 定义@RequirePermission注解,注解包含一个功能代码数组变量,只要用户角色拥有其中任何一个功能代码权限即拥有权限访问;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {

    /**
     * Provide permission codes
     */

    String[] value();

}

2. 对所有Controller中所有@RequestMapping方法添加此注解:

@RequirePermission(value = "ROLE_MANAGMENT")
@RequestMapping(value = "/viewRoleList", method = RequestMethod.GET)
public String viewRoleList(){
}

3. 增加Spring HandlerInterceptor拦截器,获得访问的Controller方法的@RequirePermission注解,获得所需权限功能代码列表,再检查当前登录用户角色对应的功能代码中是否包含其中一个要求的权限功能代码,如果包含则权限验证通过;


注:如果一个页面功能要访问的url都在同一个Controller中,则可考虑将@RequirePermission的定义范围扩展到类层,在Controller类层定义此注解,声明共通的功能代码,而方法层则声明共通之外特殊的功能代码,这样还可以减少配置。

創用 CC 授權條款
Geln's BlogGeln Yang製作,以創用CC 姓名標示-相同方式分享 3.0 Unported 授權條款釋出。
此作品衍生自sisopipo.com