一:建立一个Maven结构的Web工程
这里主要介绍下如何使用MyEclipse创建一个Maven结构的Web项目
1:下载并安装好自己的maven,并在环境变量中配置对应MAVEN_HOME、PATH路径
检测是否安装完毕,可以在cmd中输入命令检测:mvn --version
2:在MyEclipse中关联并使用Maven(这里可以使用MyEclipse自带的Maven4MyEclipse,也可以自己下载一个MyEclipse对应的Maven插件来关联我们的Maven3.1.1)
设置下自己的仓库和配置文件路径:
3:新建工程如下:
至此生成一个web结构的maven项目(还缺少部分maven目录结构,下面补齐)
4:补充maven目录结构:
5:结构弄完了,自己往pom.xml里面灌点jar配置就好了,我的如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.wjlmgqs</groupId> <artifactId>mtag</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>mtag Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.4.3</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <!--springframework 3.2.0.RELEASE --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>3.2.12.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>3.2.12.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>3.2.12.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.2.12.RELEASE</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId> commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>3.2.12.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>3.2.12.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>3.2.12.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>3.2.12.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>3.2.12.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>3.2.12.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>3.2.12.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>3.2.12.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>3.2.12.RELEASE</version> </dependency> <!--aspectj --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.5</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.5</version> </dependency> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version> </dependency> <!--mybatis 3.3.1 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.1.1</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.19</version> </dependency> <!--log :slf4j+classes+core+logback.xml --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>org.logback-extensions</groupId> <artifactId>logback-ext-spring</artifactId> <version>0.1.1</version> </dependency> <!--commons utils --> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.2</version> </dependency> <dependency> <groupId>commons-chain</groupId> <artifactId>commons-chain</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.6</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> <dependency> <groupId>commons-configuration</groupId> <artifactId>commons-configuration</artifactId> <version>1.7</version> </dependency> <dependency> <groupId>commons-digester</groupId> <artifactId>commons-digester</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.1</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>commons-net</groupId> <artifactId>commons-net</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>commons-pool</groupId> <artifactId>commons-pool</artifactId> <version>1.6</version> </dependency> <dependency> <groupId>commons-validator</groupId> <artifactId>commons-validator</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> <version>1.3</version> </dependency> <!--测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> <!--j2ee web spec --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> <!--json --> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>1.9.13</version> </dependency> </dependencies> <build> <finalName>mtag</finalName> </build> </project>
二:Spring、SpringMVC重复加载bean对象问题。
在新建完Web项目并通过Pom.xml引入需要的jar后,我就开始配置Spring、SpringMVC的相关配置文件(在配置文件中启用注解扫描Bean组件的方式),这中间遇到一个问题,在web.xml中配置的:Spring对应的ContextLoaderListener加载beans.xml并扫描注册了一次@Component对象,而SpringMVC的DispatcherServlet加载spring-mvc.xml又扫描注册了一次注解的Bean对象,造成一个对象被实例化两次。
针对摘要中的问题,我们可以这样理解并分配:
将与SpringMVC关联的Controller层注解对象都归属于spring-mvc.xml对应的上下文管理,而剩下来的组件都交由Spring的beans.xml对应上下文管理。2个配置文件的内容分别如下:
spring-mvc.xml配置内容如下:只扫描注解类型为@Controller的组件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <!-- 使用 annotation 自动注册Controller bean,并检查@Controller注解已被注入 --> <context:annotation-config /> <context:component-scan base-package="org.wjlmgqs.mtag" use-default-filters="false" > <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 完成请求和注解POJO的映射 --> <mvc:annotation-driven /> <!-- 静态资源处理 --> <mvc:resources mapping="/resource/**" location="/resource/"/> <mvc:default-servlet-handler /> <!-- 视图对应的文件映射 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> <aop:aspectj-autoproxy proxy-target-class="true" /> </beans>
beans.xml配置内容如下:只扫描排除@Controller后组件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 注解扫描的目录:使用 annotation 排除@Controller注解 --> <context:annotation-config /> <context:component-scan base-package="org.wjlmgqs.mtag" > <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- mybatis相关配置 --> <import resource="beans-mybatis.xml"/> <!-- 配置项类型配置 --> <import resource="beans-config-type.xml"/> <!-- 事务配置 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- @Transactional --> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/> </beans>
三:实现自个的数据缓存机制
自个目前只实现了2种数据的缓存:
一:数据字典加载到缓存中 ;
二:配置项类型数据加载到缓存中
- 2种缓存数据简介及其数据结构分析
这里先简单介绍下2种数据类型:
1:数据字典,表结构如下:
数据例如:
加载到我们缓存中的数据结构大致如下:Map<ID,Map<DICT_KEY,Map<String,String>>>
2:配置项类型,打个比方,我们站点有如下几种需要:
支付方式包括:支付宝、微信支付、银联在线支付等 (PayType);
物流配送方式:顺丰、中通、EMS等(DeliveryType);
社会化登录方式:QQ、Baidu、Sina等(SocialType);
而且上述几种类型和方式都是可插拔、可扩展的,这个时候我们可以尝试定义一个接口:IConfigPart,定义这些配置项的功能属性,例如ID、Name等
/** * IConfigPart.java<br> * <b>功能:配置项类型接口:所有实现该接口的类型都将注册入缓存CacheClient中</b><br> * @author wjl Email:[email protected] * Time:2015-7-24 上午11:11:56 */ public interface IConfigPart { /** * <b>简要说明:配置项类型ID</b><br> * Create on 2015-7-23 下午2:27:31 <br> */ public String getId(); /** * <b>简要说明:实现类Code</b><br> * Create on 2015-7-23 下午2:27:31 <br> */ public String getCode(); /** * <b>简要说明:实现类名称</b><br> * Create on 2015-7-23 下午2:27:31 <br> */ public String getName(); /** * <b>简要说明:顺序号</b><br> * Create on 2015-7-23 下午2:33:54 <br> */ public int getSeq(); /** * <b>简要说明:是否启用</b><br> * Create on 2015-7-23 下午2:33:54 <br> */ public boolean isMrb(); }
然后,我们又为了能够区分这些类型中哪些是支付类型、哪些是配送类型、哪些是社会化登录类型,就需要给各个类型定义一个自己的接口,并继承接口:IConfigPart,例如社会化类型:ISocailLoginConfigPart
/** * ISocailLoginConfigPart.java<br> * <b>功能:社会化登录-配置类型默认实现接口</b><br> * @author wjl Email:[email protected] * Time:2015-7-27 下午12:10:19 */ public interface ISocailLoginConfigPart extends IConfigPart{ }
再然后我们就可以定义我们具体的登录类型有哪些了:
例如:QQConfig.java
public class QQConfig implements ISocailLoginConfigPart { public static String ID = "QQConfig"; @Override public String getId() { return ID; } @Override public String getCode() { return "QQ"; } @Override public String getName() { return "腾讯QQ"; } @Override public boolean isMrb() { return true; } @Override public int getSeq() { return 1; } }
定义上面这样的结构以后,我们就可以想象这样一个数据结构:
Map<ISocailLoginConfigPart,Map<QQConfig,IConfigPart>>
//ISocailLoginConfigPart:是哪种类型,如社会化登录类型的ID
//QQConfig:是社会化登录类型下的具体哪种类型方式:QQ登录方式的ID
//IConfigPart:代表QQ登录类型这个对象,包含ID、Name、Seq等数据的具体对象
额,定义了上面2种缓存数据结构以后,分别给这2个数据结构定义一个常量ID,并扔进一个叫做CacheClient对象的Map里面,实现大致如下:
/** * CacheClient.java<br> * <b>功能:缓存数据操作集</b><br> * @author wjl Email:[email protected] * Time:2015-7-23 下午4:19:11 */ @Component public class CacheClient implements ApplicationContextAware{ private static String threadClassName = Thread.currentThread().getStackTrace()[1].getClassName(); protected static Logger log = LoggerFactory.getLogger(threadClassName); /** * <b>描述:缓存服务</b> */ private static ICacheService cacheService = null; /** * <b>描述:配置项类型</b> */ private static ConfigType configType = null; /** * <b>简要说明:构造参数私有化</b><br> * Create on 2015-7-23 下午3:30:13 <br> * @author 翁加林 */ private CacheClient(){ log.debug("...CacheClient create cache client "); } /** * <b>描述:数据缓存结构对象</b> */ private static Map<String,Map<String,Object>> cacheMap = new HashMap<String,Map<String,Object>>(); /** * <b>简要说明:初始化缓存数据结构</b><br> * Create on 2015-7-23 下午3:50:10 <br> * @author 翁加林 */ public static void initCache(){ Map<String, Object> loadDictCache = loadDictCache();//字典数据 Map<String, Object> loadConfigTypeCache = loadConfigTypeCache();//配置项 cacheMap.put(PubConstants.DICT_CLIENT_CACHE, loadDictCache); cacheMap.put(PubConstants.CONFIG_TYPE_CACHE, loadConfigTypeCache); log.debug("...CacheClient initCache data : "+cacheMap); } ..... }
额,这蛋扯得有点长了,下回分解。
- 2中缓存数据加载实现流程介绍
定义好这2种缓存的数据结构以后,我们得有个流程和方式把这些数据加载进来吧,这里就先介绍下加载的大致流程。
第一步:注册监听,在容器启动时就开始加载我们的数据
在web.xml中注册:
<listener> <listener-class>org.wjlmgqs.mtag.core.listener.InitListenter</listener-class> </listener>
对应的java代码如下:
/** * InitListenter.java<br> * <b>功能:服务启动时加载系统需要的Cache内容: * java类型:ConfigPart 配置项 * 数据库:数据字典 * </b><br> * @author 翁加林 Email:[email protected] * Time:2015-7-23 下午2:32:49 */ public class InitListenter implements ServletContextListener{ private static String threadClassName = Thread.currentThread().getStackTrace()[1].getClassName(); protected static Logger log = LoggerFactory.getLogger(threadClassName); @Override public void contextDestroyed(ServletContextEvent arg0) { } @Override public void contextInitialized(ServletContextEvent sce) { log.debug("...InitListenter contextInitialized start ..."); CacheClient.initCache();//初始化缓存操作 log.debug("...InitListenter contextInitialized end ..."); } }
可以看出,我们在容器启动时,调用
CacheClient.initCache();//初始化缓存操作
完成了缓存数据的加载。
第二步:在initCache中分别加载2种缓存数据
/** * <b>简要说明:初始化缓存数据结构</b><br> * Create on 2015-7-23 下午3:50:10 <br> * @author 翁加林 */ public static void initCache(){ Map<String, Object> loadDictCache = loadDictCache();//字典数据 Map<String, Object> loadConfigTypeCache = loadConfigTypeCache();//配置项 cacheMap.put(PubConstants.DICT_CLIENT_CACHE, loadDictCache); cacheMap.put(PubConstants.CONFIG_TYPE_CACHE, loadConfigTypeCache); log.debug("...CacheClient initCache data : "+cacheMap); }
这里分别加载了2种数据,我们分开来讲。
第三步:加载字典数据:loadDictCache()
/** * <b>简要说明:从数据库加载枚举数据到缓存结构中</b><br> * Create on 2015-7-23 下午3:01:47 <br> * @author 翁加林 */ private static Map<String, Object> loadDictCache(){ log.debug("...CacheClient loadDictCache start...."); //从数据库中获取所有字典类型 List<String> dictTypes = cacheService.getDictTypes(); log.debug("...CacheClient loadDictCache 共加载"+dictTypes.size()+"种类型枚举数据"); Map<String, Object> dictCaches = getDictCaches(); //获取所有类型对应的字典数据,并保存至缓存结构 for(String dictType : dictTypes){ Map<String,Object> dict = cacheService.getDictByType(dictType); dictCaches.put(dictType, dict); } log.debug("...CacheClient loadDictCache end...."); return dictCaches; }
从代码中可以看出,这里我们调用cacheService来进行数据查询,在其底层就是通过dao层调用mybatis方式查询数据库并获取数据。
不过这里有个问题:我们的cacheService对象咋来滴?
答:我们搭建的环境中所有service都是交由spring上下文管理的,所以从里面抓就行了。
问:咋抓?
答: 可以通过注解方式实现:
/** * <b>描述:缓存服务</b> */ @Autowired private static ICacheService cacheService = null;
不过洒家不是这么干的,吾让CacheClient实现了ApplicationContextAware接口,通过:
/** * 重载函数:注入上下文,从中获取ICacheService操作缓存数据及配置项对象 * (non-Javadoc) * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) */ @Override public void setApplicationContext(ApplicationContext context) throws BeansException { cacheService = (ICacheService) context.getBean("cacheService"); log.debug("...CacheClient setApplicationContext and get cacheService : "+cacheService); configType = (ConfigType) context.getBean("configType"); log.debug("...CacheClient setApplicationContext and get ConfigType : "+configType); }
获得cacheService实例,所以在loadDictCache()中才能正常调用
当然,实现这个接口并要让Spring上下文把cacheService对象注入到CacheClient,CacheClient也必须添加注解才行啊。
@Component public class CacheClient implements ApplicationContextAware{ ... }
dao层查数不是我本文关注的重点,无视......
第四步:加载配置项数据:loadConfigTypeCache
这货有些麻烦,简单来说就是这样一个流程:
1:通过xml定义一个我们配置项内容的结构
<mtag:configType id="configType"> <mtag:configItem classKey="org.wjlmgqs.mtag.sso.config.ISocailLoginConfig" > <mtag:configPart classKey="org.wjlmgqs.mtag.sso.config.BaiduConfig"></mtag:configPart> <mtag:configPart classKey="org.wjlmgqs.mtag.sso.config.QQConfig"></mtag:configPart> </mtag:configItem> ...... </mtag:configType>
2:通过java代码读取并解析xml,并转换为我们之前分析的Map数据结构(实际上这里我是通过Spring自定义标签的方式实现的,我们后面详解)
第五步:拿到2个Map数据结构后,就像第二步代码中看到的一样,根据常量ID扔进CacheClient的cacheMap中就ok啦
三:通过Spring自定义标签形式实现配置项类型数据的缓存数据结构解析及加载
通过Spring自定义标签形式实现配置项类型数据的缓存数据结构解析及加载:在上一篇幅中,我们了解到可以通过xml形式定义我们的配置项类型数据及其结构,然后由Spring来解析这些标签并转换为我们需要的数据结构。那么让Spring来识别我们自定义标签我们需要干些啥呢?
我们先要了解下,自定义Spring标签都需要些啥东西:
1:写自己的xml文件和里面自定义的标签呗
2:你要知道每一个规范的xml文件都有对应的一个xsd文件来规范xml中出现的每一个标签的格式:这个<mtag:configType>标签最多能出现几次,它有哪些字节点,它有啥属性等
3:你定义的xml文件谁来解析啊?你得指定个和Spring打交道的类来帮你解析吧?那就来个实现了NamespaceHandlerSupport接口的类:ConfigTypeHandler
4:这实现了NamespaceHandlerSupport接口后,并重写了默认的init方法后,发现handler需要调用一些继承了AbstractSimpleBeanDefinitionParser的parser来解析你自己写的每一个标签:<mtag:configType>、<mtag:configItem>、<mtag:configPart>,然后在handler中注册一下:
@Override public void init() { log.debug("...ConfigTypeHandler start..."); // TODO Auto-generated method stub registerBeanDefinitionParser("configType",new ConfigTypeParser()); registerBeanDefinitionParser("configItem",new ConfigItemParser()); registerBeanDefinitionParser("configPart",new ConfigPartParser()); log.debug("...ConfigTypeHandler end..."); }
5:大家也都知道Spring在xml中检测到bean标签后会根据指定的class来实现化一个javabean对象,其实也是通过Parser来帮我们转换成对应的javabean对象的,那么我们自定义标签也一样,需要给每个标签都定义一个javabean对象,它在解析标签后就直接给我们返回我们需要的javabean对象了。
这些javabean对象子元素和属性都要和配置标签对应属性保持一致(感觉好麻烦的说),注意我们是三级嵌套的标签哈~
6:我们定义的标签都是mtag开头的,那么怎么把这类<mtag>开头的标签和我们的handler映射起来呢?
答:在META-INF下建立spring.handlers,并配置:
http\://www.wjlmgqs.org/schema/mtag=org.wjlmgqs.mtag.core.config.ConfigTypeHandler
7:看上面的图,我们知道我们很装逼的指定了我们xsd文件的位置在:http://www.wjlmgqs.org/schema/mtag/config-type.xsd,其实大爷现在连个域名和云服务器都买不起,又咋给你放着破配置文件哈,所以我们要很装逼的映射下这个路径到我们本地路径(项目路径)下面
在META-INF下建立spring.schemas,并配置:
http\://www.wjlmgqs.org/schema/mtag/config-type.xsd=org/wjlmgqs/mtag/core/config/config-type.xsd
综上,就是我们自定Spring标签需要的东西,在搞这些东西的时候还碰到一些问题,如下:
一:在xml中写出标签后,第一行总有个红叉,提示:
Referenced file contains errors (http://www.wjlmgqs.org/schema/mtag/config-type.xsd). For more information, right click on the message in the Problems View and
select "Show Details..."
然后我还真看了:
说是xsd定义有问题,我反复检查,反复检查,没看出个球。
同样xsd文件也一直报个错:
同样检查出个球。
二:默认META-INF目录如下图:
不在classpath路径下面,这样运行工程后会出现如下异常:org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Failed to import bean definitions from relative location [beans-config-type.xml]
于是乎,我看了看spring是怎么搞的:
很直接,是classpath下面的,于是我把META-INF文件都放到了src/main/resources下面:
擦,不报错了。
注:代码太多,贴上很乱,后面我整理工程上传。
这里列下标签对应的文件在我工程里的位置:
四:对method做统一的起始&结束的日志处理(切面方式)
正常情况下:我们通过url访问某个ui请求对应method,里面都有很多service以及适当的日志输出,当日志内容较多的时候,我们可能就分不清楚哪些日志是哪个方法里执行出来的日志,或者说是为了更好、更快的定位到我们需要的日志。所以,这里我们简单的通过AOP方式为method包裹一层日志处理。
先简单描述下,我们要实现的方式:我们自定义一个注解(LogSign),在我们需要日志处理的method方法体上添加该注解,当我们的请求方法遇到LogSign注解时,会自动调用我们自定义的一个切面类(LogSignService),并执行里面的@Around标注的log()方法,在这个方法中输出起始日志,执行method方法,输出结束日志等。
具体步奏:
1:自定义注解LogSign
/** * LogSign.java<br> * <b>功能:定义日志注解,标有该注解的service进行日志切面拦截</b><br> * @author 翁加林 Email:[email protected] * Time:2015-7-22 下午4:28:13 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface LogSign { String beforeMsg() default ""; String endMsg() default ""; }
2:自定义切面LogSignService
/** * LogSignService.java<br> * <b>功能:定义日志服务切面,被拦截的方法前后插入日志信息(execution中限定:只platform路径下有效) * 在beanx.xml中配置了代理实现方式为代理类,所以所在类必须有默认构造函数 * </b><br> * @author 翁加林 Email:[email protected] * Time:2015-7-22 下午4:25:41 */ @Aspect @Component public class LogSignService extends BaseService implements ApplicationContextAware{ private static Logger log = LoggerFactory.getLogger("org.wjlmgqs.mtag.platform"); //环绕通知方法 @Around("execution(* org.wjlmgqs.mtag.platform..*.*(..))") public Object log(ProceedingJoinPoint point) throws Throwable{ //获取被拦截的对象及其执行方法 Object target = point.getTarget(); // 拦截的实体类 String className = target.getClass().getName(); String methodName = point.getSignature().getName(); // 拦截的方法名称 Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes(); // 拦截的放参数类型 Method method = target.getClass().getMethod(methodName,parameterTypes); Object object = null; if(method != null && method.isAnnotationPresent(LogSign.class)) { LogSign annotation = method.getAnnotation(LogSign.class); String beforeMsg = "--->>>执行"+className+"类的"+methodName+"方法--开始"; String endMsg = "---->>>执行"+className+"类的"+methodName+"方法--结束"; if(!StringUtils.isEmpty(annotation.beforeMsg())){ beforeMsg = annotation.beforeMsg(); } if(!StringUtils.isEmpty(annotation.endMsg())){ endMsg = annotation.endMsg(); } log.debug(beforeMsg); object = point.proceed();// 执行该方法 log.debug(endMsg); } else { // 没有包含该注解则不进行其他处理 object = point.proceed();// 执行该方法 } return object; } @Override public void setApplicationContext(ApplicationContext arg0) throws BeansException { // TODO Auto-generated method stub } }
3:建一个测试类Test.java
@Controller @RequestMapping(value="/test/json") public class Test extends BasePlatformUI{ private static Logger log = LoggerFactory.getLogger(Test.class); /** * <b>简要说明:测试输出json格式数据</b><br> * Create on 2015-7-30 下午5:06:04 <br> * @author 翁加林 */ @RequestMapping(value="/write") @ResponseBody @LogSign public String write(HttpServletRequest request,HttpSession session) throws Exception{ ModelAndView mav = new ModelAndView(); ModelMap modelMap = new ModelMap(); modelMap.put("mapKey", "mapValue"); modelMap.addAttribute("attributeKey", "attributeValue"); mav.addObject("model", modelMap); mav.addObject("modelMap", modelMap); String writeValueAsString = super.writeJson(mav); log.debug("...测试数据:"+writeValueAsString); return writeValueAsString; } }
这里将map对象转换为json字符串是通过jackjson实现的,代码如下:
/** * <b>简要说明:将对象转换为Json字符串</b><br> * Create on 2015-7-31 上午10:45:22 <br> * @author 翁加林 */ public String writeJson(Object obj){ ObjectMapper mapper = new ObjectMapper(); String msg = null; try { msg = mapper.writeValueAsString(obj); }catch (Exception e) { log.error("...BasePlatformUI writeJson parse error : "+e.getMessage()); } return msg ; }
4:效果
五:LogBack日志文件配置&中文乱码&MyBatis日志不输出SQL。
我的代码按照自己的习惯进行了模块划分,所以也希望输出的日志能各回各家各找各妈(按照目录分别输出)
我目前几个核心的模块划分为:core、platform、sso,所以,我希望这几个目录的代码日志能够分别输出到core.log、mybatis.log、platform.log;
另外,我希望mybatis启动、读取文件等操作日志输出到mybatis.log,查询数据库的sql语句输出到sql.log、所有的错误消息统一输出到error.log(指定error级别)。
实现上述的功能,只需要2种配置方式就成:
第一:错误消息统一输出到error.log,使用root配置
<!-- 走默认方式输出ERROR到单独文件 --> <root level="DEBUG"> <appender-ref ref="error" /> </root>
第二种:按照目录输出到对应的日志文件,类似下面贴出代码,只要修改各自模块路径就成
<!-- 按照包路径输出DEBUG级日志 --> <logger name="org.wjlmgqs.mtag.platform" level="DEBUG"> <appender-ref ref="platform" /> </logger>
不过这里需要提出和注意的2个地方:
1:mybatis等操作日志的包路径指定为:org.mybatis,就成。
2:mybatis执行sql语句默认输出是按照mapper文件(xml)中的namespace路径,所以我这里将namespace路径指定为mapper文件的包路径
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.wjlmgqs.mtag.mapper.Dict"> <select id="getDictTypes" resultType="string" > SELECT DISTINCT ID FROM MT_DATA_DICT </select> <select id="getDictByType" parameterType="string" resultType="Dict" > SELECT ID,DICT_KEY AS dictKey,DICT_VALUE AS dictValue,SUBJ FROM MT_DATA_DICT WHERE ID = #{value} <!-- ORDER BY SEQ --> </select> </mapper>
这样子,几种输出类型我们都定好了,我们剩下来需要做的就是为每个logger和root中的ref指定appender对象,基本配置如下:
<appender name="core" class="ch.qos.logback.core.rolling.RollingFileAppender"> <Encoding>UTF-8</Encoding> <File>${LOG_HOME}/core.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> <FileNamePattern>${LOG_HOME}/core%i.log</FileNamePattern> <MinIndex>1</MinIndex> <MaxIndex>5</MaxIndex> </rollingPolicy> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <MaxFileSize>5MB</MaxFileSize> </triggeringPolicy> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> </encoder> </appender>
另外,因为error只输出error级别日志,所以需要在它的<appender>下添加一个过滤器:
<filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter>
酱紫:我们总体配置就完了,这里面已经处理了sql日志输出的问题,但是还隐藏了另一个问题:部分jar中会输出一些中文乱码(GBK编码,我们总体环境都是UTF-8),虽然我们的appender中指定了编码格式、tomcat也指定了(server.xml):
<Connector port="80" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />
但还是乱码,经过搜索尝试,发现需要设置我们tomcat容器启动编码(catalina.bat):
if not exist "%CATALINA_BASE%\conf\logging.properties" goto noJuli set JAVA_OPTS=%JAVA_OPTS% -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.util.logging.config.file="%CATALINA_BASE%\conf\logging.properties" -Dfile.encoding="UTF-8" -Dsun.jnu.encoding="UTF-8" :noJuli
其中,下面这部分是我追加上去的:
-Dfile.encoding="UTF-8" -Dsun.jnu.encoding="UTF-8"