<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; Spring</title>
	<atom:link href="https://blog.gmem.cc/tag/spring/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Tue, 21 Apr 2026 10:40:56 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Spring Boot学习笔记</title>
		<link>https://blog.gmem.cc/spring-boot-study-note</link>
		<comments>https://blog.gmem.cc/spring-boot-study-note#comments</comments>
		<pubDate>Tue, 05 Sep 2017 08:27:38 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Spring]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=15876</guid>
		<description><![CDATA[<p>简介 Spring Boot是Spring的一个子项目，它让创建独立运行（通过java -jar）的、产品级别的Spring应用变得简单： 支持创建独立运行于JVM中的Spring程序 内嵌Tomcat、Jetty或者Undertow，不需要部署War包 简化Maven的POM配置 在任何可能的情况下，自动配置Spring 提供产品级特性，包括性能度量、健康检测、外部化配置 不需要任何代码生成或者XML配置 新特性 2.0 支持Java 9，至少要求Java 8 基于 Spring 5 构建，Spring 的新特性均可以在 Spring Boot <a class="read-more" href="https://blog.gmem.cc/spring-boot-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/spring-boot-study-note">Spring Boot学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>Spring Boot是Spring的一个子项目，它让创建独立运行（通过java -jar）的、产品级别的Spring应用变得简单：</p>
<ol>
<li>支持创建独立运行于JVM中的Spring程序</li>
<li>内嵌Tomcat、Jetty或者Undertow，不需要部署War包</li>
<li>简化Maven的POM配置</li>
<li>在任何可能的情况下，自动配置Spring</li>
<li>提供产品级特性，包括性能度量、健康检测、外部化配置</li>
<li>不需要任何代码生成或者XML配置</li>
</ol>
<div class="blog_h2"><span class="graybg">新特性</span></div>
<div class="blog_h3"><span class="graybg">2.0</span></div>
<ol>
<li>支持Java 9，至少要求Java 8</li>
<li>基于 Spring 5 构建，Spring 的新特性均可以在 Spring Boot 2.0 中使用</li>
<li>为各种组件的响应式编程提供了自动化配置，如：Reactive Spring Data、Reactive Spring Security 等</li>
<li>支持 Spring MVC 的非阻塞式替代方案 WebFlux 以及嵌入式 Netty Server</li>
<li>对 HTTP/2 的支持</li>
<li>更灵活的属性绑定API，不通过<pre class="crayon-plain-tag">@ConfigurationProperties</pre>注解就能实现配置内容读取和使用</li>
<li>Spring Security 整合的简化</li>
<li>Gradle 插件增强，要求Gradle版本4.4+</li>
<li>Starter整合的第三方组件版本升级：
<ol>
<li>Tomcat 升级至 9.0，Servlet版本4.0</li>
<li>Jetty 升级至 9.4，Servlet版本3.1</li>
<li>Flyway 升级至 5</li>
<li>Hibernate 升级至 5.2</li>
<li>Thymeleaf 升级至 3</li>
</ol>
</li>
</ol>
<div class="blog_h1"><span class="graybg">起步</span></div>
<p>本章我们创建一个简单的基于Spring Boot的Web应用。</p>
<div class="blog_h2"><span class="graybg">Maven依赖</span></div>
<p>首先设置父项目：</p>
<pre class="crayon-plain-tag">&lt;parent&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
    &lt;version&gt;2.1.6.RELEASE&lt;/version&gt;
&lt;/parent&gt; </pre>
<p>然后添加Web的Starter依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;version&gt;2.1.6.RELEASE&lt;/version&gt;
&lt;/dependency&gt;</pre>
<p>只需要这一个依赖就可以了，开发Spring MVC项目所需要的繁杂依赖项不需要手工指定，均使用spring-boot-starter-web的传递依赖即可。</p>
<p>最后配置一下Maven插件，打包为可执行JAR：</p>
<pre class="crayon-plain-tag">&lt;build&gt;
	&lt;plugins&gt;
		&lt;plugin&gt;
			&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
			&lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
		&lt;/plugin&gt;
	&lt;/plugins&gt;
&lt;/build&gt; </pre>
<div class="blog_h2"><span class="graybg">HelloWorld</span></div>
<pre class="crayon-plain-tag">package cc.gmem.study.spring;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
// 让Spring Boot猜测应该怎么样配置Spring（基于添加的依赖）
@EnableAutoConfiguration
public class SampleController {

    @RequestMapping( "/" )
    @ResponseBody
    String home() {
        return "Hello World!";
    }

    public static void main( String[] args ) throws Exception {
        System.getProperties().put( "server.port", 9090 );
        SpringApplication.run( SampleController.class, args );
    }
}</pre>
<p>运行此程序，然后打开浏览器访问http://localhost:9090，即可访问。</p>
<div class="blog_h1"><span class="graybg">安装</span></div>
<div class="blog_h2"><span class="graybg">基于Maven</span></div>
<pre class="crayon-plain-tag">&lt;project&gt;
    &lt;!-- 
        继承基础配置：
        1、使用JDK 1.6作为默认编译级别
        2、使用UTF-8作为源码的编码方式
    --&gt;
    &lt;parent&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
        &lt;version&gt;1.5.6.RELEASE&lt;/version&gt;
    &lt;/parent&gt;
    &lt;!-- 可以定制的配置 --&gt;
    &lt;properties&gt;
        &lt;java.version&gt;1.8&lt;/java.version&gt;
        &lt;!-- 如果有多个main函数入口，需要指定一个start-class才能进行可执行JAR打包 --&gt;
        &lt;start-class&gt;cc.gmem.study.spring.boot.HelloApp&lt;/start-class&gt;
    &lt;/properties&gt;
    &lt;!-- Web应用典型依赖，包括了对Spring MVC和Tomcat的依赖 --&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
 
    &lt;!-- 打包为可执行的JAR --&gt;
    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;
 
&lt;/project&gt;</pre>
<p>如果parent构件已经存在，可以这样引入基础配置：</p>
<pre class="crayon-plain-tag">&lt;dependencyManagement&gt;
     &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-dependencies&lt;/artifactId&gt;
            &lt;version&gt;1.5.6.RELEASE&lt;/version&gt;
            &lt;type&gt;pom&lt;/type&gt;
            &lt;scope&gt;import&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/dependencyManagement&gt;</pre>
<div class="blog_h2"><span class="graybg">基于Gradle</span></div>
<pre class="crayon-plain-tag">plugins {
    id 'org.springframework.boot' version '1.5.6.RELEASE'
    id 'java'
}


jar {
    baseName = 'myproject'
    version =  '0.0.1-SNAPSHOT'
}

repositories {
    jcenter()
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    testCompile("org.springframework.boot:spring-boot-starter-test")
}</pre>
<div class="blog_h2"><span class="graybg">Starters</span></div>
<p>所谓Starter，就是一个依赖描述符，你可以包含它们，避免手工逐个配置依赖项。上面我们已经使用了两种Starter。常用的Starter如下表：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">Starter</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>spring-boot-starter</td>
<td>核心Starter，包含了自动配置支持、日志、YAML</td>
</tr>
<tr>
<td>spring-boot-starter-activemq</td>
<td>支持基于ActiveMQ的JMS应用</td>
</tr>
<tr>
<td>spring-boot-starter-amqp</td>
<td>支持Spring AMQP、Rabbit MQ</td>
</tr>
<tr>
<td>spring-boot-starter-aop</td>
<td>支持Spring AOP和AspectJ</td>
</tr>
<tr>
<td>spring-boot-starter-cache</td>
<td>支持Spring缓存机制</td>
</tr>
<tr>
<td>spring-boot-starter-data-jpa</td>
<td>支持JPA + Hibernate</td>
</tr>
<tr>
<td>spring-boot-starter-data-mongodb</td>
<td>支持MongoDB</td>
</tr>
<tr>
<td>spring-boot-starter-data-redis</td>
<td>支持Redis</td>
</tr>
<tr>
<td>spring-boot-starter-jdbc</td>
<td>支持基于Tomcat的JDBC连接池</td>
</tr>
<tr>
<td>spring-boot-starter-jersey</td>
<td>基于JAX-RS / Jersey构建RESTful的Web应用</td>
</tr>
<tr>
<td>spring-boot-starter-test</td>
<td>支持JUnit、Hamcrest、Mockito</td>
</tr>
<tr>
<td>spring-boot-starter-web</td>
<td>支持Web应用，包括RESTful，使用Tomcat作为默认内嵌容器</td>
</tr>
<tr>
<td>spring-boot-starter-web-services</td>
<td>支持Spring WebService</td>
</tr>
<tr>
<td>spring-boot-starter-websocket</td>
<td>支持WebSocket</td>
</tr>
<tr>
<td>spring-boot-starter-jetty<br />spring-boot-starter-tomcat<br />spring-boot-starter-undertow</td>
<td>内嵌容器依赖</td>
</tr>
<tr>
<td>spring-boot-starter-log4j2<br />spring-boot-starter-logging</td>
<td>日志依赖</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">使用Spring Boot</span></div>
<div class="blog_h2"><span class="graybg">配置类</span></div>
<p>尽管你可以通过<pre class="crayon-plain-tag">SpringApplication.run()</pre>传递XML源，但是Spring Boot倾向于使用基于Java的配置。 即主要配置源应该是一个注解了<pre class="crayon-plain-tag">@Configuration</pre>的Java类，此类通常定义了main方法。</p>
<div class="blog_h3"><span class="graybg">额外配置类</span></div>
<p>你可以使用<pre class="crayon-plain-tag">@Import</pre>注解引入额外的配置类。</p>
<p>你也可以使用<pre class="crayon-plain-tag">@ComponentScan</pre>自动扫描任何Spring组件，包括@ComponentScan本身。</p>
<p>如果要引入额外的XML配置，可以使用<pre class="crayon-plain-tag">@ImportResource</pre>注解。</p>
<div class="blog_h2"><span class="graybg">自动配置</span></div>
<p>Spring Boot的自动配置机制，尝试根据当前项目的依赖，自动判断如何配置Spring上下文、运行容器、数据库。例如，如果HSQLDB位于类路径下，Spring Boot会自动配置一个内存数据库。</p>
<p>要启用自动配置，可以在主配置类上添加<pre class="crayon-plain-tag">@EnableAutoConfiguration</pre>或者<pre class="crayon-plain-tag">@SpringBootApplication</pre>注解。</p>
<p>如果要查看哪些自动配置被启用，为何被启用，可以为应用程序传入参数<pre class="crayon-plain-tag">--debug</pre>。</p>
<p>你可以明确的禁用某种自动配置：</p>
<pre class="crayon-plain-tag">@Configuration
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class Configuration {
}</pre>
<p>注意：自动配置可以<span style="background-color: #c0c0c0;">被渐进的替代</span>。例如，当你手工配置了数据源后，Spring Boot就不会自动配置HSQLDB。</p>
<div class="blog_h3"><span class="graybg">@SpringBootApplication</span></div>
<p>此注解等价于@Configuration, @EnableAutoConfiguration,@ComponentScan的组合。其中@ComponentScan为：</p>
<pre class="crayon-plain-tag">@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })</pre>
<p>由于没有为@ComponentScan指定basePackage，<span style="background-color: #c0c0c0;">因此默认扫描当前类所在包及其子包</span>。</p>
<div class="blog_h2"><span class="graybg">Bean和注入</span></div>
<p>可以通过@ComponentScan来查找Bean，或者在配置类的方法中使用@Bean。@Component, @Service, @Repository, @Controller等注解都是支持的。</p>
<p>要为Bean注入字段，可以使用@Autowired,@Inject等注解。</p>
<div class="blog_h2"><span class="graybg">运行</span></div>
<p>很多IDE，包括Eclipse、IDEA都提供了对Spring Boot应用程序的支持。</p>
<p>通过命令行运行的示例：</p>
<pre class="crayon-plain-tag">java -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8000,suspend=n -jar boot-study.jar</pre>
<p>通过Maven运行的示例：</p>
<pre class="crayon-plain-tag">export MAVEN_OPTS=-Xmx1024m -XX:MaxPermSize=128M
mvn spring-boot:run</pre>
<p>通过Gradle运行的示例：</p>
<pre class="crayon-plain-tag">gradle bootRun</pre>
<div class="blog_h2"><span class="graybg">热部署</span></div>
<p>Spring Boot仅仅是简单的Java应用程序，JVM的热交换机制直接可以使用。但是JVM热交换有所限制，更加完整的解决方案是JRebel或者Spring Load。模块spring-boot-devtools也提供了快速的应用重启机制。</p>
<div class="blog_h3"><span class="graybg">静态内容重新载入</span></div>
<p>有多种实现方式，推荐使用spring-boot-devtools模块，因为它提供了额外的开发时特性——快速重启、LiveReload。DevTools的工作方式是监控类路径变化，这意味着你需要触发项目构建，在IDEA中可以调用Make Project触发（或者开启自动构建）。<span style="background-color: #c0c0c0;">静态内容的变化不会导致应用重启</span>，但是会导致LiveReload。</p>
<div class="blog_h3"><span class="graybg">模板重新载入</span></div>
<p>Spring Boot支持的模板技术大部分支持禁用缓存的功能，这样模板被修改后可以立即被感知到。使用spring-boot-devtools的情况下，模板自动重新载入。</p>
<div class="blog_h3"><span class="graybg">Java类重新载入</span></div>
<p>大部分IDE支持代码热替换，因此你对类进行非结构性的改变时，构建后Java类会自动更新。</p>
<p>Spring Loaded允许方法签名改变后的类仍然能够重新载入。</p>
<div class="blog_h3"><span class="graybg">快速重启</span></div>
<p>spring-boot-devtools提供了自动的重启功能，其速度比JRebel、Spring Loaded慢，但是比冷启动快的多。</p>
<p>spring-boot-devtools的快速重启功能依赖于ClassLoader，打包好的第三方库，使用Base加载器加载，而正在开发的那些类则使用restart加载器加载。当快速重启发生后，之前的restart加载器被丢弃，新的restart加载器被创建。</p>
<div class="blog_h2"><span class="graybg">DevTools</span></div>
<p>此模块提供一些开发时特性，要启用DevTools，引入：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;
    &lt;version&gt;1.5.6.RELEASE&lt;/version&gt;
    &lt;optional&gt;true&lt;/optional&gt;
&lt;/dependency&gt;</pre>
<p>DevTools在生产环境下会自动禁用，生产环境的判断依据是：</p>
<ol>
<li>以java -jar启动Spring Boot应用</li>
<li>或者，使用了特殊的ClassLoader </li>
</ol>
<div class="blog_h3"><span class="graybg">默认属性</span></div>
<p>模板引擎具有缓存功能、Spring MVC也在HTTP头中添加了缓存字段。DevTools通过配置属性，禁用了这些功能。</p>
<div class="blog_h3"><span class="graybg">自动重启</span></div>
<p>默认情况下，使用了DevTools的应用会在类路径发生变化时自动重启，注意模板、静态资源不会触发重启。具体来说，下列位置的资源不会触发重启：</p>
<p style="padding-left: 30px;">/META-INF/maven, /META-INF/resources ,/resources ,/static ,/public和/templates</p>
<p>如果需要显式指定那些资源不会触发重启，配置下面的属性：</p>
<pre class="crayon-plain-tag"># 自己指定不触发的资源
spring.devtools.restart.exclude=static/**,public/**
# 在默认不触发资源目录列表的基础上，额外增加
spring.devtools.restart.additional-exclude=</pre>
<p>如果需要额外指定会触发重启的资源，配置下面的属性：</p>
<pre class="crayon-plain-tag">spring.devtools.restart.additional-exclude=</pre>
<p>如果你使用某种持续的自动编译、产生输出的IDE，并且期望在这些输出发生变化时，触发重启，配置下面的属性：</p>
<pre class="crayon-plain-tag">spring.devtools.restart.trigger-file= </pre>
<p>要禁用DevTools的重启功能，配置下面的属性：</p>
<pre class="crayon-plain-tag">spring.devtools.restart.enabled=false</pre>
<div class="blog_h3"><span class="graybg">restart加载器</span></div>
<p>配置下面的属性以确定哪些JAR包由restart加载器加载：</p>
<pre class="crayon-plain-tag"># 下面的JAR由Base加载器加载
restart.exclude.companycommonlibs=/mycorp-common-[\\w-]+\.jar
# 下面的JAR由restart加载器加载
restart.include.projectcommon=/mycorp-myproj-[\\w-]+\.jar</pre>
<div class="blog_h3"><span class="graybg">LiveReload</span></div>
<p>DevTools包含了一个内嵌的LiveReload服务器，可以用来在资源发生变化时触发浏览器刷新。各大浏览器均有<a href="http://livereload.com/extensions/">LiveReload</a>扩展。</p>
<p>如果要禁用LiveReload服务器，配置：</p>
<pre class="crayon-plain-tag">spring.devtools.livereload.enabled=false </pre>
<div class="blog_h1"><span class="graybg">Spring Boot特性</span></div>
<div class="blog_h2"><span class="graybg">SpringApplication</span></div>
<p>此类提供了一些便捷方法，用于Spring应用的自举。最简单的用法：</p>
<pre class="crayon-plain-tag">SpringApplication.run(MyConfiguration.class, args);</pre>
<p>可以使用链式调用进行定制化：</p>
<pre class="crayon-plain-tag">new SpringApplicationBuilder()
        .sources(Parent.class)
        .child(Application.class)
        .bannerMode(Banner.Mode.OFF)
        .run(args); </pre>
<div class="blog_h3"><span class="graybg">启动失败</span></div>
<p>启动后，控制台默认打印INFO级别的日志。</p>
<p>如果应用自举失败，已经注册的FailureAnalyzer会打印相关的信息，Spring Boot提供了很多FailureAnalyzer实现。 </p>
<p>如果FailureAnalyzer没有提供有价值的信息，你可以设置应用程序参数<pre class="crayon-plain-tag">--debug</pre>或者设置org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer的日志级别。</p>
<div class="blog_h3"><span class="graybg">定制Banner</span></div>
<p>你可以在类路径下放置一个banner.txt文件，或者设置banner.location来指定任意文件。你还可以在类路径下添加banner.png|gif|jpg等图片，这些图片会被转换为ASCII（透明的PNG背景被转换为黑色），打印在文本Banner之前。</p>
<div class="blog_h3"><span class="graybg">应用事件</span></div>
<p>除了ContextRefreshedEvent，SpringApplication会发布很多额外的事件：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>ApplicationStartingEvent</td>
<td>开始启动时发布，此时仅仅注册了listeners、initializers，尚未进行其它处理</td>
</tr>
<tr>
<td>ApplicationEnvironmentPreparedEvent</td>
<td>Spring context需要的Environment准备好之后调用，此时Context尚未创建</td>
</tr>
<tr>
<td>ApplicationPreparedEvent</td>
<td>Bean定义已经全部加载，即将刷新Context</td>
</tr>
<tr>
<td>ApplicationReadyEvent</td>
<td>Context刷新完毕，相关的回调已经被调用</td>
</tr>
<tr>
<td>ApplicationFailedEvent</td>
<td>启动失败后发布</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Web环境</span></div>
<p>SpringApplication会自动尝试创建合适的ApplicationContext实现类。默认的：</p>
<ol>
<li>如果当前应用是Web应用，创建AnnotationConfigEmbeddedWebApplicationContext</li>
<li>否则创建AnnotationConfigApplicationContext</li>
</ol>
<p>你可以调用setWebEnvironment()来强制指定是否Web应用。</p>
<div class="blog_h3"><span class="graybg">ApplicationArguments</span></div>
<p>你可以注入此类型，以访问传递给SpringApplication.run的参数。</p>
<div class="blog_h2"><span class="graybg">配置外部化</span></div>
<p>Environment对象是配置信息的容器。</p>
<p>Spring Boot允许你把配置信息外部化，这样同一套代码就可以很容易的在不同环境下运行。</p>
<p>你可以使用YAML 、环境变量、命令行参数来指定配置。</p>
<p>使用<pre class="crayon-plain-tag">@Value</pre>注解可以直接注入配置参数，也可以通过Environment来访问，或者通过@ConfigurationProperties绑定到结构化对象。例如：</p>
<pre class="crayon-plain-tag">@Component
public class Bean {
    @Value("${name}")
    private String name;

}</pre>
<p>如果类路径下的application.properties文件中有name这个键，或者命令行指定了--name参数，则会被注入到上述Bean的属性中。</p>
<div class="blog_h3"><span class="graybg">配置优先级</span></div>
<p>Spring使用PropertySource来定位配置属性，它会从以下位置寻找（优先级从高到低）：</p>
<ol>
<li>当使用DevTools时，DevTools全局设置，位于~/.spring-boot-devtools.properties</li>
<li>位于测试用例上的@TestPropertySource注解</li>
<li>位于测试用例上的@SpringBootTest#properties注解</li>
<li>命令行参数。例如 --server.port=9090</li>
<li>来自SPRING_APPLICATION_JSON中的属性，这是内联在环境变量或者系统属性中的一个JSON</li>
<li>ServletConfig初始化参数</li>
<li>ServletContext初始化参数</li>
<li>来自java:comp/env的JNDI属性</li>
<li>系统属性System.getProperties()</li>
<li>OS环境变量</li>
<li>RandomValuePropertySource中的random.*属性</li>
<li>位于应用JAR包外部的application-{profile}.properties文件（或者YAML），针对特定profile</li>
<li>位于应用JAR包内部的application-{profile}.properties文件（或者YAML），针对特定profile</li>
<li>位于应用JAR包外部的application.properties文件（或者YAML）</li>
<li>位于应用JAR包内部的application.properties文件（或者YAML）</li>
<li>@Configuration类上的@PropertySource注解</li>
<li>缺省属性，来自SpringApplication.setDefaultProperties</li>
</ol>
<p>其中application.properties的搜索位置包括（优先级从高到低）：</p>
<ol>
<li>当前目录的/config子目录</li>
<li>当前目录</li>
<li>类路径下的/config包</li>
<li>类路径根目录</li>
</ol>
<p>如果你不想使用上述默认属性文件名称，可以指定命令行参数：</p>
<pre class="crayon-plain-tag">--spring.config.location=classpath:/default.properties,classpath:/override.properties</pre>
<div class="blog_h3"><span class="graybg">针对特定Profile的属性 </span></div>
<p>你可以针对不同运行环境，为Spring Boot应用定义多个Profile。Environment包含了若干内置Profile，且默认激活（如果你没有激活其它）default这个Profile。</p>
<p>针对特定Profile的属性定义在application-{profile}.properties文件中。</p>
<p>通过设置spring.profiles.active属性，可以改变当前活动Profile。例如：</p>
<pre class="crayon-plain-tag">spring.profiles.active=dev,hsqldb</pre>
<div class="blog_h3"><span class="graybg">属性占位符</span></div>
<p>在属性文件中，你可以通过占位符引用先前定义的属性：</p>
<pre class="crayon-plain-tag">app.name=MyApp
app.description=${app.name} is a Spring Boot application</pre>
<div class="blog_h3"><span class="graybg">Maven变量展开</span></div>
<p>你可以在属性文件中引用Maven项目属性：</p>
<pre class="crayon-plain-tag">spring.profiles.active=@environment@
app.encoding=@project.build.sourceEncoding@
app.java.version=@java.version@</pre>
<p>配合Maven Profile：</p>
<pre class="crayon-plain-tag">&lt;profile&gt;
    &lt;id&gt;pro&lt;/id&gt;
    &lt;properties&gt;
        &lt;environment&gt;pro&lt;/environment&gt;
    &lt;/properties&gt;
&lt;/profile&gt;</pre>
<p>则运行<pre class="crayon-plain-tag">mvn package -Ppro</pre>后属性文件中的@environment@自动替换为pro</p>
<div class="blog_h2"><span class="graybg">日志</span></div>
<p>Spring Boot支持使用Java Util Logging、Log4J2、Logback这几种日志实现。如果你使用Starters，则默认使用Logback。要定制日志配置，使用以下配置文件：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">日志实现</td>
<td style="text-align: center;">配置文件</td>
</tr>
</thead>
<tbody>
<tr>
<td>Logback</td>
<td>logback-spring.xml, logback-spring.groovy, logback.xml 或者logback.groovy</td>
</tr>
<tr>
<td>Log4j2</td>
<td>log4j2-spring.xml 或者 log4j2.xml</td>
</tr>
<tr>
<td>Java Util Logging</td>
<td>logging.properties</td>
</tr>
</tbody>
</table>
<p>你可以使用Slf4J提供的日志API。</p>
<div class="blog_h3"><span class="graybg">日志配置</span></div>
<p>可以在application.properties中配置对应的属性：</p>
<pre class="crayon-plain-tag"># 设置根日志级别
logging.level.root=DEBUG
# 设置日志级别
logging.level.cc.gmem=DEBUG
# 配置基于文件的日志
logging.file=${java.io.tmpdir}/application.log
# 配置日志输出格式
logging.pattern.console= "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
logging.pattern.file= "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"</pre>
<div class="blog_h2"><span class="graybg">Web应用支持</span></div>
<p>Spring MVC框架允许你创建特殊的@Controller、@RestController以处理入站的HTTP请求，具体处理逻辑由注解了@RequestMapping的方法负责。</p>
<div class="blog_h3"><span class="graybg">自动配置</span></div>
<p>执行以下自动配置：</p>
<ol>
<li>包含ContentNegotiatingViewResolver 、BeanNameViewResolver 这两个Bean</li>
<li>支持包括WebJars在内的静态资源</li>
<li>自动注册Converter、GenericConverter、Formatter</li>
<li>支持HttpMessageConverters</li>
<li>自动注册MessageCodesResolver</li>
<li>支持静态index.html</li>
<li>支持定制Favicon</li>
<li>自动使用ConfigurableWebBindingInitializer </li>
</ol>
<p>如果你仅仅想添加额外的拦截器、formatters、view controllers，可以在WebMvcConfigurerAdapter的子类上加@Configuration注解，但是不使用@EnableWebMvc注解。</p>
<p>如果你希望使用自己实现的RequestMappingHandlerMapping、RequestMappingHandlerAdapter 、ExceptionHandlerExceptionResolver ，声明一个WebMvcRegistrationsAdapter，提供这些组件。</p>
<p>如果你希望对Spring MVC进行完全的控制，在主配置类上添加@EnableWebMvc注解。</p>
<div class="blog_h3"><span class="graybg">HttpMessageConverters</span></div>
<p>如果需要自定义消息转换器，声明HttpMessageConverters类型的Bean即可：</p>
<pre class="crayon-plain-tag">import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
// ...
@Bean
public HttpMessageConverters customConverters() {
    HttpMessageConverter&lt;?&gt; additional = ...
    HttpMessageConverter&lt;?&gt; another = ...
    return new HttpMessageConverters(additional, another);
}</pre>
<div class="blog_h3"><span class="graybg">定制JSON编解码器</span></div>
<p>当基于JacksonJSON来编解码JSON时，你可能需要定制JsonSerializer、JsonDeserializer。</p>
<p>结合Spring Boot使用时，如果要注册定制的编解码器，需要在编解码器类（或者其外部类）上声明注解：</p>
<pre class="crayon-plain-tag">@JsonComponent
public class JsonCodecs {
    public static class Serializer extends JsonSerializer {}
    public static class Deserializer extends JsonDeserializer {}
}</pre>
<div class="blog_h3"><span class="graybg">静态内容</span></div>
<p>默认情况下，Spring Boot从类路径、ServletContext的根目录下寻找：</p>
<ol>
<li>/static</li>
<li>/public</li>
<li>/resources</li>
<li>/META-INF/resources </li>
</ol>
<p>这几个目录，并将其作为静态内容看待，你可以设置spring.resources.static-locations以修改静态目录。 这些静态内容默认映射URL为/**，你可以设置下面的属性以修改：</p>
<pre class="crayon-plain-tag"># 重新定位URL
spring.mvc.static-path-pattern=/resources/** </pre>
<p>除了以目录方式组织的静态内容，Spring Boot还支持<a href="http://www.webjars.org/">WebJars</a>。任何/webjars/**目录下的JAR，如果符合WebJar格式，将会被作为静态资源使用。</p>
<div class="blog_h3"><span class="graybg">Favicon</span></div>
<p>你可以在静态文件根目录中放置一个favicon.ico文件。</p>
<div class="blog_h3"><span class="graybg">模板引擎支持</span></div>
<p>Spring Boot支持<a href="http://freemarker.org/docs/">FreeMarker</a>、<a href="http://docs.groovy-lang.org/docs/next/html/documentation/template-engines.html#_the_markuptemplateengine">Groovy</a>、<a href="http://www.thymeleaf.org/">Thymeleaf</a>、<a href="https://mustache.github.io/">Mustache</a>这几种模板引擎的自动配置。</p>
<p>JSP也可以使用。</p>
<div class="blog_h3"><span class="graybg">错误处理</span></div>
<p>Spring Boot默认提供了一个/error映射，来处理所有错误，并且注册为Servlet容器的全局错误页面。此错误页面的默认行为：</p>
<ol>
<li>对于浏览器客户端：以HTML格式显示一个页面。要定制此页面，可以配置一个映射到/error的View</li>
<li>对于机器客户端：返回一个JSON响应，包含错误码、详细错误信息。此JSON响应对应的Java类为ErrorAttributes，你可以注册一个ErrorAttributes的子类型Bean，改变响应内容<br />你还可以针对特定的控制器、异常类型，定制此JSON：<br />
<pre class="crayon-plain-tag">// 针对和FooController在同一包中的所有控制器
@ControllerAdvice(basePackageClasses = FooController.class)
public class FooControllerAdvice extends ResponseEntityExceptionHandler {
    // 针对YourException
    @ExceptionHandler(YourException.class)
    @ResponseBody
    ResponseEntity&lt;?&gt; handleControllerException(HttpServletRequest request, Throwable ex) {
        HttpStatus status = getStatus(request);
        // CustomErrorType的JSON展示会代替ErrorAttributes写到响应中
        return new ResponseEntity&lt;&gt;(new CustomErrorType(status.value(), ex.getMessage()), status);
    }

    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;Media Source
        }
        return HttpStatus.valueOf(statusCode);
    }

}Environment</pre>
</li>
</ol>
<p>要完成替换上述默认行为，可以注册一个ErrorController类型的Bean。</p>
<div class="blog_h3"><span class="graybg">定制错误页</span></div>
<p>如果希望针对不同的HTTP状态码定制错误页面，添加一个文件到/error目录下。错误页面可以是静态HTML或者某种被支持的模板。 </p>
<p>例如，要对404定制错误页面，可以创建src/main/resources/public/error/404.html。要对所有5xx定制错误页面，可以在前面的目录中创建5xx.ftl。</p>
<div class="blog_h3"><span class="graybg">CORS支持</span></div>
<p>跨源资源共享（Cross-origin resource sharing）是被浏览器广泛实现的W3C规范，允许你指定一个灵活的策略，允许哪些跨Domain请求被发送。使用CORS可以避免使用IFRAME、JSONP等变通技术。</p>
<p>Spring MVC 4.2开始对CORS进行了开箱即用的支持。你可以在控制器方法上使用@CrossOrigin注解。或者配置全局性的CORS策略：</p>
<pre class="crayon-plain-tag">@Configuration
public class MyConfiguration {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**");
            }
        };
    }
}</pre>
<div class="blog_h3"><span class="graybg">JAX-RS和Jersey</span></div>
<p>如果你喜欢基于JAX-RS编程模型创建REST端点，你可以使用Jersey、 Apache CXF等框架。要集成到Spring你只需要把它们的Servlet、Filter注册为@Bean。</p>
<p>Jersey 2.x对Spring集成提供了支持，Spring Boot可以进行自动配置，你需要依赖spring-boot-starter-jersey这个Starter并且注册ResourceConfig类型的Bean：</p>
<pre class="crayon-plain-tag">@Component
@ApplicationPath("/api")  // 默认情况下Jersey Servlet被映射到/*，此注解用于定制映射路径
public class JerseyConfig extends ResourceConfig {
    public JerseyConfig() {
        register(Endpoint.class);
    }
}
// 所有注册的Endpoint，必须是@Component，且包含HTTP资源注解（例如GET)
@Component
@Path("/hello")
public class Endpoint {
    @GET
    public String message() {
        return "Hello";
    }
}</pre>
<p>要进行更定制化的配置，实现任意数量的ResourceConfigCustomizer类型的Bean。</p>
<p>默认情况下，Jersey在一个ServletRegistrationBean类型的@Bean中初始化其Servlet。此Servlet默认是延迟加载的，你可以配置属性spring.jersey.servlet.load-on-startup让其立即加载。 你也可以配置spring.jersey.type=filter，这样一个ServletFilter会代替上述Servlet。</p>
<div class="blog_h3"><span class="graybg">JAX-RS和CXF</span></div>
<p>添加Maven依赖：</p>
<pre class="crayon-plain-tag">&lt;dependencies&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter&lt;/artifactId&gt;
  &lt;/dependency&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;org.apache.cxf&lt;/groupId&gt;
    &lt;artifactId&gt;cxf-spring-boot-starter-jaxrs&lt;/artifactId&gt;
    &lt;version&gt;3.3.1&lt;/version&gt;
  &lt;/dependency&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
    &lt;scope&gt;test&lt;/scope&gt;
  &lt;/dependency&gt;
  &lt;!-- 对JSON的支持 --&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;org.codehaus.jackson&lt;/groupId&gt;
    &lt;artifactId&gt;jackson-jaxrs&lt;/artifactId&gt;
    &lt;version&gt;1.9.13&lt;/version&gt;
  &lt;/dependency&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;org.codehaus.jackson&lt;/groupId&gt;
    &lt;artifactId&gt;jackson-xc&lt;/artifactId&gt;
    &lt;version&gt;1.9.13&lt;/version&gt;
  &lt;/dependency&gt; 
&lt;/dependencies&gt;</pre>
<p>RESTful资源接口示例，一般直接将Service层作为RESTful资源的接口：</p>
<pre class="crayon-plain-tag">import java.util.Collection;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.javacodegeeks.examples.jaxrs.model.Student;

// 将此类标识为根资源
@Path("students")
// 指定请求、响应的MIME类型
@Consumes("application/json;charset=UTF-8")
@Produces(MediaType.APPLICATION_JSON)
public interface StudentService {
  
  // 动词，此外支持@POST, @UPDATE，@DELETE
  @GET
  public Collection&lt;Student&gt; getAllStudents();
  
  @Path("{id}")
  @GET
  // 返回值类型javax.ws.rs.core.Response，是HTTP响应的抽象
  public Response getById(@PathParam("id") Long id);

}</pre>
<p>在Spring属性中配置CXF：</p>
<pre class="crayon-plain-tag"># 默认/services
cxf.path=/studentservice
# 让CXF自动扫描带有JAX-RS注解的类
cxf.jaxrs.classes-scan=true
cxf.jaxrs.classes-scan-packages=cc.gmem.springboot.jaxrs</pre>
<p>现在你可以通过URL：http://localhost:8080/studentservice/students来访问 StudentService暴露的RESTful服务了。</p>
<div class="blog_h3"><span class="graybg">内嵌Servlet容器</span></div>
<p>Spring Boot支持内嵌的Tomcat、Jetty或者Undertow。通过合适的Starter你就可以得到自动化的配置。内嵌服务器默认监听端口8080。</p>
<p>当使用内嵌Servlet容器时，标准的Servlet组件（Servlet、Filter、Listener）既可以注册为Spring Beans，也可以自动被扫描。要启用自动扫描，使用@ServletComponentScan注解，@WebServlet、@WebFilter、@WebListener可以被自动扫描。</p>
<p>如果应用中仅仅有一个Servlet，它会默认被映射到 /，如果有多个Servlet，其Bean名称被作为URL前缀。Filter默认映射到/*。</p>
<p>如果自动化配置不能满足需要，你可以实现自己的ServletRegistrationBean、FilterRegistrationBean或者ServletListenerRegistrationBean。</p>
<p>要定制Servlet容器的运行参数，可以使用配置属性，通常定义在application.properties文件中。通用的配置属性包括：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">Servlet容器属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>server.port</td>
<td>监听端口</td>
</tr>
<tr>
<td>server.address</td>
<td>监听地址</td>
</tr>
<tr>
<td>server.ssl</td>
<td>
<p>SSL相关配置，示例：</p>
<pre class="crayon-plain-tag"># 密钥数据库的位置
server.ssl.key-store:classpath:keystore.jks
# 密钥数据库的密码
server.ssl.key-store-password:kurento
# 密钥数据库的类型
server.ssl.keyStoreType:JKS
# 使用的密钥
server.ssl.keyAlias:kurento-selfsigned</pre>
</td>
</tr>
<tr>
<td>server.session.persistence</td>
<td>是否进行会话的持久化</td>
</tr>
<tr>
<td>server.session.timeout</td>
<td>会话过期时间</td>
</tr>
<tr>
<td>server.session.store-dir</td>
<td>会话存放位置</td>
</tr>
<tr>
<td>server.session.cookie.*</td>
<td>Cookie相关配置</td>
</tr>
<tr>
<td>server.error.path</td>
<td>错误页面配置</td>
</tr>
<tr>
<td>server.tomcat<br />server.undertow</td>
<td>容器特定的配置属性，<a href="https://github.com/spring-projects/spring-boot/blob/v1.5.6.RELEASE/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java">这个页面</a>包含所有可用属性</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">EmbeddedWebApplicationContext</span></div>
<p>这是一种特化的WebApplicationContext，在自举时它会寻找一个EmbeddedServletContainerFactory实现类，通常是TomcatEmbeddedServletContainerFactory、JettyEmbeddedServletContainerFactory或者UndertowEmbeddedServletContainerFactory，以创建Servlet容器。
<div class="blog_h2"><span class="graybg">WebSockets支持</span></div>
<p>Spring Boot支持基于内嵌容器的自动化WebSockets配置，包含对spring-boot-starter-websocket的依赖即可。 </p>
<div class="blog_h2"><span class="graybg">任务调度支持</span></div>
<p>要启用任务调度支持，只需要在应用程序入口添加<pre class="crayon-plain-tag">@EnableScheduling</pre>注解即可，之后你就可以在任何服务的方法上使用注解了：</p>
<pre class="crayon-plain-tag">@EnableScheduling
public class App {}


// 每5秒调度执行一次
@Scheduled( fixedRate = 5000 )
@Scheduled( cron = "*/5 * * * * * *" )
// 每次执行完毕后，延迟5秒再次执行
@Scheduled( fixedDelay = 5000 )
public synchronized void cleanup() {
}</pre>
<div class="blog_h2"><span class="graybg">缓存支持</span></div>
<p>引入依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-cache&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
<p>在主程序上使用注解来启用缓存支持：</p>
<pre class="crayon-plain-tag">import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class CacheApplication {} </pre>
<div class="blog_h1"><span class="graybg">常用样例</span></div>
<div class="blog_h2"><span class="graybg">JPA和缓存</span></div>
<div class="blog_h3"><span class="graybg">POM配置</span></div>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;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/xsd/maven-4.0.0.xsd"&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;mysql&lt;/groupId&gt;
            &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
            &lt;version&gt;6.0.6&lt;/version&gt;
        &lt;/dependency&gt;

        &lt;!-- 使用Log4j2 --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter&lt;/artifactId&gt;
            &lt;version&gt;RELEASE&lt;/version&gt;
            &lt;exclusions&gt;
                &lt;exclusion&gt;
                    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                    &lt;artifactId&gt;spring-boot-starter-logging&lt;/artifactId&gt;
                &lt;/exclusion&gt;
            &lt;/exclusions&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-log4j2&lt;/artifactId&gt;
            &lt;version&gt;RELEASE&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.fasterxml.jackson.dataformat&lt;/groupId&gt;
            &lt;artifactId&gt;jackson-dataformat-yaml&lt;/artifactId&gt;
            &lt;version&gt;2.5.0&lt;/version&gt;
        &lt;/dependency&gt;

        &lt;!-- Spring Boot 缓存支持 --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-cache&lt;/artifactId&gt;
            &lt;version&gt;RELEASE&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!-- Spring Boot data-jpa支持 --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
            &lt;version&gt;RELEASE&lt;/version&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/project&gt;</pre>
<div class="blog_h3"><span class="graybg">application.properties</span></div>
<pre class="crayon-plain-tag">spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none

spring.datasource.url=jdbc:mysql://10.255.223.241:3306/digital
spring.datasource.username=digital
spring.datasource.password=digital
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver</pre>
<div class="blog_h3"><span class="graybg">实体类</span></div>
<pre class="crayon-plain-tag">package cc.gmem.study.sb.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table( name = "media" )
public class Media {

    @Id
    private Long mediaId;

    @Column
    private Long saleId;

    @Column
    private String title;

    @Column( name = "is_bn" )
    private String isbn;

    @Column( name = "shelf_status" )
    private boolean onShelf;
}</pre>
<div class="blog_h3"><span class="graybg">存储层</span></div>
<pre class="crayon-plain-tag">package cc.gmem.study.sb.repo;


import cc.gmem.study.sb.entity.Media;
import org.springframework.cache.annotation.*;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

/**
 * JpaRepository&lt;EntityClass,IdType&gt;，继承了：
 *      PagingAndSortingRepository 支持分页和排序
 *          CrudRepository 支持常规增删改查
 *              Repository 标识接口
 */

/* 类级别的缓存公共设置 */
@CacheConfig( cacheNames = "media" )
public interface MediaRepository extends JpaRepository&lt;Media, Long&gt; {

    // 指定一个JPA查询
    @Query( "SELECT m FROM Media m WHERE m.isbn=:isbn AND m.onShelf=true" )
    /**
     * 将函数调用结果放入缓存，优先从缓存中查找，如果找不到才真正调用函数
     * key 指定SpEL表达式作为缓存键，p0表示第一个入参，result表示返回值
     * keyGenerator 不能和key同时使用，指定一个org.springframework.cache.interceptor.KeyGenerator实现类
     * sync 多个线程同时调用此函数时，是否串行化。当底层操作非常耗时时可以设置为true
     * condition 指定SpEL，仅当（在函数调用前的）求值结果为true时才会缓存
     * unless 指定SpEL，仅当（在函数调用后的）求值结果为false时才会缓存，可以引用result变量
     * cacheManager 指定使用的缓存管理器，@EnableCaching会自动注册可用的缓存管理器，查找缓存实现的顺序为：
     *      GenericJCache EhCache 2.x Hazelcast Infinispan Redis Guava Simple
     *      可以使用spring.cache.type强制指定
     * cacheResolver 指定使用的缓存解析器
     */
    @Cacheable( key = "#p0", sync = false, condition = "#p0.length()==13", unless = "#result == null" )
    Media findByIsbn( @Param( "isbn" ) String isbn );

    @Override
    /**
     * 调用此函数时导致缓存清除
     * allEntries 清除所有缓存
     * beforeInvocation 在调用函数前还是后执行清除
     */
    @CacheEvict( key = "#p0", allEntries = false, beforeInvocation = false )
    void deleteById( Long mediaId );

    @Override
    // 下面的注解总是调用函数并缓存其结果
    @CachePut( key = "#p0.isbn" )
    // 可以同时存储多个缓存
    @Caching(
        put = {
            // 可以引用result
            @CachePut(value = "media", key = "#result.username", condition = "#result != null"),
            @CachePut(value = "media", key = "#result.id", condition = "#result != null")
        }
    )
    Media save( Media media );
}</pre>
<div class="blog_h3"><span class="graybg">服务层</span></div>
<pre class="crayon-plain-tag">package cc.gmem.study.sb.service;


import cc.gmem.study.sb.entity.Media;
import cc.gmem.study.sb.repo.MediaRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

@Service
@Transactional
public class MediaService {

    @Autowired
    private MediaRepository repo;

    public Media findUserByIsbn( String isbn ) {
        return repo.findByIsbn( isbn );
    }
}</pre>
<div class="blog_h3"><span class="graybg">入口点</span></div>
<pre class="crayon-plain-tag">package cc.gmem.study.sb;

import cc.gmem.study.sb.entity.Media;
import cc.gmem.study.sb.service.MediaService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.ApplicationContext;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
// 启用Spring基于注解的缓存管理
@EnableCaching
// 从何处扫描JPA实体类
@EntityScan( basePackages = { "cc.gmem.study.sb.entity" } )
// 从何处查找Repository接口的子接口，并自动生成实现类
@EnableJpaRepositories( basePackages = "cc.gmem.study.sb.repo" )
public class JPACacheApplication {

    private static final Logger LOGGER = LoggerFactory.getLogger( JPACacheApplication.class );

    public static void main( String[] args ) {
        ApplicationContext ctx = SpringApplication.run( JPACacheApplication.class, args );
        MediaService ms = ctx.getBean( MediaService.class );
        Media m = ms.findUserByIsbn( "9787547227961" );
        LOGGER.debug( m.getTitle() );
        m = ms.findUserByIsbn( "9787547227961" );
        LOGGER.debug( m.getTitle() );
    }
}</pre>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">关于日志</span></div>
<p>org.springframework.boot.diagnostics应该调整到debug级别， org.springframework应该调整到warn级别，这样可以看到更多有价值的日志。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/spring-boot-study-note">Spring Boot学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/spring-boot-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Spring对WebSocket的支持</title>
		<link>https://blog.gmem.cc/ws-support-of-spring</link>
		<comments>https://blog.gmem.cc/ws-support-of-spring#comments</comments>
		<pubDate>Tue, 05 Sep 2017 03:38:21 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Network]]></category>
		<category><![CDATA[Spring]]></category>
		<category><![CDATA[STOMP]]></category>
		<category><![CDATA[WebSocket]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=15848</guid>
		<description><![CDATA[<p>简介 Spring 4.x引入了新的模块spring-websocket，对WebSocket提供了全面的支持，Spring的WebSocket实现遵循JSR-356（Java WebSocket API），并且添加了一些额外特性。 绝大部分现代浏览器均支持WebSocket，包括IE 10+。对于不支持WebSocket的浏览器，Spring允许基于 SockJS协议作为备选传输方案。 消息架构  与REST那种大量URL + HTTP方法来区分对象和操作的风格完全不同，WebSocket仅仅使用单个URL。WebSocket更加和传统的MOM类似，它是异步的、 事件驱动的基于消息的架构。 Spring 4 引入了新的模块spring-messaging，抽象出了Message、MessageChannel、MessageHandler等消息架构的基础概念。此模块包含了一些注解，用于将消息映射到方法（类似于Spring MVC把URL映射到方法）。 子协议支持 WebSocket是在TCP之上很薄的一层封装，它仅仅是把比特流转换为消息（文本、二进制）流，解析消息的职责由应用程序负责。我们可以在WebSocket之上提供应用层子协议。 在WebSocket握手阶段，客户端和服务器可以基于Sec-WebSocket-Protocol头来协商子协议。Spring支持STOMP —— 一个简单的消息协议。 WebSocket <a class="read-more" href="https://blog.gmem.cc/ws-support-of-spring">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/ws-support-of-spring">Spring对WebSocket的支持</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">简介</span></div>
<p>Spring 4.x引入了新的模块spring-websocket，对WebSocket提供了全面的支持，Spring的WebSocket实现遵循JSR-356（Java WebSocket API），并且添加了一些额外特性。</p>
<p>绝大部分现代浏览器均<a href="http://caniuse.com/#feat=websockets">支持WebSocket</a>，包括IE 10+。对于不支持WebSocket的浏览器，Spring允许基于 SockJS协议作为备选传输方案。</p>
<div class="blog_h3"><span class="graybg">消息架构 </span></div>
<p>与REST那种大量URL + HTTP方法来区分对象和操作的风格完全不同，WebSocket仅仅使用单个URL。WebSocket更加和传统的MOM类似，它是异步的、 事件驱动的基于消息的架构。</p>
<p>Spring 4 引入了新的模块spring-messaging，抽象出了Message、MessageChannel、MessageHandler等消息架构的基础概念。此模块包含了一些注解，用于将消息映射到方法（类似于Spring MVC把URL映射到方法）。</p>
<div class="blog_h3"><span class="graybg">子协议支持</span></div>
<p>WebSocket是在TCP之上很薄的一层封装，它仅仅是把比特流转换为消息（文本、二进制）流，解析消息的职责由应用程序负责。我们可以在WebSocket之上提供应用层子协议。</p>
<p>在WebSocket握手阶段，客户端和服务器可以基于Sec-WebSocket-Protocol头来协商子协议。Spring支持STOMP —— 一个简单的消息协议。</p>
<div class="blog_h2"><span class="graybg">WebSocket API</span></div>
<div class="blog_h3"><span class="graybg">配置WebSocketHandler</span></div>
<p>Spring提供了可以在很多WebSocket引擎中运行的API，支持的引擎包括Tomcat 7.0.47+, Jetty 9.1+, GlassFish 4.1+, WebLogic 12.1.3+等。</p>
<p>要创建一个WebSocket服务器，可以实现WebSocketHandler接口，或者继承TextWebSocketHandler或者BinaryWebSocketHandler类：</p>
<pre class="crayon-plain-tag">import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.TextMessage;

public class HelloHandler extends TextWebSocketHandler {
    // 接受消息的回调
    public void handleTextMessage(WebSocketSession session, TextMessage message) {
        // 发送消息
        session.sendMessage( new TextMessage( payload ) );
    }

}</pre>
<p>每个WebSocketHandler， 处理单个URL。在一个WebSocket端口上可以有多个WebSocketHandler。要注册WebSocketHandler，可以：</p>
<pre class="crayon-plain-tag">import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(helloHander(), "/hello");
    }

    @Bean
    public WebSocketHandler helloHander() {
        return new HelloHander();
    }

}</pre>
<p>也可以使用等价的XML配置：</p>
<pre class="crayon-plain-tag">&lt;beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket.xsd"&gt;

    &lt;websocket:handlers&gt;
        &lt;websocket:mapping path="/hello" handler="helloHander"/&gt;
    &lt;/websocket:handlers&gt;

    &lt;bean id="helloHander" class="cc.gmem.study.spring.ws.HelloHandler"/&gt;

&lt;/beans&gt;</pre>
<div class="blog_h3"><span class="graybg">定制WebSocket握手</span></div>
<p>通过HandshakeInterceptor可以对WebSocket最初基于HTTP的握手进行定制，此拦截器暴露beforeHandshake/afterHandshake方法，实现这些方法可以：</p>
<ol>
<li>阻止握手</li>
<li>设置在WebSocketSession中可以使用的属性 </li>
</ol>
<p>拦截器的注册方式为：</p>
<pre class="crayon-plain-tag">@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new HelloHandler(), "/hello")
                // 添加拦截器
                .addInterceptors(new HttpSessionHandshakeInterceptor());
    }

}</pre>
<p>等价的XML配置：</p>
<pre class="crayon-plain-tag">&lt;websocket:handlers&gt;
    &lt;websocket:mapping path="/hello" handler="helloHandler"/&gt;
    &lt;websocket:handshake-interceptors&gt;
        &lt;bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/&gt;
    &lt;/websocket:handshake-interceptors&gt;
&lt;/websocket:handlers&gt;</pre>
<div class="blog_h3"><span class="graybg">装饰WebSocketHandler</span></div>
<p>使用WebSocketHandlerDecorator来装饰WebSocketHandler，可以实现额外的行为。当基于Java-Config / XML来配置时，日志、异常处理这两个装饰器自动添加。</p>
<p>ExceptionWebSocketHandlerDecorator会捕获任何WebSocketHandler抛出的异常，并以1011状态码（服务器错误）关闭WebSocket会话。</p>
<div class="blog_h3"><span class="graybg">部署</span></div>
<p>WebSocket API可以和Spring MVC一起使用，DispatcherServlet同时负责WebSocket握手和普通HTTP请求的处理。</p>
<p>你也可以独立在其它HTTP服务环境中使用WebSocket API，可以借助WebSocketHttpRequestHandler集成WebSocketHandler到HTTP服务环境中。</p>
<div class="blog_h3"><span class="graybg">WebSocket引擎配置</span></div>
<p>每种底层Servlet引擎都暴露了一些配置属性，进行缓冲区大小、超时时间等参数的配置。</p>
<p>当使用Tomcat/WildFly/GlassFish时，你可以使用ServletServerContainerFactoryBean进行引擎配置：</p>
<pre class="crayon-plain-tag">@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        // WebSocket消息缓冲区大小，如果客户端发来的消息较大，需要按需调整
        // 和libwebsockets配合时，客户端报错error on reading from skt : 104，即因为缓冲区不够大
        container.setMaxTextMessageBufferSize(8192);
        container.setMaxBinaryMessageBufferSize(8192);
        return container;
    }
}</pre>
<p>当使用Jetty时，你需要提供一个WebSocketServerFactory，并传递给Spring的DefaultHandshakeHandler：</p>
<pre class="crayon-plain-tag">@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(helloHandler(),"/hello")
               // 设置握手处理器
               .setHandshakeHandler(handshakeHandler());
    }

    @Bean
    public DefaultHandshakeHandler handshakeHandler() {

        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
        policy.setInputBufferSize(8192);
        policy.setIdleTimeout(600000);

        return new DefaultHandshakeHandler(
                new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
    }
}</pre>
<div class="blog_h3"><span class="graybg">Origin配置</span></div>
<p>从 Spring4.1.5开始，WebSocket/SockJS默认仅仅支持同源请求。不同策略下的行为如下：</p>
<ol>
<li>仅仅允许同源请求（默认）。在此模式下，如果启用SockJS，则IFrame的HTTP响应头X-Frame-Options被设置为SAMEORIGIN，JSONP被禁用</li>
<li>允许指定列表的源，每个源必须以http或者https开头。在此模式下，如果启用SocketJS，IFrame、JSONP两种传输都被禁用</li>
<li>设置为*。在此模式下，所有传输都可以使用</li>
</ol>
<p>修改配置的代码：</p>
<pre class="crayon-plain-tag">@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    registry.addHandler(helloHandler(), "/hello").setAllowedOrigins("*");
}</pre>
<div class="blog_h2"><span class="graybg">SockJS支持</span></div>
<p>WebSocket不受一些老旧的浏览器支持，并且某些网络代理阻止了WebSocket协议。因此Spring将SockJS作为备选实现，模拟WebSocket API。</p>
<p>要启用SockJS支持，调用：</p>
<pre class="crayon-plain-tag">@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    registry.addHandler(helloHandler(), "/hello").withSockJS();
}

// 等价XML配置 &lt;websocket:sockjs/&gt; </pre>
<div class="blog_h3"><span class="graybg">心跳消息</span></div>
<p>为了防止代理服务器认为连接已经挂起，SockJS Protocol需要发送心跳消息。Spring提供配置参数<pre class="crayon-plain-tag">.withSockJS().setHeartbeatTime( )</pre>来设置心跳频率，默认值25s。</p>
<div class="blog_h3"><span class="graybg">Servlet3异步请求</span></div>
<p>HTTP流/长轮询这两种传输，要求连接打开时间比使用它的时间更长。在Servlet容器中，这依赖于Servlet 3的异步支持实现  —— 允许请求处理线程退出，之后由其它线程继续向响应中写入数据。</p>
<p>异步请求的问题在于，服务器不知道客户端是否已经断开，只有在后续继续写入响应时，才会抛出异常。不管怎么样，心跳还是能够最终发现断开的。</p>
<div class="blog_h3"><span class="graybg">相关CORS头</span></div>
<p>如果允许跨源请求，SockJS协议依赖CORS来支持跨站HTTP流/长轮询，因此CORS头会被自动添加，除非检测到响应头中指定了对应的CORS头。</p>
<p>配置suppressCors可以禁止自动添加CORS头。</p>
<p>SockJS期望的头包括：</p>
<ol>
<li>Access-Control-Allow-Origin，基于Origin请求头初始化</li>
<li>Access-Control-Allow-Credentials总设置为true</li>
<li>Access-Control-Request-Headers从对应的请求头初始化</li>
<li>Access-Control-Allow-Methods传输机制所需要的HTTP方法</li>
<li>Access-Control-Max-Age设置为31536000（一年）</li>
</ol>
<div class="blog_h3"><span class="graybg"><a id="sockjs-java-client"></a>Java客户端</span></div>
<p>Spring实现了SockJS的Java客户端，允许你在服务器中使用SockJS，或者在压力测试中模拟大量客户端。</p>
<p>此客户端支持websocket/xhr-streaming/xhr-polling这三种传输。其中：</p>
<ol>
<li>WebSocketTransport可以连同下面的实现使用：
<ol>
<li>JSR-356的StandardWebSocketClient</li>
<li>Jetty 9的JettyWebSocketClient</li>
<li>Spring的任何WebSocketClient实现类</li>
</ol>
</li>
<li>XhrTransport有两种实现：
<ol>
<li>RestTemplateXhrTransport，基于Spring的RestTemplate</li>
<li>JettyXhrTransport，基于Jetty的HttpClient</li>
</ol>
</li>
</ol>
<p>客户端代码示例：</p>
<pre class="crayon-plain-tag">List&lt;Transport&gt; transports = new ArrayList&lt;&gt;(2);
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
transports.add(new RestTemplateXhrTransport());

SockJsClient sockJsClient = new SockJsClient(transports);
sockJsClient.doHandshake(new HelloHandler(), "ws://gmem.cc:8888/hello");</pre>
<p>当模拟大量并发客户端时，底层HTTP客户端实现应该配有足够的资源，例如：</p>
<pre class="crayon-plain-tag">HttpClient jettyHttpClient = new HttpClient();
jettyHttpClient.setMaxConnectionsPerDestination(1000);
jettyHttpClient.setExecutor(new QueuedThreadPool(1000));</pre>
<div class="blog_h2"><span class="graybg">STOMP支持</span></div>
<p>WebSocket协议定义了两种消息类型：文本和二进制数据，但是消息的内容没有定义。通常情况下，服务器和客户端能够协商使用一种子协议，来定义消息的结构，STOMP是一种常见的选择，其优势在于：</p>
<ol>
<li>浏览器中可以使用<a href="https://github.com/jmesnil/stomp-websocket">stomp.js</a></li>
<li>不需要引入新的消息格式</li>
<li>支持基于destination的消息路由</li>
<li>能够与支持STOMP的MOM集成</li>
</ol>
<div class="blog_h3"><span class="graybg">STOMP简介</span></div>
<p>STOMP是一种文本协议，最初设计供Ruby/Python/Perl之类的脚本语言使用，以连接到企业的消息代理。STOMP被设计用来处理常见的消息模式，可以基于任何双向可靠信道 —— 例如TCP、WebSocket ——传输。</p>
<p>尽管STOMP是基于文本的协议，但是它的载荷部分可以是二进制的。</p>
<p>STOMP是一种基于Frame的协议，其Frame设计理念源于HTTP。一个Frame的结构如下：</p>
<pre class="crayon-plain-tag">COMMAND
header1:value1
header2:value2

Body^@</pre>
<p>客户端可以使用SEND或者SUBSCRIBE命令，可以发送、订阅消息。此时需要指定一个destination头。下面是两个示例：</p>
<pre class="crayon-plain-tag">SUBSCRIBE
id:sub-1
destination:/topic/price.stock.*

^@ </pre><br />
<pre class="crayon-plain-tag">SEND
destination:/queue/trade
content-type:application/json
content-length:44

{"action":"BUY","ticker":"MMM","shares",44}^@</pre>
<p>STOMP服务器可以使用MESSAGE来广播消息到所有订阅者：</p>
<pre class="crayon-plain-tag">MESSAGE
message-id:nxahklf6-1
subscription:sub-1
destination:/topic/price.stock.MMM

{"ticker":"MMM","price":129.45}^@ </pre>
<p>当使用Spring的STOMP支持时，Spring的WebSocket应用相对客户端而言是STOMP代理。消息会被路由给@Controller下的消息处理方法或者一个简单内存消息代理处理。</p>
<p>你也可以配置Spring，让其与支持STOMP的消息中间件（例如RabbitMQ、ActiveMQ）一起工作，这样客户端就可以把消息发送消息中间件网络中。Spring负责维护到MOM的TCP连接、把消息中继到MOM、并且把监听到的消息下发给连接到Spring的那些客户端。</p>
<div class="blog_h3"><span class="graybg">启用STOMP</span></div>
<p>利用spring-messaging和spring-websocket模块，Spring能够支持STOMP over WS。</p>
<p>配置示例：</p>
<pre class="crayon-plain-tag">@Configuration
// 启用基于WebSocket的消息代理（使用某种子协议）
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 在/stomp暴露一个基于WebSocket/SockJS的STOMP端点
        registry.addEndpoint("/stomp").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {

        // 如果destination以/app开头，则消息路由给@Controller下的消息处理方法
        config.setApplicationDestinationPrefixes("/app");

        // 所有destination均由@Controller下的消息@MessageMapping方法处理
        config.setApplicationDestinationPrefixes("/");

        // 下面的两种开头的destination广播给所有其它客户端
        config.enableSimpleBroker("/topic", "/queue");
    }

}

@Controller
@MessageMapping("greeting")
public class GreetingController {
    @Inject 
    private SimpMessagingTemplate template; // 用于发送消息
    // 此消息处理方法处理/app/greeting/hello这一目标
    @MessageMapping("hello") {
    public String hello(String greeting) {
        String msg =  "[" + getTimestamp() + ": " + greeting;
        // 可以向任何地方发送消息
        this.template.convertAndSend("/topic/greetings", msg);
    }
}</pre>
<p>等价XML配置：</p>
<pre class="crayon-plain-tag">&lt;websocket:message-broker application-destination-prefix="/app"&gt;
    &lt;websocket:stomp-endpoint path="/stomp"&gt;
        &lt;websocket:sockjs/&gt;
    &lt;/websocket:stomp-endpoint&gt;
    &lt;websocket:simple-broker prefix="/topic, /queue"/&gt;
&lt;/websocket:message-broker&gt;</pre>
<p>消息目的地，默认的路径分隔好符是  / ，客户端<span style="background-color: #c0c0c0;">发送时，目的地必须以 / 为第一个字符</span>。除非包含多个路径分段，@MessageMapping的路径不需要包含 / 。</p>
<p>如果要使用MOM领域更加通用的点号分隔符，调用：</p>
<pre class="crayon-plain-tag">registry.setPathMatcher(new AntPathMatcher("."));</pre>
<p>等价的XML配置为：</p>
<pre class="crayon-plain-tag">&lt;websocket:message-broker application-destination-prefix="/app" path-matcher="pathMatcher"&gt;
&lt;/websocket:message-broker&gt;
&lt;bean id="pathMatcher" class="org.springframework.util.AntPathMatcher"&gt;
    &lt;constructor-arg index="0" value="." /&gt;
&lt;/bean&gt;</pre>
<p>即使使用点号分隔符，客户端发送的目的地，也要以 / 开头。 </p>
<div class="blog_h3"><span class="graybg">JS客户端</span></div>
<pre class="crayon-plain-tag">// 可以使用SockJS：
var socket = new SockJS("/stomp");
var client = Stomp.over(socket); 
// 或者直接使用WebSocket
var client = Stomp.over( new WebSocket( 'ws://172.21.0.1:9090/signal' ) );

// 心跳设置
client.heartbeat.outgoing = 20000;   // 每20秒发送一次心跳给服务器
client.heartbeat.incoming = 0;       // 不接受服务器发送来的心跳

// 调试设置
client.debug = function(str) {
    console.log(str);
};

// 连接
client.connect(login, passcode, connectCallback, errorCallback);
client.connect(headers, connectCallback, errorCallback);
function connectCallback( frame ){
}

// 发送消息，目的地、头、体
client.send("/queue/hello", {priority: 9}, "Hello, STOMP");

// 订阅消息
var subscription = client.subscribe("/topic/hello", callback);
function callback( message ){
    console.log( message.body );
}

// 带消息确认设置的订阅：客户端确认
var subscription = client.subscribe("/topic/hello", callback, {ack: 'client'});
function callback( message ){
    // 确认
    message.ack();
}

// 事务支持
var tx = client.begin();
// transaction头必须
client.send("/queue/hello", {transaction: tx.id}, "message in a transaction");
tx.commit();  // 提交事务
tx.abort();   // 撤销事务</pre>
<p>或者直接使用WebSocket：</p>
<pre class="crayon-plain-tag">var socket = new WebSocket("/stomp");
var client = Stomp.over(socket);</pre>
<div class="blog_h3"><span class="graybg">Java客户端</span></div>
<pre class="crayon-plain-tag">WebSocketClient webSocketClient = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
stompClient.setMessageConverter(new StringMessageConverter());
stompClient.setTaskScheduler(taskScheduler); // 用于发送心跳

// 创建连接
String url = "ws://127.0.0.1:8080/endpoint";
StompSessionHandler sessionHandler = new StompSessionHandlerImpl();
class StompSessionHandlerImpl extends StompSessionHandlerAdapter {
    public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
        // 连接成功后，此回调被调用
    }
}
stompClient.connect(url, sessionHandler);

// 发送消息
session.send("/topic/foo", "payload");
// 订阅消息
session.subscribe("/topic/foo", new StompFrameHandler() {
    public Type getPayloadType(StompHeaders headers) {
        return String.class;
    }
    public void handleFrame(StompHeaders headers, Object payload) {
        // 处理消息
    }
}); </pre>
<div class="blog_h3"><span class="graybg">消息流</span></div>
<p>spring-messaging提供了以下抽象：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">对象</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Message</td>
<td>一个带有头、载荷的消息</td>
</tr>
<tr>
<td>MessageHandler </td>
<td>处理消息的逻辑单元</td>
</tr>
<tr>
<td>MessageChannel </td>
<td>在发送者/接收者之间传输消息的信道的抽象，通道总是单向的</td>
</tr>
<tr>
<td>SubscribableChannel </td>
<td>继承MessageChannel，用于传输消息到所有订阅者</td>
</tr>
<tr>
<td>ExecutorSubscribableChannel</td>
<td>继承SubscribableChannel，使用异步线程池传输消息</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">基于注解的消息处理 </span></div>
<p>你可以在@Controller类的方法上添加@MessageMapping注解，这类方法可以映射某个/某些消息destination。</p>
<p>@MessageMapping对应的URL支持Ant风格的通配符，例如/foo*、/foo/**。路径变量也是支持的，例如/foo/{id}中的id可以通过注解了@DestinationVariable的方法参数访问到。</p>
<p>你可以为@MessageMapping方法注入很多种参数：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Message</td>
<td>访问完整的消息</td>
</tr>
<tr>
<td>@Payload</td>
<td>访问消息的载荷，消息被基于org.springframework.messaging.converter.MessageConverter转换</td>
</tr>
<tr>
<td>@Header</td>
<td>访问消息头</td>
</tr>
<tr>
<td>@Headers</td>
<td>访问所有消息头的Map</td>
</tr>
<tr>
<td>@DestinationVariable</td>
<td>访问路径变量</td>
</tr>
<tr>
<td>Principal</td>
<td>在WS握手阶段登陆的用户</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">身份验证</span></div>
<p>使用STOMP时，身份验证基于HTTP协议的机制进行。</p>
<p>尽管STOMP协议包含login、passcode头，但是它们通常在STOMP over TCP的情况下使用。Spring默认会忽略这些头，并且假设在HTTP升级到WebSocket之前已经完成身份验证。</p>
<p>如果需要基于STOMP头进行身份验证，可以进行如下配置：</p>
<pre class="crayon-plain-tag">@Configuration
@EnableWebSocketMessageBroker
public class AppConfig extends AbstractWebSocketMessageBrokerConfigurer {
  @Override
  public void configureClientInboundChannel(ChannelRegistration registration) {
    registration.setInterceptors(new ChannelInterceptorAdapter() {
        @Override
        public Message&lt;?&gt; preSend(Message&lt;?&gt; message, MessageChannel channel) {
            StompHeaderAccessor accessor =  MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
            if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                String login = accessor.getNativeHeader( "login" ).get( 0 );
                Principal user = new PrincipalImpl( login );
                accessor.setUser(user);
            }
            return message;
        }
    });
  }
}</pre>
<p>注意，不进行任何配置的情况下，你不能为@MessageMapping方法注入Principal对象，执行了上述配置则可以注入。其它备选的身份验证方式包括：</p>
<ol>
<li>子类化DefaultHandshakeHandler，覆盖determineUser方法，这样可以在WebSocket握手阶段确定用户。示例：<br />
<pre class="crayon-plain-tag">registry.addEndpoint( "/signal" ).setHandshakeHandler( new DefaultHandshakeHandler() {
    @Override
    protected Principal determineUser( ServerHttpRequest request, WebSocketHandler wsHandler, Map&lt;String, Object&gt; attributes ) {
        Principal principal = request.getPrincipal();
        if ( principal == null ) {
            Collection&lt;SimpleGrantedAuthority&gt; authorities = new ArrayList&lt;&gt;();
            authorities.add( new SimpleGrantedAuthority( AuthoritiesConstants.ANONYMOUS ) );
            principal = new AnonymousAuthenticationToken( "WebsocketConfiguration", "anonymous", authorities );
        }
        return principal;
    }
} );</pre>
</li>
<li>使用基于HTTP的身份验证，Spring会尝试从HttpServletRequest.getUserPrincipal中获得当前用户</li>
</ol>
<div class="blog_h3"><span class="graybg">用户目的地</span></div>
<p>默认情况下，Spring认为<pre class="crayon-plain-tag">/user/</pre>开头的目的地属于用户目的地，每个WebSocket会话都有这种目的地的同名副本。</p>
<p>客户端代码示例：</p>
<pre class="crayon-plain-tag">let client = Stomp.over( new WebSocket( 'ws://172.21.0.1:9090/signal' ) );
client.connect( {}, ( frame ) =&gt; {
    start();
} );
function start() {
    // 客户端订阅用户目的地，需要/user前缀
    client.subscribe( '/user/rtsp/preview/sdpanswer', function ( frame ) {
        console.log( frame.body );
    } );
    client.send( '/app/rtsp/preview/sdpoffer', {}, '1' );
}</pre>
<p>服务器代码示例：</p>
<pre class="crayon-plain-tag">@Controller
@MessageMapping( "/rtsp/preview" )
public class RtspPreviewController {
    @MessageMapping( "/sdpoffer" )
    // 发送到用户目的地（仅仅发送给当前WebSocket会话对应的客户端），需要指定完整路径，/user前缀不需要
    @SendToUser( "/rtsp/preview/sdpanswer" )
    public String connect( String payload ) {
        return payload;
    }
}</pre>
<p>关于@SendToUser需要注意，实际发送到的目的地是/user/{username}/rtsp/preview，Spring按照以下规则确定username：</p>
<ol>
<li>如果当前会话的Principal存在，则取Principal.getName()作为用户名</li>
<li>否则，取会话标识符，会话标识符来自消息头中的simpSessionId字段</li>
</ol>
<p>当允许同一个用户在多个浏览器中登陆时，要注意这个情况，如果Principal存放登陆名，客户端可能接收到不期望的消息。</p>
<div class="blog_h3"><span class="graybg">混合使用STOMP和原始WebSocket</span></div>
<p>配置示例：</p>
<pre class="crayon-plain-tag">@SpringBootApplication
// 两个注解都需要：
@EnableWebSocket
@EnableWebSocketMessageBroker
public class VideoSurveillanceApp extends AbstractWebSocketMessageBrokerConfigurer implements WebSocketConfigurer {
    public void registerWebSocketHandlers( WebSocketHandlerRegistry registry ) {
        // 下面的端点使用原始WebSocket
        registry.addHandler( helloHandler(), "/hello" );
    }
    public void registerStompEndpoints( StompEndpointRegistry registry ) {
        // 下面的端点使用STOMP
        registry.addEndpoint( "/signal" );
    }
}</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/ws-support-of-spring">Spring对WebSocket的支持</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/ws-support-of-spring/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Spring对JMS的支持</title>
		<link>https://blog.gmem.cc/jms-support-of-spring</link>
		<comments>https://blog.gmem.cc/jms-support-of-spring#comments</comments>
		<pubDate>Fri, 07 Aug 2015 09:28:05 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[ActiveMQ]]></category>
		<category><![CDATA[JMS]]></category>
		<category><![CDATA[Spring]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=15268</guid>
		<description><![CDATA[<p>简介 Spring 提供了JMS的集成，简化JMS的使用，提供的API封装类似于Spring的JDBC集成。 JMS的功能大体上分为两类——接收、发送消息。Spring提供了： JmsTemplate来完成消息的发送、同步接收 消息监听器容器（message listener containers）来异步的接收消息（事件驱动） 使用Spring JMS JmsTemplate JmsTemplate是Spring的jms-core包的核心类，它封装了资源的创建、释放部分，简化收发消息的代码。 JmsTemplate类是线程安全的，除非需要不同的收发配置（QoS），整个系统可以仅仅使用单例的JmsTemplaet。 基本API 发送消息时，你可以提供一个MessageCreator回调来实现消息发送： [crayon-69e945c70e510538576000/] 如果需要使用更加复杂的JMS API，可以调用下面的方法： [crayon-69e945c70e514281462508/] JMS API中有很多QoS设置项，例如优先级、TTL，这些都暴露为JmsTemplate的属性。你可以根据业务需要创建多个JmsTemplate。某些JMS实现在ConnectionFactory级别管理QoS设置，要明确指定使用JmsTemplate上的QoS选项，需要调用[crayon-69e945c70e516310989771-i/]  JmsTemplate集成了简单请求/应答模式的支持： <a class="read-more" href="https://blog.gmem.cc/jms-support-of-spring">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/jms-support-of-spring">Spring对JMS的支持</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>Spring 提供了JMS的集成，简化JMS的使用，提供的API封装类似于Spring的JDBC集成。</p>
<p>JMS的功能大体上分为两类——接收、发送消息。Spring提供了：</p>
<ol>
<li>JmsTemplate来完成消息的发送、同步接收</li>
<li>消息监听器容器（message listener containers）来异步的接收消息（事件驱动）</li>
</ol>
<div class="blog_h1"><span class="graybg">使用Spring JMS</span></div>
<div class="blog_h2"><span class="graybg">JmsTemplate</span></div>
<p>JmsTemplate是Spring的jms-core包的核心类，它封装了资源的创建、释放部分，简化收发消息的代码。</p>
<p>JmsTemplate类是线程安全的，除非需要不同的收发配置（QoS），整个系统可以仅仅使用单例的JmsTemplaet。</p>
<div class="blog_h3"><span class="graybg">基本API</span></div>
<p>发送消息时，你可以提供一个MessageCreator回调来实现消息发送：</p>
<pre class="crayon-plain-tag">jmsTemplate.send( queueName, new MessageCreator() {
    @Override
    public Message createMessage( Session session ) throws JMSException {
        return session.createTextMessage( ... );
    }
} );</pre>
<p>如果需要使用更加复杂的JMS API，可以调用下面的方法：</p>
<pre class="crayon-plain-tag">jmsTemplate.execute( new SessionCallback&lt;Object&gt;() {
    public Object doInJms( Session session ) throws JMSException {
        return null;
    }
} );
jmsTemplate.execute( new ProducerCallback&lt;Object&gt;() {
    public Object doInJms( Session session, MessageProducer producer ) throws JMSException {
        return null;
    }
} );</pre>
<p>JMS API中有很多QoS设置项，例如优先级、TTL，这些都暴露为JmsTemplate的属性。你可以根据业务需要创建多个JmsTemplate。某些JMS实现在ConnectionFactory级别管理QoS设置，要明确指定使用JmsTemplate上的QoS选项，需要调用<pre class="crayon-plain-tag">jmsTemplate.setExplicitQosEnabled( true )</pre> </p>
<p>JmsTemplate集成了简单请求/应答模式的支持：</p>
<pre class="crayon-plain-tag">Message response = jmsTemplate.sendAndReceive( session -&gt; {
    Message msg = session.createTextMessage();
    msg.setJMSReplyTo( "临时答复队列名称" );
    return msg;
} );</pre>
<div class="blog_h2"><span class="graybg">连接</span></div>
<p>你需要为JmsTemplate提供一个ConnectionFactory的引用，后者属于JMS规范的一部分，任何JMS提供商都需要实现。ConnectionFactory是创建到MOM中间件连接的工厂。</p>
<div class="blog_h3"><span class="graybg">资源缓存</span></div>
<p>标准的JMS API，在收发消息时需要使用很多中间对象：ConnectionFactory ⇨ Connection ⇨ Session ⇨ MessageProducer ⇨ send()。这些中间对象如果反复创建、销毁，会影响性能。Spring提供了一些具有缓存能力的ConnectionFactory实现：</p>
<table class="full-width fixed-word-wrap">
<tbody>
<tr>
<td><em><strong>SingleConnectionFactory</strong></em><br />在每次被调用createConnection()时，总是返回同一个连接，并且忽略对close()的调用</td>
</tr>
<tr>
<td>
<p><strong><em>CachingConnectionFactory</em></strong></p>
<p>在SingleConnectionFactory的基础上，增加了Session、MessageProducer、MessageConsumer缓存的能力。默认缓存大小1</p>
<p>设置sessionCacheSize可以增加Session的缓存数量。由于会话是基于不同签收模式（acknowledgment mode）来缓存的，因此实际缓存的Session数量可能多于声明的数量，sessionCacheSize设置为1时最多缓存4个（对应4种签收模式）</p>
<p>MessageProducer、MessageConsumer连同它们所属的Session一起缓存。在缓存时考虑其特别设置的属性。MessageProducer基于destination缓存。MessageConsumer基于destination、selector、noLocal递送标记、持久化订阅名称来缓存</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">消息监听容器</span></div>
<p>Spring支持类似于EJB的消息驱动Bean的功能，该功能由消息监听容器实现，你可以用XML配置或者注解驱动的方式，指定POJO的方法会自动被调用以完成消息的处理。</p>
<p>消息监听容器负责从JMS队列/主题监听消息，并负责管理多线程的消息消费、事务管理、资源获取/是否。</p>
<p>消息监听容器的实现类主要有：</p>
<table class="full-width fixed-word-wrap">
<tbody>
<tr>
<td>
<p><strong><em>SimpleMessageListenerContainer</em></strong></p>
<p>在启动时创建固定数量的Session、Consumer，使用JMS标准API MessageConsumer.setMessageListener()来注册监听器，由JMS实现来执行回调</p>
<p>支持JMS原生事务 —— 切换sessionTransacted标记或者设置acknowledge为acknowledge，你的回调抛出的异常会导致回滚</p>
<p>不支持外部管理的事务，兼容性较好</p>
</td>
</tr>
<tr>
<td>
<p><em><strong>DefaultMessageListenerContainer</strong></em> </p>
<p>基于轮询方式实现。支持外部管理事务，当使用JtaTransactionManager时，每个接收到的消息都注册到XA事务，与JEE环境兼容</p>
<p>容器的缓存级别可以被定制，如果不启用缓存，每当接收一个连接时都会创建Collection、Session。不启用缓存 + 非持久化订阅可能导致在高负载时丢失消息</p>
<p>该容器还能够支持消息代理宕机重启后，自动恢复自己的功能。默认的，它使用一个简单的BackOff实现，每5秒重试连接到代理，你可以指定自己的BackOff实现</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">XML配置示例</span></div>
<pre class="crayon-plain-tag">&lt;!--
  container-type = default表示DefaultMessageListenerContainer
  concurrency = 1 表示每一个jms:listener对应的Session/Consumer个数（也就是线程数？）
  destination-type = durableTopic 表示目标是持久化订阅，这种情况下 client-id + subscription是必须的
  cache为缓存方式：
      none：不缓存
      connection：为每个监听器线程缓存Connection对象
      session：为每个监听器线程缓存Connection、Session对象
      consumer：为每个监听器线程缓存Connection、Session、Consumer对象
      auto：默认值，通常取值consumer，但是指定了外部事务管理器时，取值none
      transaction-manager：事务管理器
--&gt;
&lt;jms:listener-container container-type="default" connection-factory="cf" acknowledge="auto" concurrency="1"
                        destination-type="durableTopic" client-id="APP" cache="auto"&gt;
    &lt;jms:listener destination="监听的主题" ref="你提供的回调Bean" method="你提供的回调方法" subscription="持久化订阅名称"/&gt;
&lt;/jms:listener-container&gt;

&lt;!-- 不使用JMS名字空间时的类似配置 --&gt;
&lt;bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"&gt;
    &lt;property name="messageListener" ref="messageListener" /&gt;
&lt;/bean&gt;
&lt;bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter"&gt;
    &lt;constructor-arg&gt;
        &lt;bean class="你提供的回调Bean"/&gt;
    &lt;/constructor-arg&gt;
    &lt;property name="defaultListenerMethod" value="你提供的回调方法"/&gt;
    &lt;!-- 禁止消息类型转换 --&gt;
    &lt;property name="messageConverter"&gt;
        &lt;null/&gt;
    &lt;/property&gt;
&lt;/bean&gt;</pre>
<div class="blog_h3"><span class="graybg">注解配置示例</span></div>
<p>编程式配置DMLC：</p>
<pre class="crayon-plain-tag">@Configuration
@EnableJms
public class AppConfig {

    @Bean
    public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
        DefaultJmsListenerContainerFactory factory =
                new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory());
        factory.setDestinationResolver(destinationResolver());
        factory.setConcurrency("3-10");
        return factory;
    }
}</pre>
<p>监听器配置：</p>
<pre class="crayon-plain-tag">@Component
public class MyService {
    // 使用@Header注入单个消息头、使用@Headers注入所有消息头，接收参数必须为Map
    // 使用@Payload可以明确指定接收载荷的参数
    // Message、Session可以被注入
    @JmsListener(destination = "queue")
    public void processOrder(String data, @Header("ordertype") String orderType) {
    }
    
    
    @JmsListener(destination = "myDestination")
    @SendTo("status") // 可以指定应答队列
    public OrderStatus processOrder(Order order) {
        return status;
    }
} </pre>
<div class="blog_h3"><span class="graybg">MessageListenerAdapter</span></div>
<p>这个组件允许你把任意POJO作为MDP（消息驱动POJO）使用，而不需要实现MessageListener接口：</p>
<pre class="crayon-plain-tag">public class MessageListenerAdapter implements MessageListener, SessionAwareMessageListener&lt;Message&gt;{
    public MessageListenerAdapter(Object delegate) {
         setDelegate(delegate);
    }
}</pre>
<p>构造方法参数delegate可以是任何对象， MessageListenerAdapter假设它实现以下接口：</p>
<pre class="crayon-plain-tag">public interface MessageDelegate {
    // 根据消息类型的不同，自动选择调用的方法
    void handleMessage(String message);
    void handleMessage(Map message);
    void handleMessage(byte[] message);
    void handleMessage(Serializable message);
}</pre>
<p>delegate也可以提供其它形式的方法，但是需要指定defaultListenerMethod配置。 </p>
<div class="blog_h3"><span class="graybg">回调方法</span></div>
<p>回调方法可以具有参数，对应接收到的消息，也<span style="background-color: #c0c0c0;">可以具有返回值</span>，对应发送到原始消息Reply-To字段指定的，或者listener默认配置的应答队列的应答消息。</p>
<p>参数类型可以是各种各样的，但是需要messageConverter的配合。</p>
<div class="blog_h2"><span class="graybg">事务管理</span></div>
<p>Spring提供了针对单个ConnectionFactory的JmsTransactionManager，事务资源是绑定到线程的。JmsTransactionManager会自动检测到事务资源并打开之。</p>
<p>在JEE环境下，ConnectionFactory可能对Connection、Session进行缓存，资源因而是跨事务重用的。在独立运行环境下，使用Spring的SingleConnectionFactory会导致Connection共享，而每个事务使用独立的Session。</p>
<p>JmsTemplate也可以使用JtaTransactionManager + 支持XA的ConnectionFactory，以支持分布式事务。</p>
<p>在独立运行环境下，你需要设置JmsTemplate的sessionAcknowledgeMode、sessionTransacted来告知Spring是否使用Jms事务。当联用PlatformTransactionManager、JmsTemplate时，JmsTemplate总是提供事务性的Session对象</p>
<div class="blog_h2"><span class="graybg">消息转换</span></div>
<p>JmsTemplate的<pre class="crayon-plain-tag">convertAndSend()</pre>、 <pre class="crayon-plain-tag">receiveAndConvert()</pre>可以在收发消息时完成数据类型转换。 转换工作代理给了MessageConverter类，缺省实现支持String  ⇨ TextMessage、byte[] ⇨ BytesMesssage、java.util.Map ⇨ MapMessage之间的转换。</p>
<p>如果要对转换后，发送前对JMS消息对象进行处理，可以使用MessagePostProcessor：</p>
<pre class="crayon-plain-tag">jmsTemplate.convertAndSend( queue, map, new MessagePostProcessor() {
    public Message postProcessMessage( Message message ) throws JMSException {
        message.setIntProperty( "AccountID", 1000 );
        message.setJMSCorrelationID( "123-00001" );
        return message;
    }
} );</pre>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">DMLC无法接收消息</span></div>
<p>通过JMX查看ActiveMQ队列状态，发现消息已经被消费。打开WARN级别的Spring日志，发现报方法找不到。配置的回调方法：</p>
<pre class="crayon-plain-tag">public void onP2PMessage( Message message ) throws Exception {
}</pre>
<p>此问题的原因是，回调方法的参数必须是JMS消息的载荷。因此，对于TextMessage，回调参数类型应该是String。 </p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/jms-support-of-spring">Spring对JMS的支持</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/jms-support-of-spring/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Spring配置：集成Hibernate、JacksonJSON、AspectJ等框架</title>
		<link>https://blog.gmem.cc/spring-config-with-hibernate-jacksonjson-aspectj</link>
		<comments>https://blog.gmem.cc/spring-config-with-hibernate-jacksonjson-aspectj#comments</comments>
		<pubDate>Mon, 26 Nov 2012 08:39:24 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[AspectJ]]></category>
		<category><![CDATA[Cache]]></category>
		<category><![CDATA[Hibernate]]></category>
		<category><![CDATA[JacksonJSON]]></category>
		<category><![CDATA[Log4jdbc]]></category>
		<category><![CDATA[Scheduler]]></category>
		<category><![CDATA[Spring]]></category>
		<category><![CDATA[配置文件]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1110</guid>
		<description><![CDATA[<p>本文提及的该套配置文件，覆盖了JavaEE项目开发的最常见需求，包括：依赖注入、事务控制、AOP、任务调度、缓存、MVC框架等方面的内容。 容易改变的配置项独立到属性文件中： [crayon-69e945c70e9dd846865661/] Spring配置文件部分： [crayon-69e945c70e9e0202347443/] Spring MVC配置文件部分： [crayon-69e945c70ea07787034244/] ehcache配置部分： [crayon-69e945c70ea0a935511916/] Web.xml配置（使用了Spring的JavaConfig）： [crayon-69e945c70ea0c060043280/] Java Config类： [crayon-69e945c70ea0f668616961/] [crayon-69e945c70ea11761900382/] Maven依赖包列表： [crayon-69e945c70ea13031396053/] Maven插件配置（支持AspectJ）： [crayon-69e945c70ea17909430192/]</p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/spring-config-with-hibernate-jacksonjson-aspectj">Spring配置：集成Hibernate、JacksonJSON、AspectJ等框架</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both">本文提及的该套配置文件，覆盖了JavaEE项目开发的最常见需求，包括：依赖注入、事务控制、AOP、任务调度、缓存、MVC框架等方面的内容。<br />
容易改变的配置项独立到属性文件中：<br />
<pre class="crayon-plain-tag">hibernateDialect=org.hibernate.dialect.MySQL5Dialect
#启用了log4jdbc支持
jdbcDriver=net.sf.log4jdbc.DriverSpy
jdbcDriverUrl=jdbc:log4jdbc:mysql://192.168.0.201:3306/initpemsdb1.1.1
jdbcUserName=root
jdbcPassword=root</pre><br />
Spring配置文件部分：<br />
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans 
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:util="http://www.springframework.org/schema/util" 
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xmlns:task="http://www.springframework.org/schema/task" 
    xmlns:cache="http://www.springframework.org/schema/cache"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-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/task  http://www.springframework.org/schema/task/spring-task-3.0.xsd
        http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.1.xsd
    "&gt;
    &lt;!-- 定义属性，在后面的配置中可以使用#{appCfg.xxx}引用 --&gt;
    &lt;util:properties id="appCfg" location="classpath:applicationConfig.properties" /&gt;
    &lt;!-- 包扫描配置 --&gt;
    &lt;context:component-scan base-package="cc.gmem.demo"&gt;
        &lt;!-- 去除Java Config类 --&gt;
        &lt;context:exclude-filter type="annotation" expression="org.springframework.context.annotation.Configuration" /&gt;
        &lt;!-- 去除Spring MVC类 --&gt;
        &lt;context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" /&gt;
        &lt;!-- 匹配AspectJ Pattern的类被去除 --&gt;
        &lt;context:exclude-filter type="aspectj" expression="cc.gmem.demo.aop.web.*" /&gt;
    &lt;/context:component-scan&gt;
    &lt;!-- 启用注解支持 --&gt;
    &lt;context:annotation-config /&gt;
    &lt;!-- 支持@Configurable注解 --&gt;
    &lt;context:spring-configured /&gt;
    &lt;!--  启用注解方式的事务：基于AspectJ织入 --&gt;
    &lt;tx:annotation-driven transaction-manager="txManager" mode="aspectj" /&gt;
    &lt;!--  启用注解方式的缓存配置：基于AspectJ织入 --&gt;
    &lt;cache:annotation-driven cache-manager="cacheManager" mode="aspectj" /&gt;
    &lt;!--  启用注解方式的任务执行(例如@Async)：基于AspectJ织入 --&gt;
    &lt;task:annotation-driven executor="taskExecutor" mode="aspectj" /&gt;
    
    &lt;!--  任务调度器配置，pool-size为线程池大小，限制了同时最多被调度的任务 --&gt;
    &lt;task:scheduler pool-size="100" id="scheduler" /&gt;
    &lt;!-- 任务计划列表，支持固定频率、固定延迟、Cron表达式等 --&gt;
    &lt;task:scheduled-tasks scheduler="scheduler"&gt;
        &lt;task:scheduled fixed-rate="120000" method="run" ref="hibernateStatisticsMonitor" /&gt;
    &lt;/task:scheduled-tasks&gt;
    &lt;!-- 任务执行器，用于异步执行任务 --&gt;
    &lt;task:executor id="taskExecutor" keep-alive="3600" pool-size="10-100" queue-capacity="10000" rejection-policy="CALLER_RUNS" /&gt;
    &lt;!-- 引入其它Spring配置文件 --&gt;
    &lt;import resource="report-and-etl.xml" /&gt;  
    &lt;!-- Hibernate配置  --&gt;
    &lt;bean id="dataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource"&gt;
        &lt;property name="alias" value="connectionPool" /&gt;
        &lt;property name="driver" value="#{appCfg.jdbcDriver}" /&gt;
        &lt;property name="driverUrl" value="#{appCfg.jdbcDriverUrl}" /&gt;
        &lt;property name="user" value="#{appCfg.jdbcUserName}" /&gt;
        &lt;property name="password" value="#{appCfg.jdbcPassword}" /&gt;
        &lt;property name="statistics" value="10s" /&gt;
        &lt;property name="minimumConnectionCount" value="10" /&gt;
        &lt;property name="maximumConnectionCount" value="100" /&gt;
        &lt;property name="simultaneousBuildThrottle" value="10" /&gt;
        &lt;property name="maximumActiveTime" value="3600000" /&gt;
    &lt;/bean&gt;
    &lt;bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" autowire="byName" /&gt;
    &lt;bean id="nativeJdbcExtractor" class="org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor" lazy-init="true" /&gt;
    &lt;bean id="sessionFactory" p:dataSource-ref="dataSource" class="cc.gmem.demo.hibernate.AnnotationSessionFactoryBean" depends-on="appCfg" &gt;
        &lt;property name="packagesToScan"&gt;
            &lt;list&gt;
                &lt;value&gt;cc.gmem.demo.**.domain&lt;/value&gt;
            &lt;/list&gt;
        &lt;/property&gt;
        &lt;property name="hibernateProperties"&gt;
            &lt;value&gt;
                hibernate.dialect=#{appCfg.hibernateDialect}
                hibernate.jdbc.batch_size=30
                hibernate.show_sql=false
                hibernate.format_sql=true
                hibernate.generate_statistics=true
                hibernate.transaction.factory_class=org.hibernate.transaction.JDBCTransactionFactory
                hibernate.current_session_context_class=org.springframework.orm.hibernate3.SpringSessionContext
                hibernate.query.factory_class=org.hibernate.hql.ast.ASTQueryTranslatorFactory
                hibernate.hbm2ddl.auto=update
                #启用二级缓存
                hibernate.cache.use_second_level_cache=true
                hibernate.cache.use_query_cache=true
                hibernate.cache.region.factory_class=org.hibernate.cache.SingletonEhCacheRegionFactory
            &lt;/value&gt;
        &lt;/property&gt;
    &lt;/bean&gt;
    &lt;!-- Spring缓存配置 --&gt;
    &lt;bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"&gt;
        &lt;property name="cacheManager" ref="ehcache" /&gt;
    &lt;/bean&gt;
    &lt;bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"&gt;
        &lt;property name="configLocation"&gt;
            &lt;value&gt;classpath:ehcache.xml&lt;/value&gt;
        &lt;/property&gt;
        &lt;property name="shared" value="true" /&gt;
    &lt;/bean&gt;
    &lt;bean id="hibernateStatisticsMonitor" class="cc.gmem.demo.hibernate.HibernateStatisticsMonitor" /&gt;
    &lt;bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" p:sessionFactory-ref="sessionFactory" /&gt;
    &lt;!-- Velocity引擎配置 --&gt;
    &lt;bean id="velocityEngine" class="org.apache.velocity.app.VelocityEngine"&gt;
        &lt;constructor-arg type="java.util.Properties"&gt;
            &lt;value&gt;
                &lt;![CDATA[
                #{T(org.apache.velocity.runtime.RuntimeConstants).RUNTIME_LOG_LOGSYSTEM_CLASS}=org.apache.velocity.runtime.log.Log4JLogChute
                runtime.log.logsystem.log4j.logger=velocityLogger
                #{T(org.apache.velocity.runtime.RuntimeConstants).INPUT_ENCODING}=UTF-8
                #{T(org.apache.velocity.runtime.RuntimeConstants).OUTPUT_ENCODING}=UTF-8
                #{T(org.apache.velocity.runtime.RuntimeConstants).VM_PERM_ALLOW_INLINE}=true
                #{T(org.apache.velocity.runtime.RuntimeConstants).RESOURCE_LOADER}=class
                #{T(org.apache.velocity.runtime.RuntimeConstants).PARSER_POOL_SIZE}=100
                class.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
                ]]&gt;
            &lt;/value&gt;
        &lt;/constructor-arg&gt;
    &lt;/bean&gt;
    &lt;!--国际化支持--&gt;
    &lt;bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"&gt;
         &lt;property name="basenames"&gt;
             &lt;list&gt;
                 &lt;value&gt;classpath:message.gmemdemo&lt;/value&gt;
             &lt;/list&gt;
         &lt;/property&gt;
         &lt;property name="defaultEncoding" value="UTF-8" /&gt;
         &lt;property name="cacheSeconds" value="5" /&gt;
    &lt;/bean&gt;
    &lt;!--Jackson JSON --&gt;
    &lt;bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" autowire="byType" /&gt;
    &lt;!-- Spring类型转换服务，特别是用于时间日期格式转换 --&gt;
    &lt;bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"&gt;
        &lt;property name="converters"&gt;
            &lt;set&gt;
                &lt;bean class="net.greenmemory.spring.converter.DateTimeConvertor"&gt;
                    &lt;constructor-arg&gt;
                        &lt;array&gt;
                            &lt;value&gt;yyyy-MM-dd&lt;/value&gt;
                            &lt;value&gt;yyyy-MM-dd HH:mm:ss&lt;/value&gt;
                            &lt;value&gt;yyyy-MM-dd HH:mm:ss.SSS&lt;/value&gt;
                       &lt;/array&gt;
                    &lt;/constructor-arg&gt;
                &lt;/bean&gt;
            &lt;/set&gt;
       &lt;/property&gt;
       &lt;property name="formatters"&gt;
           &lt;set&gt;
               &lt;bean class="org.springframework.format.datetime.DateFormatter"&gt;
                   &lt;constructor-arg&gt;
                       &lt;value&gt;yyyy-MM-dd&lt;/value&gt;
                   &lt;/constructor-arg&gt;
               &lt;/bean&gt;
           &lt;/set&gt;
       &lt;/property&gt;
       &lt;property name="formatterRegistrars"&gt;
           &lt;set&gt;
           &lt;/set&gt;
       &lt;/property&gt;
    &lt;/bean&gt;
&lt;/beans&gt;</pre><br />
Spring MVC配置文件部分：<br />
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
    "&gt;
    &lt;context:component-scan base-package="cc.gmem.demo.**.ctrl"&gt;
        &lt;context:exclude-filter type="annotation" expression="org.springframework.context.annotation.Configuration" /&gt;
    &lt;/context:component-scan&gt;
    &lt;context:component-scan base-package="cc.gmem.demo.aop.web" /&gt;

    &lt;context:annotation-config /&gt;

    &lt;mvc:annotation-driven&gt;
        &lt;mvc:message-converters&gt;
            &lt;bean class="org.springframework.http.converter.json.jacksonjson.MappingJackson2HttpMessageConverter"&gt;
                &lt;property name="objectMapper" ref="objectMapper" /&gt;
            &lt;/bean&gt;
        &lt;/mvc:message-converters&gt;
    &lt;/mvc:annotation-driven&gt;

    &lt;mvc:interceptors&gt;
        &lt;bean id="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor"&gt;
            &lt;property name="sessionFactory" ref="sessionFactory" /&gt;
        &lt;/bean&gt;
            &lt;mvc:interceptor&gt;     
            &lt;mvc:mapping path="/home/*" /&gt; 
            &lt;!-- 自定义MVC拦截器 --&gt;
            &lt;bean class="cc.gmem.demo.spring.LogonInterceptor" /&gt; 
        &lt;/mvc:interceptor&gt;  
        
    &lt;/mvc:interceptors&gt;

    &lt;bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"&gt;
        &lt;property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /&gt;
        &lt;property name="prefix" value="/WEB-INF/jsp/" /&gt;
        &lt;property name="suffix" value=".jsp" /&gt;
        &lt;property name="order" value="0" /&gt;
    &lt;/bean&gt;

    &lt;mvc:resources mapping="/css/**" location="/css/" /&gt;
    &lt;mvc:resources mapping="/icons/**" location="/icons/" /&gt;
    &lt;mvc:resources mapping="/images/**" location="/images/" /&gt;
    &lt;mvc:resources mapping="/themes/**" location="/themes/" /&gt;
    &lt;mvc:resources mapping="/js/**" location="/js/" /&gt;
&lt;/beans&gt;</pre><br />
ehcache配置部分：<br />
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;ehcache&gt;
    &lt;diskStore path="D:/Temp/ehcache" /&gt;
    &lt;defaultCache eternal="false" /&gt;
    &lt;cache name="entityCache" eternal="false" maxElementsInMemory="1000" timeToIdleSeconds="120" timeToLiveSeconds="3600"
        overflowToDisk="true" memoryStoreEvictionPolicy="LFU" /&gt;
&lt;/ehcache&gt;</pre><br />
Web.xml配置（使用了Spring的JavaConfig）：<br />
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"&gt;
    &lt;context-param&gt;
        &lt;param-name&gt;contextClass&lt;/param-name&gt;
        &lt;param-value&gt;org.springframework.web.context.support.AnnotationConfigWebApplicationContext&lt;/param-value&gt;
    &lt;/context-param&gt;
    &lt;context-param&gt;
        &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
        &lt;param-value&gt;cc.gmem.demo.spring.SpringBeanDefinitionRegistrar&lt;/param-value&gt;
    &lt;/context-param&gt;
    &lt;listener&gt;
        &lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt;
    &lt;/listener&gt;
    &lt;servlet&gt;
        &lt;servlet-name&gt;springmvc&lt;/servlet-name&gt;
        &lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;
        &lt;init-param&gt;
            &lt;param-name&gt;contextClass&lt;/param-name&gt;
            &lt;param-value&gt;org.springframework.web.context.support.AnnotationConfigWebApplicationContext&lt;/param-value&gt;
        &lt;/init-param&gt;
        &lt;init-param&gt;
            &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
            &lt;param-value&gt;cc.gmem.demo.spring.SpringMVCBeanDefinitionRegistrar&lt;/param-value&gt;
        &lt;/init-param&gt;
    &lt;/servlet&gt;
    &lt;servlet-mapping&gt;
        &lt;servlet-name&gt;springmvc&lt;/servlet-name&gt;
        &lt;url-pattern&gt;/&lt;/url-pattern&gt;
    &lt;/servlet-mapping&gt;

    &lt;servlet-mapping&gt;
        &lt;servlet-name&gt;adminServlet&lt;/servlet-name&gt;
        &lt;url-pattern&gt;/proxool&lt;/url-pattern&gt;
    &lt;/servlet-mapping&gt;

    &lt;session-config&gt;
        &lt;session-timeout&gt;60&lt;/session-timeout&gt;
    &lt;/session-config&gt;

    &lt;error-page&gt;
        &lt;error-code&gt;404&lt;/error-code&gt;
        &lt;location&gt;/WEB-INF/jsp/error-404.jsp&lt;/location&gt;
    &lt;/error-page&gt;
    &lt;error-page&gt;
        &lt;error-code&gt;401&lt;/error-code&gt;
        &lt;location&gt;/WEB-INF/jsp/error-401.jsp&lt;/location&gt;
    &lt;/error-page&gt;
    &lt;error-page&gt;
        &lt;error-code&gt;500&lt;/error-code&gt;
        &lt;location&gt;/WEB-INF/jsp/error-500.jsp&lt;/location&gt;
    &lt;/error-page&gt;
     &lt;welcome-file-list&gt;
        &lt;welcome-file&gt;index.jsp&lt;/welcome-file&gt;
    &lt;/welcome-file-list&gt;
&lt;/web-app&gt;</pre><br />
Java Config类：<br />
<pre class="crayon-plain-tag">@Configuration
/* XML-based bean definitions */
@ImportResource ( "classpath:applicationContext.xml" )
public class SpringBeanDefinitionRegistrar
{

    /* Auto injected spring context */
    @SuppressWarnings ( "unused" )
    @Inject
    private ApplicationContext applicationContext;

    /* Programmatic bean definition */
    @Bean ( name = "helper" )
    public Helper helper()
    {
        Helper helper = new Helper();
        return helper;
    }

}</pre><br />
<pre class="crayon-plain-tag">@Configuration
@ImportResource ( "classpath:spring-mvc.xml" )
public class SpringMVCBeanDefinitionRegistrar
{

}</pre><br />
Maven依赖包列表：<br />
<pre class="crayon-plain-tag">&lt;properties&gt;
    &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
    &lt;spring.version&gt;3.1.2.RELEASE&lt;/spring.version&gt;
    &lt;hibernate.version&gt;3.6.10.Mod&lt;/hibernate.version&gt;
    &lt;aspectj.version&gt;1.6.11&lt;/aspectj.version&gt;
    &lt;jackson.version&gt;2.0.4&lt;/jackson.version&gt;
&lt;/properties&gt;
&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring-core&lt;/artifactId&gt;
        &lt;version&gt;${spring.version}&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring-jdbc&lt;/artifactId&gt;
        &lt;version&gt;${spring.version}&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring-beans&lt;/artifactId&gt;
        &lt;version&gt;${spring.version}&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring-webmvc&lt;/artifactId&gt;
        &lt;version&gt;${spring.version}&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring-orm&lt;/artifactId&gt;
        &lt;version&gt;${spring.version}&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring-test&lt;/artifactId&gt;
        &lt;version&gt;${spring.version}&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;org.hibernate&lt;/groupId&gt;
        &lt;artifactId&gt;hibernate-core&lt;/artifactId&gt;
        &lt;version&gt;${hibernate.version}&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;org.aspectj&lt;/groupId&gt;
        &lt;artifactId&gt;aspectjweaver&lt;/artifactId&gt;
        &lt;version&gt;${aspectj.version}&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.aspectj&lt;/groupId&gt;
        &lt;artifactId&gt;aspectjrt&lt;/artifactId&gt;
        &lt;version&gt;${aspectj.version}&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
        &lt;artifactId&gt;jackson-core&lt;/artifactId&gt;
        &lt;version&gt;${jackson.version}&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
        &lt;artifactId&gt;jackson-databind&lt;/artifactId&gt;
        &lt;version&gt;${jackson.version}&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
        &lt;artifactId&gt;jackson-annotations&lt;/artifactId&gt;
        &lt;version&gt;${jackson.version}&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;javax.servlet&lt;/groupId&gt;
        &lt;artifactId&gt;servlet-api&lt;/artifactId&gt;
        &lt;version&gt;2.4&lt;/version&gt;
        &lt;scope&gt;provided&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;javax.servlet.jsp&lt;/groupId&gt;
        &lt;artifactId&gt;jsp-api&lt;/artifactId&gt;
        &lt;version&gt;2.0&lt;/version&gt;
        &lt;scope&gt;provided&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;javax.servlet&lt;/groupId&gt;
        &lt;artifactId&gt;jstl&lt;/artifactId&gt;
        &lt;version&gt;1.2&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;taglibs&lt;/groupId&gt;
        &lt;artifactId&gt;standard&lt;/artifactId&gt;
        &lt;version&gt;1.1.2&lt;/version&gt;
        &lt;scope&gt;provided&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;javax.el&lt;/groupId&gt;
        &lt;artifactId&gt;el-api&lt;/artifactId&gt;
        &lt;version&gt;2.2&lt;/version&gt;
        &lt;scope&gt;provided&lt;/scope&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;proxool&lt;/groupId&gt;
        &lt;artifactId&gt;proxool&lt;/artifactId&gt;
        &lt;version&gt;0.9.1&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;org.javassist&lt;/groupId&gt;
        &lt;artifactId&gt;javassist&lt;/artifactId&gt;
        &lt;version&gt;3.15.0-GA&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;net.sf.log4jdbc&lt;/groupId&gt;
        &lt;artifactId&gt;log4jdbc3&lt;/artifactId&gt;
        &lt;version&gt;1.2-ext-3&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.sqlinform&lt;/groupId&gt;
        &lt;artifactId&gt;sqlinform&lt;/artifactId&gt;
        &lt;version&gt;0.0.1&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;javax.annotation&lt;/groupId&gt;
        &lt;artifactId&gt;jsr250-api&lt;/artifactId&gt;
        &lt;version&gt;1.0&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;javax.inject&lt;/groupId&gt;
        &lt;artifactId&gt;javax.inject&lt;/artifactId&gt;
        &lt;version&gt;1&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;joda-time&lt;/groupId&gt;
        &lt;artifactId&gt;joda-time&lt;/artifactId&gt;
        &lt;version&gt;2.1&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;cglib&lt;/groupId&gt;
        &lt;artifactId&gt;cglib&lt;/artifactId&gt;
        &lt;version&gt;2.2.2&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
        &lt;groupId&gt;org.apache.poi&lt;/groupId&gt;
        &lt;artifactId&gt;poi&lt;/artifactId&gt;
        &lt;version&gt;3.9&lt;/version&gt;
    &lt;/dependency&gt;
    
    &lt;dependency&gt;
        &lt;groupId&gt;opensymphony&lt;/groupId&gt;
        &lt;artifactId&gt;quartz-all&lt;/artifactId&gt;
        &lt;version&gt;1.6.3&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;</pre><br />
Maven插件配置（支持AspectJ）：<br />
<pre class="crayon-plain-tag">&lt;plugin&gt;
    &lt;groupId&gt;org.codehaus.mojo&lt;/groupId&gt;
    &lt;artifactId&gt;aspectj-maven-plugin&lt;/artifactId&gt;
    &lt;version&gt;1.4&lt;/version&gt;
    &lt;executions&gt;
        &lt;execution&gt;
            &lt;id&gt;compile&lt;/id&gt;
            &lt;configuration&gt;
                &lt;XnoInline&gt;true&lt;/XnoInline&gt;
                &lt;forceAjcCompile&gt;true&lt;/forceAjcCompile&gt;
                &lt;showWeaveInfo&gt;true&lt;/showWeaveInfo&gt;
                &lt;source&gt;1.5&lt;/source&gt;
                &lt;target&gt;1.5&lt;/target&gt;
                &lt;encoding&gt;UTF-8&lt;/encoding&gt;
                &lt;verbose&gt;true&lt;/verbose&gt;
                &lt;outxml&gt;true&lt;/outxml&gt;
                &lt;aspectLibraries&gt;
                    &lt;aspectLibrary&gt;
                        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
                        &lt;artifactId&gt;spring-aspects&lt;/artifactId&gt;
                    &lt;/aspectLibrary&gt;
                &lt;/aspectLibraries&gt;
            &lt;/configuration&gt;
            &lt;goals&gt;
                &lt;goal&gt;compile&lt;/goal&gt;
            &lt;/goals&gt;
        &lt;/execution&gt;
    &lt;/executions&gt;
&lt;/plugin&gt;</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/spring-config-with-hibernate-jacksonjson-aspectj">Spring配置：集成Hibernate、JacksonJSON、AspectJ等框架</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/spring-config-with-hibernate-jacksonjson-aspectj/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>基于JavaConfig方式的Spring+Hibernate集成</title>
		<link>https://blog.gmem.cc/javaconfig-based-sh</link>
		<comments>https://blog.gmem.cc/javaconfig-based-sh#comments</comments>
		<pubDate>Mon, 05 Nov 2012 05:47:36 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Hibernate]]></category>
		<category><![CDATA[Spring]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1508</guid>
		<description><![CDATA[<p>JavaConfig类 包含Spring、Hibernate、事务管理等功能的样例 [crayon-69e945c70ee70017129832/] 初始化Spring ApplicationContext的代码 [crayon-69e945c70ee76990831754/]</p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/javaconfig-based-sh">基于JavaConfig方式的Spring+Hibernate集成</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h3"><span class="graybg">JavaConfig类</span></div>
包含Spring、Hibernate、事务管理等功能的样例<br />
<pre class="crayon-plain-tag">package sparknet.wnet.saic.intf.spring;

import java.io.IOException;
import java.util.Properties;

import javax.inject.Inject;
import javax.sql.DataSource;

import net.greenmemory.breeze.persist.OracleProxoolDataSource;
import net.greenmemory.commons.db.QueryRunner;
import net.greenmemory.commons.lang.NumberUtils;
import net.greenmemory.spring.core.io.ClassPathResource;

import org.hibernate.SessionFactory;
import org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.FilterType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor;
import org.springframework.transaction.interceptor.TransactionInterceptor;

import cc.gmem.tools.hibernatetools.spring.Hibernate3LocalSessionFactoryBean;

@Configuration
@ComponentScan (
        basePackages = { "cc.gmem.saic", "cc.gmem.intf" },
        excludeFilters = { @Filter ( type = FilterType.ANNOTATION, value = Configuration.class ) } )
@EnableAspectJAutoProxy
public class BeanDefinitionRegistrar
{

    @Inject
    private ApplicationContext applicationContext;

    @Bean ( name = "cfg" )
    public Properties getAppConfig()
    {
        Properties props = new Properties();
        try
        {
            props.load( new ClassPathResource( "wsi.properties" ).getInputStream() );
        }
        catch ( IOException e )
        {
            throw new BeanInitializationException( e.getMessage(), e );
        }
        return props;
    }

    @Bean ( name = "dataSource" )
    public DataSource getDataSource()
    {
        OracleProxoolDataSource dataSource = new OracleProxoolDataSource();
        dataSource.setAlias( getAppConfig().getProperty( "jdbc.alias" ) );
        dataSource.setDriver( getAppConfig().getProperty( "jdbc.driverClass" ) );
        dataSource.setDriverUrl( getAppConfig().getProperty( "jdbc.url" ) );
        dataSource.setUser( getAppConfig().getProperty( "jdbc.user" ) );
        dataSource.setPassword( getAppConfig().getProperty( "jdbc.password" ) );
        dataSource.setMinimumConnectionCount( NumberUtils.toInt( getAppConfig().getProperty( "jdbc.minimumConnectionCount" ), 10 ) );
        dataSource.setMaximumConnectionCount( NumberUtils.toInt( getAppConfig().getProperty( "jdbc.maximumConnectionCount" ), 100 ) );
        dataSource.setSimultaneousBuildThrottle( NumberUtils.toInt( getAppConfig().getProperty( "jdbc.simultaneousBuildThrottle" ), 50 ) );
        dataSource.setMaximumActiveTime( NumberUtils.toInt( getAppConfig().getProperty( "jdbc.maximumActiveTime" ), 1200000 ) );
        dataSource.setEncrypted( false );
        dataSource.init();
        return dataSource;
    }

    @Bean ( name = "sessionFactory" )
    public SessionFactory getSessionFactory() throws Exception
    {
        Hibernate3LocalSessionFactoryBean bean = new Hibernate3LocalSessionFactoryBean();
        bean.setApplicationContext( applicationContext );
        bean.setDataSource( getDataSource() );
        bean.setMappingResources( new String[] { "wnetJhInterface.hbm.xml" } );
        bean.setAutoRegister( true );
        Properties hibernateProperties = new Properties();
        hibernateProperties.setProperty( "hibernate.dialect", "org.hibernate.dialect.Oracle10gDialect" );
        hibernateProperties.setProperty( "hibernate.jdbc.batch_size", "100" );
        hibernateProperties.setProperty( "hibernate.default_entity_mode", "dom4j" );
        hibernateProperties.setProperty( "hibernate.transaction.factory_class", "org.hibernate.transaction.JDBCTransactionFactory" );
        hibernateProperties.setProperty( "hibernate.current_session_context_class", "org.springframework.orm.hibernate3.SpringSessionContext" );
        bean.setHibernateProperties( hibernateProperties );
        bean.afterPropertiesSet();
        return bean.getObject();
    }

    @Bean ( name = "txManager" )
    public PlatformTransactionManager getTransactionManager() throws Exception
    {
        HibernateTransactionManager txMgr = new HibernateTransactionManager();
        txMgr.setSessionFactory( getSessionFactory() );
        return txMgr;
    }

    @Bean ( name = "jdbcTemplate" )
    public JdbcTemplate getJdbcTemplate()
    {
        return new JdbcTemplate( getDataSource() );
    }

    @Bean ( name = "queryRunner" )
    public QueryRunner getQueryRunner()
    {
        return new QueryRunner( getDataSource() );
    }

    @Bean
    public AnnotationTransactionAttributeSource transactionAttributeSource()
    {
        return new AnnotationTransactionAttributeSource();
    }

    @Bean
    public TransactionInterceptor transactionInterceptor() throws Exception
    {
        return new TransactionInterceptor( getTransactionManager(), transactionAttributeSource() );
    }

    @Bean
    public TransactionAttributeSourceAdvisor internalTransactionAdvisor() throws Exception
    {
        return new TransactionAttributeSourceAdvisor( transactionInterceptor() );
    }

    @Bean
    public InfrastructureAdvisorAutoProxyCreator internalAutoProxyCreator()
    {
        return new InfrastructureAdvisorAutoProxyCreator();
    }
}</pre> 
<div class="blog_h3"><span class="graybg">初始化Spring ApplicationContext的代码</span></div>
<pre class="crayon-plain-tag">ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanDefinitionRegistrar.class);
DataSource ds= ctx.getBean(DataSource.class);</pre> 
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/javaconfig-based-sh">基于JavaConfig方式的Spring+Hibernate集成</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/javaconfig-based-sh/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AspectJ编程学习笔记</title>
		<link>https://blog.gmem.cc/aspectj-study-note</link>
		<comments>https://blog.gmem.cc/aspectj-study-note#comments</comments>
		<pubDate>Sun, 10 Apr 2011 02:20:04 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[AOP]]></category>
		<category><![CDATA[AspectJ]]></category>
		<category><![CDATA[Spring]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1697</guid>
		<description><![CDATA[<p>AOP基本概念 名词  含义 切面（方面，Aspect） 一个关注点的模块化，这个关注点实现可能横切（crosscutting）多个对象切面的例子包括：事务控制、日志记录、权限控制等在AspectJ中，切面表现为Java类，其源码具有AspectJ的特殊语法增强，使用ajc编译器编译 连接点（Joinpoint） 程序执行过程中明确的点，例如方法的调用开始、结束，或者特定的异常被抛出，横切（crosscutting）在连接点发生 通知（Advice） 在特定的连接点，AOP框架执行的操作 切入点（Pointcut） 切入点是这样的一种程序构造：包含一个连接点的集合、以及收集的连接点的上下文信息（例如方法参数、被执行方法和对象）。通知在这些切入点被触发。AOP框架允许开发者以多种方式定义切入点 引入（Introduction） 添加方法、字段、接口、注解等到被通知的类 目标对象（Target Object） 包含连接点的对象，也被称作被通知或被代理对象 AOP代理（AOP Proxy） AOP框架创建的对象，以目标对象为基础，织入了通知逻辑代理主要包括动态代理、字节码增强两种类型 AspectJ基础知识 AspectJ编译器能识别任何普通的Java代码，可以使用ajc编译.java文件 织入方式： <a class="read-more" href="https://blog.gmem.cc/aspectj-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/aspectj-study-note">AspectJ编程学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">AOP基本概念</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">名词</td>
<td style="text-align: center;"> 含义</td>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 200px;">切面（方面，Aspect）</td>
<td>一个关注点的模块化，这个关注点实现可能横切（crosscutting）多个对象切面的例子包括：事务控制、日志记录、权限控制等在AspectJ中，切面表现为Java类，其源码具有AspectJ的特殊语法增强，使用ajc编译器编译</td>
</tr>
<tr>
<td>连接点（Joinpoint）</td>
<td>程序执行过程中明确的点，例如方法的调用开始、结束，或者特定的异常被抛出，横切（crosscutting）在连接点发生</td>
</tr>
<tr>
<td>通知（Advice）</td>
<td>在特定的连接点，AOP框架执行的操作</td>
</tr>
<tr>
<td>切入点（Pointcut）</td>
<td>切入点是这样的一种程序构造：包含<strong>一个连接点的集合、以及收集的连接点的上下文信息</strong>（例如方法参数、被执行方法和对象）。通知在这些切入点被触发。AOP框架允许开发者以多种方式定义切入点</td>
</tr>
<tr>
<td>引入（Introduction）</td>
<td>添加方法、字段、接口、注解等到被通知的类</td>
</tr>
<tr>
<td>目标对象（Target Object）</td>
<td>包含连接点的对象，也被称作被通知或被代理对象</td>
</tr>
<tr>
<td>AOP代理（AOP Proxy）</td>
<td>AOP框架创建的对象，以目标对象为基础，织入了通知逻辑代理主要包括动态代理、字节码增强两种类型</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">AspectJ基础知识</span></div>
<ol>
<li>AspectJ编译器能识别任何普通的Java代码，可以使用ajc编译.java文件</li>
<li>织入方式：<br />
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="width: 100px;">源代码织入</td>
<td>织入器作为编译器的一部分，处理源代码，支持经典语法和注解语法。生成的字节码符合JVM规范，需要使用ajc代替javac</td>
</tr>
<tr>
<td>字节码织入</td>
<td>传递给织入器的是字节码。使用这种方式时，包含编译普通Java类、编译切面，织入3个步骤。</td>
</tr>
<tr>
<td>加载时织入</td>
<td>传递给织入器的是Java类字节码、切面类，以及aop.xml配置文件。</td>
</tr>
</tbody>
</table>
</li>
<li id="joinpoints-exposed-by-aspject">AspectJ暴露的连接点。<strong>只能在这些连接点上应用通知</strong>：<br />
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 80px; text-align: center;"> </td>
<td style="width: 120px; text-align: center;">暴露的连接点</td>
<td style="text-align: center;">代码表现</td>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="2">方法</td>
<td>执行<br />Execution</td>
<td>方法体：该连接点环绕整个方法体的执行过程。这也是Spring AOP唯一支持的接入点类型。通知被织入方法体<br /> <strong>切入点语法：execution(方法签名)</strong></td>
</tr>
<tr>
<td>调用<br />Call</td>
<td>方法调用：该接入点发生在方法被调用时。通知被织入调用者代码处<br /> <strong>切入点语法：call(方法签名)</strong></td>
</tr>
<tr>
<td rowspan="2">构造器</td>
<td>执行<br />Execution</td>
<td>对象构造逻辑：该连接点环绕整个构造器的执行过程<br /> <strong>切入点语法：execution(构造器签名)</strong></td>
</tr>
<tr>
<td>调用<br />Call</td>
<td>对象构造调用<br /> <strong>切入点语法：call(构造器签名)</strong></td>
</tr>
<tr>
<td rowspan="2">字段访问</td>
<td>读访问<br />Read access</td>
<td>读取类或对象的字段<br /> <strong>切入点语法：get(字段签名)</strong></td>
</tr>
<tr>
<td>写访问<br />Read access</td>
<td>写入类或对象的字段<br /> <strong>切入点语法：set(字段签名)</strong></td>
</tr>
<tr>
<td>异常处理</td>
<td>处理器<br />Handler</td>
<td>处理异常的catch块<br /> <strong>切入点语法：handler(类型签名)</strong></td>
</tr>
<tr>
<td rowspan="3">初始化</td>
<td>类初始化<br />Class initialization</td>
<td>类加载，包括静态成员初始化部分<br /> <strong>切入点语法：staticinitialization(类型签名)</strong></td>
</tr>
<tr>
<td>对象初始化<br />Object initialization</td>
<td>在构造器中的对象初始化。包含：从一个父构造器返回，到当前构造器执行完成的部分（不包括调用父构造器的部分）<br /> <strong>切入点语法：initialization(构造器签名)</strong><br /> Spring的@Configurable即为此例 。下面的例子解释了连接点的范围：<br />
<pre class="crayon-plain-tag">public class SavingsAccount extends Account
{
    private boolean isOverdraft;
    private int     minimumBalance;
    //构造器1
    public SavingsAccount( int accountNumber, 
                                 boolean isOverdraft )
    {
        super( accountNumber );
        //对象初始化连接点：构造器1，2
        this.isOverdraft = isOverdraft;
    }
    //构造器2
    public SavingsAccount( int accountNumber )
    {
        //对象初始化连接点：构造器2
        this( accountNumber, false );
        //对象初始化连接点：构造器2
        this.minimumBalance = 25;
    }
}</pre>
</td>
</tr>
<tr>
<td>对象预初始化<br />pre-initialization</td>
<td>在构造器中的对象初始化之前：用的很少，从当前构造器调用，到父构造器调用结束为止<br /> <strong>切入点语法：preinitialization(构造器签名)</strong></td>
</tr>
<tr>
<td>通知</td>
<td>执行<br />Execution</td>
<td>通知的执行：环绕某个通知的整个执行。可以对通知进行通知<br /> <strong>切入点语法：adviceexecution()</strong></td>
</tr>
</tbody>
</table>
</li>
<li>AspectJ切入点声明语法<br />
<pre class="crayon-plain-tag">/**
 * 切入点声明语法，包含5个部分：
 * 访问标识符：例如 public
 * 关键字：pointcut
 * 切入点名称()
 * 切入点类型：例如execution、call，参考：第3点
 * 切入点签名：根据切入点类型不同，可能是类型签名、方法签名、字段签名
 */
public pointcut secureAccess(): execution(* MessageCommunicator.deliver(..));</pre></p>
<p>AspectJ支持仅有切入点名称，而没有类型和签名的切入点，通常使用在抽象切面中。<br /> AspectJ支持匿名切入点，作为通知签名的一部分存在。<br /> AspectJ支持使用 !、 &amp;&amp; 、|| 来作为切入点的运算符</p>
</li>
<li>AspectJ通知定义语法：由<strong>通知声明、切入点定义、通知体三部分</strong>组成<br />
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">组成</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2">整体语法形式：<br />
<pre class="crayon-plain-tag">[@AdviceName("通知的名称")]
[返回值] 通知类型([上下文信息]) [returning|throwing] [throws 通知抛出的异常] : 切入点定义([上下文信息]) {
    通知体
}</pre>
</td>
</tr>
<tr>
<td>通知声明</td>
<td>通知类型可以是：before, after 或 around，其中around需要声明返回值通知声明可以指定通知体可用的上下文信息，包括：执行对象通知声明可以指定通知体可能抛出的异常</td>
</tr>
<tr>
<td>切入点定义</td>
<td>位于冒号之后，任何匹配切入点定义的连接点上都会执行本通知</td>
</tr>
<tr>
<td style="width: 100px;">通知体</td>
<td>可以访问一系列特殊的变量或关键字，例如：proceed、thisJoinPoint、thisJoinPointStaticPart、 thisEnclosingJoinPointStaticPart</td>
</tr>
</tbody>
</table>
</li>
<li>AspectJ支持的通知类型<br />
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">通知类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Before advice</td>
<td>在连接点执行之前执行，如果抛出异常，那么连接点不被执行</td>
</tr>
<tr>
<td>After advice</td>
<td>在连接点执行之后执行，根据连接点的执行结果，具有3个子类型：After (finally)：在连接点执行之后执行，不论其结果，通知声明语法：<strong>after()</strong>After returning：在连接点执行成功后执行，通知声明语法：<strong>after() returning(&lt;ReturnType returnObject&gt;)</strong>After throwing：在连接点执行失败后执行，通知声明语法：<strong>after() throwing(&lt;ExceptionType exceptionObject&gt;)</strong></td>
</tr>
<tr>
<td>Around advice</td>
<td>环绕连接点的执行过程，具有修改连接点执行上下文的能力，可以用来：
<ol>
<li>在连接点之前/之后添加额外的逻辑，例如性能分析</li>
<li>跳过原先逻辑还执行备选的逻辑，例如缓存。只要不调用proceed()，即跳过</li>
<li>使用try-catch包裹原先逻辑，提供异常处理策略，例如事务管理</li>
</ol>
</td>
</tr>
</tbody>
</table>
</li>
</ol>
<div class="blog_h2"><span class="graybg">AspectJ切入点签名语法详解</span></div>
<div class="blog_h3"><span class="graybg">通配符</span></div>
<p>通配符用于匹配一系列的连接点。</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td> *</td>
<td>匹配点号.范围内的任意数目的字符在类型签名中：表示部分包名或者部分类名在其他地方：表示部分的方法或者字段名</td>
</tr>
<tr>
<td> ..</td>
<td>匹配任意数目的字符，包括任意数量的点号在类型签名中：表示任意直接、间接的子包在方法签名中：表示任意数量的方法参数</td>
</tr>
<tr>
<td> +</td>
<td>作为类型签名的后缀，表示包含任意的子类型</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">类型签名语法</span></div>
<p>这里的类型泛指：类、接口、注解、切面、基本类型<br /> <strong>基本类型签名示例：</strong></p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">签名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Account</td>
<td>仅匹配Account类型</td>
</tr>
<tr>
<td>*Account</td>
<td>匹配任何类名以Account结尾的类型</td>
</tr>
<tr>
<td>java.*.Date</td>
<td>匹配java的<strong>直接子包</strong>中的Date</td>
</tr>
<tr>
<td>java..*</td>
<td>匹配java包及其任意子包中的任意类型</td>
</tr>
<tr>
<td>javax..*Model+</td>
<td>匹配java包及其任意子包中的任意以Model结尾的类型，及其子类型</td>
</tr>
</tbody>
</table>
<p><strong>基于注解的类型签名示例：</strong><br /> 由于Java不支持注解的继承，所有注解名后不会出现加号。注意注解必须运行时可见（<pre class="crayon-plain-tag">RetentionPolicy.RUNTIME</pre> ）</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">签名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>@Secured Account</td>
<td>标注了@Secured注解的Account类型</td>
</tr>
<tr>
<td>@Sensitive *</td>
<td>标注了@Sensitive注解的任何类型</td>
</tr>
<tr>
<td>@Business* Customer+</td>
<td>Customer及其子类型，且标注了Business开头的注解</td>
</tr>
</tbody>
</table>
<p><strong>基于泛型的类型签名示例：</strong></p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">签名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Map&lt;Long,Account&gt;</td>
<td>仅仅匹配Map&lt;Long,Account&gt;类型</td>
</tr>
<tr>
<td>*&lt;Account&gt;</td>
<td>任何把Account作为其泛型参数的类型</td>
</tr>
<tr>
<td>Collection&lt;@Sensitive *&gt;</td>
<td>Collection类型，其泛型参数类必须被标注了@Sensitive</td>
</tr>
<tr>
<td>Collection&lt;? extends Account&gt;</td>
<td>Collection类型，其泛型参数类必须是Account或者其子类</td>
</tr>
<tr>
<td>Collection&lt;? super Account&gt;</td>
<td>Collection类型，其泛型参数类必须是Account的父类</td>
</tr>
</tbody>
</table>
<p><strong>联合类型签名示例：</strong></p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">签名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>!Collection</td>
<td>除了Collection的任何类型</td>
</tr>
<tr>
<td>Collection || Map</td>
<td>Collection或者Map类型</td>
</tr>
<tr>
<td>java.util.RandomAccess+ &amp;&amp; java.util.List+</td>
<td>任何同时实现了这两个接口的子类，例如ArrayList</td>
</tr>
<tr>
<td>!@Secured *</td>
<td>任何没有标注@Secured 的类型</td>
</tr>
<tr>
<td>@Secured @Sensitive *</td>
<td>任何同时标注了@Secured @Sensitive的类型</td>
</tr>
<tr>
<td>@(Secured || Sensitive) *</td>
<td>任何标注了@Secured @Sensitive之一的类型</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">方法和构造器签名语法</span></div>
<p>构造器签名与方法类似，但是：</p>
<ol>
<li>由于构造器没有返回值，因此不得在签名中指定返回值</li>
<li>由于构造器不能为静态，故不得添加static关键字</li>
<li>由于构造器没有名称，必须使用new作为其名称部分</li>
</ol>
<p><a href="/wp-content/uploads/2011/04/method-pattern-format.jpg"><img src="https://blog.gmem.cc/wp-content/uploads/2011/04/method-pattern-format.jpg" alt="" /></a><br /> <strong>基本方法签名示例：</strong></p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">签名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>public void Account.set*(*)</td>
<td>Account类中，包含单个参数、返回类型为void、以set开头的公共方法</td>
</tr>
<tr>
<td>public void Account.*()</td>
<td>Account类中，任何返回类型为void、没有参数的公共方法</td>
</tr>
<tr>
<td>public * Account.*()</td>
<td>Account类中，任何没有参数的公共方法</td>
</tr>
<tr>
<td>public * Account.*(..)</td>
<td>Account类中，包含任意参数（包括0参）的公共方法</td>
</tr>
<tr>
<td>* Account.*(..)</td>
<td><span style="color: #000000;">Account类中，包含任意参数（包括0参）的方法</span></td>
</tr>
<tr>
<td>* *.*(..)* *(..)</td>
<td>任意方法</td>
</tr>
<tr>
<td>!public * Account.*(..)</td>
<td>任何Account类的非公共方法</td>
</tr>
<tr>
<td>* *(..) throws SQLException</td>
<td>任何声明抛出SQLException的方法</td>
</tr>
<tr>
<td>* Account+.*(..)</td>
<td>Account及其子类的任何方法</td>
</tr>
<tr>
<td>* java.io.Reader.read(char[],..)</td>
<td>java.io.Reader类的read方法，第一个参数必须为char[]</td>
</tr>
<tr>
<td>* javax..*.add*Listener(EventListener+)</td>
<td>javax的任意子包中的类的方法，必须以add开头、Listener结尾，且包含唯一的参数EventListener类型及其子类型</td>
</tr>
<tr>
<td>* java.io.PrintStream.printf(String,Object...)</td>
<td>java.io.PrintStream的printf方法，第一个参数为字符串，第二个参数为Object...</td>
</tr>
<tr>
<td>Account AccountService.*(..)</td>
<td>AccountService类中任何返回AccountService的方法</td>
</tr>
<tr>
<td>public Account.new()</td>
<td>匹配Account类的0参公共构造器</td>
</tr>
</tbody>
</table>
<p><strong>基于注解的方法签名示例：</strong></p>
<p>注意注解必须运行时可见（<pre class="crayon-plain-tag">RetentionPolicy.RUNTIME</pre> ）</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 250px; text-align: center;">签名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>@Secured * *(..)</td>
<td>任何标注了@Secured的方法</td>
</tr>
<tr>
<td>@Secured @Transactional * *(..)</td>
<td><span style="color: #000000;">任何同时标注了@</span><span style="color: #000000;">Secured、@Transactional 的方法</span></td>
</tr>
<tr>
<td>@(Secured || Transactional) * *(..)</td>
<td><span style="color: #000000;">任何标注了@</span><span style="color: #000000;">Secured或者@Transactional 的方法</span></td>
</tr>
<tr>
<td>(@Sensitive *) *(..)</td>
<td>任何返回值类型上标注了@Sensitive的方法</td>
</tr>
<tr>
<td>* (@BusinessEntity *).*(..)</td>
<td>任何标注了@BusinessEntity的类的任何方法</td>
</tr>
<tr>
<td>* *(@RequestParam (*))</td>
<td>任何包含了单个参数，且参数上标注了@RequestParam 的方法，例如：void show(@RequestParam Long id)</td>
</tr>
<tr>
<td>* *(@Sensitive *)* *((@Sensitive *))</td>
<td>任何包含了单个参数，且参数的类型上标注了@RequestParam 的方法，例如：void create(ClassAnnotatedWithSensitive obj)</td>
</tr>
<tr>
<td>* *(@RequestParam(@Sensitive *))</td>
<td><span style="color: #000000;">void create(@RequestParam </span><span style="color: #000000;">ClassAnnotatedWithSensitive </span><span style="color: #000000;">obj</span><span style="color: #000000;">)</span></td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">字段签名语法</span></div>
<p>字段签名中，使用get表示读字段，set表示写字段：<br /> <img src="https://blog.gmem.cc/wp-content/uploads/2011/04/field-pattern-format.jpg" alt="" /><br /> <strong>字段签名示例：</strong></p>
<p>注意注解必须运行时可见（<pre class="crayon-plain-tag">RetentionPolicy.RUNTIME</pre> ）</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">签名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>private double Account.balance</td>
<td>Account类的私有double类型的balance字段</td>
</tr>
<tr>
<td>* Account.*</td>
<td>Account类的任何字段</td>
</tr>
<tr>
<td>* Account+.*</td>
<td>Account类及其子类的任何字段</td>
</tr>
<tr>
<td>@Sensitive * *</td>
<td>字段上标注了@Sensitive的任何字段</td>
</tr>
<tr>
<td>(@Sensitive *) *.*</td>
<td>字段的类型上标注了@Sensitive的任何字段</td>
</tr>
<tr>
<td>* (@Sensitive *).*</td>
<td>标注了@Sensitive的类的任何字段</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">AspectJ切入点的实现</span></div>
<p>AspectJ提供几种切入点指示器（designators），配合上面所述的切入点签名，即组成切入点。<br /> 切入点通过两种方式与连接点进行匹配：</p>
<ol>
<li>类型限定的切入点：直接映射到某个类型的连接点，参考<a href="#joinpoints-exposed-by-aspject">AspectJ暴露的连接点</a>一节中的“切入点语法”部分</li>
<li>无类型的切入点：根据指定的信息，来选择特定的连接点。这些信息可以是：连接点上下文的运行时类型、控制流，或者此法作用域（lexical scope）</li>
</ol>
<div class="blog_h3"><span class="graybg">类型限定的切入点</span></div>
<p>匹配：指定类型和签名的连接点<br /> Spring：仅支持execution类型<br /> 静态确定：是</p>
<div class="blog_h3"><span class="graybg">无类型的切入点</span></div>
<p>不去限定连接点的类型，还是通过若干种规则去匹配多种连接点。</p>
<p>Spring虽然仅仅支持execution一种类型的连接点，但是却支持多种方式来选择它们</p>
<p><strong>基于控制流的切入点</strong><br /> 这一类的切入点匹配 <strong>处于其他切入点的控制流之中</strong> 的连接点。这句话比较难以理解，以贷款业务为例，假设Account.debit()方法调用Account.getBalance()进行余额查询，那么，可以说：getBalance在debit的控制流之中。类似的，字段访问、异常处理，也可以处于某个切入点的控制流之中。<br /> Spring不支持这一类切入点，不能静态确定（需要运行时判断）<br /> 包含两种切入点类型：cflow、cflowbelow，后者不包括启动控制流的连接点本身</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 300px; text-align: center;">切入点</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>cflow(execution(* Account.debit(..)))</td>
<td>匹配所有这样的连接点：位于Account的任何debit方法的控制流之中，包括debit方法本身</td>
</tr>
<tr>
<td>cflowbelow(execution(* Account.debit(..)))</td>
<td>匹配所有这样的连接点：位于Account的任何debit方法的控制流之中，不包括debit方法本身</td>
</tr>
<tr>
<td>cflow(execution(@Transactional * *(..)))</td>
<td>匹配所有位于配置@Transactional方法的控制流之中的任何连接点</td>
</tr>
<tr>
<td>cflow(transacted())</td>
<td>匹配切入点transacted所选择的任何连接点的控制流之中的连接点</td>
</tr>
</tbody>
</table>
<p>下面以事务管理的场景为例，来说明控制流切入点的用法：</p>
<pre class="crayon-plain-tag">package cc.gmem.aj.cflow;
public class Account
{
    //两个方法均具有事务注解，但是，我们需要在“最外层”来启动事务
    //需要忽略内层的事务注解
    @Transactional
    public void debit()
    {
        getBalance();
    }
    @Transactional
    public void getBalance()
    {
    }
}</pre><br />
<pre class="crayon-plain-tag">public aspect TransactionAspect
{
    //匹配所有标注了@Transactional的方法执行
    private pointcut transacted() : execution(@Transactional * *(..));
    //匹配这样的@Transactional方法：不在@Transactional方法的所确定的控制流的内部
    private pointcut topLevelTransacted() : transacted() &amp;&amp; !cflowbelow(transacted());

    Object around() : topLevelTransacted() {
        System.out.println( "启动事务" );
        Object ret = proceed();
        System.out.println( "结束事务" );
        return ret;
    }
}</pre>
<p><strong>基于词法结构的切入点</strong><br /> 匹配：在指定类型、方法、构造器内的连接点<br /> Spring：支持within()，不支持withincode()<br /> 可以静态确定<br /> 包含两种切入点类型：within(类型签名)、withincode(构造器签名|方法签名)</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">切入点</td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>within(Account)</td>
<td>Account类，及其嵌套类中的任何连接点</td>
</tr>
<tr>
<td>within(Account+)</td>
<td>Account类（或者子类），及其嵌套类中的任何连接点</td>
</tr>
<tr>
<td>within(@javax.persistence.Entity *)</td>
<td>标注了的@Entity类，及其嵌套类中的任何连接点</td>
</tr>
<tr>
<td>withincode(* Account.debit(..))</td>
<td>Account的debit方法中的任何连接点</td>
</tr>
</tbody>
</table>
<p><strong>执行对象切入点</strong><br /> 根据<strong>运行时对象类型</strong>来选择连接点。并且，可以收集执行对象——<strong>双重功能</strong>。</p>
<p>包含两种类型：this(&lt;Type or ObjectIdentifier&gt;)、target(&lt;Type or ObjectIdentifier&gt;)，前者匹配this引用为指定类型的连接点；后者匹配方法调用target为指定类型的连接点。</p>
<p>该类切入点<strong>不支持类型通配，也不支持泛型</strong>。该类切入点<strong>不会匹配任何静态方法的执行连接点</strong>。</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">切入点</td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>this(Account)</td>
<td>任何连接点，只要表达式 this instnaceof Account为真，即匹配</td>
</tr>
<tr>
<td>target(Account)</td>
<td>任何（通常是方法调用类型）连接点，只要被调用的方法所属对象 instanceof Account，即匹配</td>
</tr>
</tbody>
</table>
<p>注意区别：<br /> execution(* Account.*(..)) 匹配Account类的任何静态、成员方法<br /> execution(* *.*(..))  &amp;&amp; this(Account)：匹配Account及其子类的任何成员方法<br /> <strong>参数切入点</strong><br /> 该类切入点根据<strong>连接点的参数对象的运行时类型</strong>来匹配。并且，可以收集匹配的参数对象。<br /> Spring支持此类切入点。无法静态确定。<br /> 所谓参数对象，根据连接点不同，含义也不同：</p>
<ol>
<li>对于方法、构造器连接点，参数对象即方法参数</li>
<li>对于异常处理连接点，参数对象即被处理的异常</li>
<li>对于字段写入连接点，参数对象即被写入的值</li>
</ol>
<p>该类切入点的形式为：args(Type or ObjectIdentifier, ..)</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">切入点</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>args(Account, .., int)</td>
<td>任何方法或者构造器，第一个参数的运行时类型为Account或其子类型，最后一个参数的运行时类型为int</td>
</tr>
<tr>
<td>args(RemoteException)</td>
<td>任何具有单个RemoteException类型参数的连接点。</td>
</tr>
</tbody>
</table>
<p><strong>基于注解的切入点</strong></p>
<p>注意：注解本身被标注的注解影响AOP行为：</p>
<ol>
<li>@Retention(RetentionPolicy.RUNTIME)：只有设置为运行时保留，才能在运行时进行匹配性判断</li>
<li>@<span style="color: #000000;">Inherited：只有标注了此注解，子编程元素（类、方法）才能继承父元素上标注的注解</span></li>
</ol>
<p>AspectJ支持根据类型、方法、字段所标注的注解来匹配连接点</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 350px; text-align: center;">切入点</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>@this(TypePattern or ObjectIdentifier)</td>
<td>任何连接点，只要this的类上具有TypePattern指定的注解注意：如果注解是@Inherited的，父类上有指定注解即可</td>
</tr>
<tr>
<td>@target(TypePattern or ObjectIdentifier)</td>
<td>任何连接点，只要target的类上具有TypePattern指定的注解</td>
</tr>
<tr>
<td>@args(TypePattern or ObjectIdentifier, ..)</td>
<td>任何连接点，只要参数的类上具有TypePattern指定的注解</td>
</tr>
<tr>
<td>@within(TypePattern or ObjectIdentifier)</td>
<td>任何连接点，只要在具有TypePattern指定的注解的类型的词法作用域内</td>
</tr>
<tr>
<td>@withincode(TypePattern or ObjectIdentifier)</td>
<td>任何连接点，只要在具有TypePattern指定的注解的构造器或方法的词法作用域内</td>
</tr>
<tr>
<td>@annotation(TypePattern or ObjectIdentifier)</td>
<td>任何连接点，只要目标上具有指定的注解目标的含义：
<ol>
<li>对于方法、构造器、通知执行连接点：目标为对应的程序元素</li>
<li>对于字段访问、异常处理连接点：目标为被访问的字段或者异常</li>
<li>对于初始化、预初始化连接点：目标为匹配的起始调用(first-called)构造器</li>
<li>对于静态初始化连接点：目标为被初始化的类</li>
</ol>
</td>
</tr>
</tbody>
</table>
<p><strong>条件检查切入点</strong><br /> 此类切入点在连接点上的一些条件检查。通常和其它切入点联合使用</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">切入点</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>if(debug)</td>
<td>任何debug静态字段（位于切面类）设置为true的连接点</td>
</tr>
<tr>
<td>if(System.currentTimeMillis() &gt; triggerTime)</td>
<td>任何发生在triggerTime以后的连接点</td>
</tr>
<tr>
<td>if(circle.getRadius() &lt; 5)</td>
<td>任何连接点，只要circle的radius小于5。circle必须是被收集的上下文对象，或者切面的静态字段。</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">AspectJ切面类基本结构</span></div>
<p>本段代码示意经典的AspectJ语法：</p>
<pre class="crayon-plain-tag">package cc.gmem.aj;

//切面的声明
public aspect SecurityAspect
{

    private Authenticator auth = new Authenticator();

    //切入点的声明
    pointcut secureAccess(): execution(* MessageCommunicator.deliver(..));
    //通知声明：针对secureAccess切入点的before通知
    before() : secureAccess() {
        System.out.println( "Checking and authenticating user" );
        auth.doAuthenticate();
    }
}</pre>
<p>本段代码示意基于注解的AspectJ语法：<br /> 基于注解的语法，可以作为普通Java类编译，并且使用LTW（加载时织入）机制，在类被加载到JVM中时进行织入。</p>
<pre class="crayon-plain-tag">package cc.gmem.aj;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class SecurityAspect
{

    private Authenticator auth = new Authenticator();

    @Pointcut ( "execution(* cc.gmem.aj.MessageCommunicator.deliver(..))" )
    public void secureAccess()
    {
    }

    @Before ( "secureAccess()" )
    public void secure()
    {
        System.out.println( "Checking and authenticating user" );
        auth .doAuthenticate();
    }
}</pre>
<div class="blog_h2"><span class="graybg">AspectJ切面示例</span></div>
<p>环绕通知的简单例子，注意proceed()的使用：</p>
<pre class="crayon-plain-tag">public aspect SecurityAspect
{
    private Authenticator auth = new Authenticator();

    pointcut secureAccess(): execution(* MessageCommunicator.deliver(..));

    Object around() : secureAccess() {
        long start = System.nanoTime();
        //伪关键字proceed，用于继续执行被通知的目标方法
        Object ret = proceed();
        //thisJoinPointStaticPart是通知可用变量之一，可以获取连接点的静态信息，例如方法名
        System.out.println( thisJoinPointStaticPart.getSignature() + " took " + ( end - start ) + " nanoseconds" );
        return ret;
    }
}</pre>
<p>上下文收集的简单例子：</p>
<pre class="crayon-plain-tag">package cc.gmem.aj;

import java.sql.Connection;
import java.sql.SQLException;

public aspect ConnectionAspect
{
    //匹配Connection的任何抛出SQLException方法的调用
    //被调用（target）的Connection对象收集为connection变量。
    pointcut connectionOperation( Connection connection )//独立定义的切入点，必须使用这种参数声明来传递上下文信息给通知
        : call(* Connection.*(..) throws SQLException) &amp;&amp; target(connection);

    Object around( Connection connection ) : connectionOperation(connection) {//传递了上下文
        long startTime = System.nanoTime();
        Object ret = proceed();
        //thisJoinPoint是通知体可用的变量之一
        System.out.println( "Operation " + thisJoinPoint + " on " + connection + " took " + ( System.nanoTime() - startTime ) );
        return ret;
    }
}</pre>
<p>上下文收集注解的例子：</p>
<pre class="crayon-plain-tag">before(Secured secured ) : 
    //匹配任何方法执行
    execution(* *(..)) 
    //匹配连接点目标————即方法本身————上具有@secured注解的连接点
    &amp;&amp; @annotation(secured)
{
    //注解可以像普通对象一样被使用
    checkPermission(secured.permission());
}</pre>
<p>&nbsp;</p>
<p>上下文收集的另外一个例子，注意传递给目标的参数被收集，可被通知体使用：</p>
<pre class="crayon-plain-tag">package cc.gmem.aj;

public aspect AccountAspect
{

    boolean processOverdraft( Account account, float amount )
    {
        return true;
    }

    void around( Account account, float amount ) throws InsufficientBalanceException
    //匹配任何对Account.debit(float) throws InsufficientBalanceException方法的调用
        : call(void Account.debit(float) throws InsufficientBalanceException)
        &amp;&amp; target(account) &amp;&amp; args(amount)//收集的上下文信息作为局部变量使用
    {
        try
        {
            proceed( account, amount );//传递给目标方法的参数，可以任意修改
        }
        catch ( InsufficientBalanceException ex )
        {
            //进行透支处理
            if ( !processOverdraft( account, amount ) )
            {
                throw ex;
            }
        }
    }
}</pre>
<div class="blog_h2"><span class="graybg">进行加载时织入（LTW）</span></div>
<p>在classpath的META-INF/aop.xml中，声明需要织入的切面：</p>
<pre class="crayon-plain-tag">&lt;aspectj&gt;
    &lt;aspects&gt;
        &lt;aspect name="cc.gmem.aj.SecurityAspect"/&gt;
    &lt;/aspects&gt;
&lt;/aspectj&gt;</pre>
<p>通过指定JVM启动参数-javaagent来启用LTW：</p>
<pre class="crayon-plain-tag">-javaagent:%ASPECTJ_HOME%\lib\aspectjweaver.jar</pre>
<p>如果使用Spring，可以通过配置，避免修改JVM启动参数</p>
<div class="blog_h2"><span class="graybg">AspectJ横切构造（crosscutting construct）</span></div>
<div class="blog_h3"><span class="graybg">通用横切构造</span></div>
<p>AspectJ支持连接点、切入点、切面等构造。</p>
<div class="blog_h3"><span class="graybg">动态横切构造：修改行为</span></div>
<p>AspectJ的动态横切构造依赖于“通知”实现。通知包括Before、After、Around几种。</p>
<p><b>收集连接点上下文</b></p>
<p>连接点上下文包括两种类型：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td> 连接点牵涉的对象</td>
<td>切入点：this(), target(), args()可以用于收集相应的对象可以使用类型（Type）或者对象名称（ObjectIdentifier）两种方式来收集，对于后者，必须在通知声明处声明相应的对象</td>
</tr>
<tr>
<td>连接点关联的注解</td>
<td>切入点：@this(), @target(), @args(), @annotation(),@within(), @withincode()可用于收集相应的注解</td>
</tr>
</tbody>
</table>
<p><strong>通过反射访问连接点上下文</strong></p>
<p>作为备选方式，AspectJ提供了基于反射机制的访问静态（不会随着连接点执行而变化的）、动态连接点上下文信息的方法。这种方式可以完全替换上一条进行动态上下文收集，但是具有以下缺点：</p>
<ol>
<li>性能较为低下</li>
<li>缺少静态语法检查</li>
<li>比较笨重</li>
</ol>
<p>通过AspectJ提供的通知体可用的特殊变量来使用这种反射API：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>thisJoinPoint</td>
<td>变量类型为JoinPoint。可以访问目标对象、执行对象、参数的信息，包括相应的注解信息。可以通过getStaticPart()方法访问静态信息</td>
</tr>
<tr>
<td>thisJoinPointStaticPart</td>
<td>变量类型为JoinPoint.StaticPart。可以访问源码位置、连接点类型、连接点签名。</td>
</tr>
<tr>
<td>thisEnclosingJoinPointStaticPart</td>
<td>变量类型为JoinPoint.StaticPart。用于访问包围的连接点静态上下文，所谓“包围的连接点”依连接点类型而不同，例如：
<ol>
<li>对于方法调用，包围连接点是caller的执行</li>
<li>对于异常处理，包围连接点是try-catch块所在方法</li>
</ol>
</td>
</tr>
</tbody>
</table>
<p><strong>两种访问连接点上下文的方式的对照表</strong></p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">切入点语法</td>
<td style="text-align: center;"> 反射方式</td>
</tr>
</thead>
<tbody>
<tr>
<td>pointcut pc(Account acc) : this(acc)</td>
<td>Account acc = (Account)thisJoinPoint.getThis()</td>
</tr>
<tr>
<td>pointcut pc(Account acc): target(acc)</td>
<td>Account acc = (Account)thisJoinPoint.getTarget()</td>
</tr>
<tr>
<td>pointcut pc(Account acc,Customer cust): args(acc, cust)</td>
<td>Object[] arguments = thisJoinPoint.getArgs();<br /> Account acc = (Account)arguments[0];<br /> Customer cust = (Customer)arguments[1];</td>
</tr>
<tr>
<td>poincut pc(Secure sec) : @this(annot);</td>
<td>Secure sec = thisJoinPoint.getThis().getClass().<br /> getAnnotation(Secure.class)</td>
</tr>
<tr>
<td>poincut pc(Secure sec) : @target(sec);</td>
<td>Secure sec = thisJoinPoint.getTarget().getClass().<br /> getAnnotation(Secure.class);</td>
</tr>
<tr>
<td>pointcut pc(Secure sec, Transactional tx) : @args(sec, tx);</td>
<td>Object[] arguments = thisJoinPoint.getArgs();<br /> Secure sec = arguments[0].getClass().<br /> getAnnotations(Secure.class);<br /> Transactional tx = arguments[1].getClass().<br /> getAnnotations(Transactional.class);</td>
</tr>
<tr>
<td>poincut pc(Sensitive sens) : @annotation(sec);</td>
<td>FieldSignature sig =<br /> (FieldSignature)thisJoinPointStaticPart.getSignature();<br /> Sensitive sens = sig.getField().getAnnotation(Sensitive.class);</td>
</tr>
<tr>
<td>pointcut pc(Secure sec) : @within(sec);</td>
<td>thisJoinPointStaticPart.getSignature().<br /> getDeclaringType().getAnnotation(Secure.class);</td>
</tr>
<tr>
<td>pointcut pc(Secure sec) : @withincode(sec);</td>
<td>MethodSignature sig =<br /> (MethodSignature)thisJoinPointStaticPart.getSignature();<br /> Secure sec = sig.getMethod().getAnnotation(Secure.class);</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">静态横切构造：修改结构</span></div>
<p><strong>静态横切总是在动态横切之前应用</strong>。</p>
<p>静态横切构造由：<strong>跨类型声明（inter-type declaration ，ITD，亦称“引入introduction)”）</strong>和<strong>织入时声明（weave-time declarations）</strong>组成<br /> <strong>跨类型声明（引入）</strong><br /> 可以修改类、接口、或者切面的静态结构，例如增加一个字段，添加一个接口。在一个切面中，声明其他类型的结构，故曰ITD。下面是一个例子：</p>
<pre class="crayon-plain-tag">package cc.gmem.aj;

public aspect TrackingAspect
{

    //声明MessageCommunicator实现接口AccessTracked，效果和Java标准语法一样
    declare      parents: MessageCommunicator implements AccessTracked;

    //声明AccessTracked类型的一个字段，所有它的子类型自动获取此字段
    private long AccessTracked.lastAccessedTime;

    //声明AccessTracked类型的两个方法，所有它的子类型自动继承这些方法
    public void AccessTracked.updateLastAccessedTime()
    {
        lastAccessedTime = System.currentTimeMillis();
    }

    public long AccessTracked.getLastAccessedTime()
    {
        return lastAccessedTime;
    }

    before( AccessTracked accessTracked ) : //声明了一个通知局部变量accessTracked
        execution(* AccessTracked+.*(..)) //AccessTracked子类型的任何方法
        &amp;&amp; !execution(* AccessTracked.*(..))//除了AccessTracked本身的方法
        &amp;&amp; this(accessTracked)//this对象收集为accessTracked变量
    {
        accessTracked.updateLastAccessedTime();
    }

    private static interface AccessTracked
    {
    }
}</pre>
<p><strong>成员引入的规则</strong></p>
<ol>
<li>只能引入private、public成员，前者只能被切面类访问，后者可以被系统中所有类访问</li>
<li>如果是private的，多个切面可以引入同名的成员</li>
<li>切面可以引入字段、函数、构造器到类或者接口。特别的，可以引入方法的实现到接口——缺省实现</li>
<li>如果切面引入一个和类中同名的方法，那么，类中的那个方法被保留</li>
<li>成员引入只能针对一个类型</li>
</ol>
<p><strong>接口缺省实现的例子</strong></p>
<pre class="crayon-plain-tag">package cc.gmem.aj;

public interface Nameable
{
    public void setName( String name );

    public String getName();
    //缺省实现
    static aspect Impl
    {
        private String Nameable.name;

        public void Nameable.setName( String name )
        {
            this.name = name;
        }

        public String Nameable.getName()
        {
            return this.name;
        }
    }
}</pre>
<p><strong>使用ITD来实现Java的Mixin机制</strong></p>
<p>Mixin是一种程序构造，允许在已有类中混入特定的逻辑，Java语言没有语言级别的机制，下面的例子示意了如何使用AspectJ进行混入</p>
<pre class="crayon-plain-tag">//一个标记性接口，用于混入功能
public interface BeanSupport {}
//一个简单的POJO，需要混入：属性变更监听的功能
public class Customer implements BeanSupport
{
    private String address;
    public String getAddress()
    {
        return address;
    }
    public void setAddress( String address )
    {
        this.address = address;
    }
}</pre>
<p>通过下面的代码，即可为Customer或者任何实现了BeanSupport的类型混入属性变更的功能，而没有任何的侵入性。</p>
<pre class="crayon-plain-tag">public aspect BeanMakerAspect
{

    private PropertyChangeSupport BeanSupport.propertyChangeSupport;
    public void BeanSupport.addPropertyChangeListener( PropertyChangeListener listener )
    {
        propertyChangeSupport.addPropertyChangeListener( listener );
    }
    public void BeanSupport.removePropertyChangeListener( PropertyChangeListener listener )
    {
        propertyChangeSupport.removePropertyChangeListener( listener );
    }
    pointcut beanCreation( BeanSupport bean )
    //匹配BeanSupport及其子类型的初始化连接点
    : initialization(BeanSupport+.new(..)) &amp;&amp; this(bean);

    pointcut beanPropertyChange( BeanSupport bean, Object newValue )
    //匹配BeanSupport及其子类型的任何Setter方法的执行
    : execution(void BeanSupport+.set*(*))
    &amp;&amp; args(newValue) &amp;&amp; this(bean);
    //BeanSupport初始化成功后，创建一个propertyChangeSupport字段的实例
    after( BeanSupport bean ) returning : beanCreation(bean) {
        bean.propertyChangeSupport = new PropertyChangeSupport( bean );
    }
    //Setter调用后，发布相应的事件
    void around( BeanSupport bean, Object newValue )  : beanPropertyChange(bean, newValue) 
    {
        String methodName = thisJoinPointStaticPart.getSignature().getName();
        String propertyName = Introspector.decapitalize( methodName.substring( 3 ) );
        Object oldValue = getPropertyValue( bean, propertyName );
        proceed( bean, newValue );
        bean.propertyChangeSupport.firePropertyChange( propertyName, oldValue, newValue );
    }
}</pre>
<p><strong>修改类层次结构</strong></p>
<p>AspectJ提供两种需要类层次的形式：</p>
<pre class="crayon-plain-tag">declare parents : [TypePattern] implements [InterfaceList];
declare parents : [TypePattern] extends [Class or InterfaceList];
//举例：所有实体类实现BeanSupport接口
public aspect EntityBeanParticipationAspect {
    declare parents: @Entity * implements BeanSupport;
}
//hasMethod、hasField，任何包含指定模式方法或者字段的类：
declare parents: hasmethod(@Observed * *(*)) implements BeanSupport;</pre>
<p><strong>引入注解</strong></p>
<p>AspectJ支持为字段、类、方法等添加注解</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 50%; text-align: center;">语法形式</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>declare @method: &lt;Method signature pattern&gt;:&lt;Annotation&gt;;</td>
<td>引入注解给AccountService的任何方法，例如：declare @method: * AccountService.*(..):<br /> @Transactional(Propagation.Required);</td>
</tr>
<tr>
<td>declare @constructor: &lt;Constructor signature pattern&gt;:&lt;Annotation&gt;;</td>
<td>引入注解给构造器，例如：declare @constructor: AccountService+.new():<br /> @ConfigurationOnly;</td>
</tr>
<tr>
<td>declare @field: &lt;Field signature pattern&gt;:&lt;Annotation&gt;;</td>
<td>引入注解给字段，例如：declare @field: * MissileInformation.*:<br /> @Classified;</td>
</tr>
<tr>
<td>declare @type: &lt;Type signature pattern&gt;:&lt;Annotation&gt;;</td>
<td>引入注解给类型，例如：declare @type: banking..* :<br /> @PrivacyControlled;</td>
</tr>
</tbody>
</table>
<p><b>简化异常处理</b></p>
<p>允许不强制捕获受查异常，声明方式如下：</p>
<pre class="crayon-plain-tag">//这里声明不需要强制捕获的异常
declare soft : &lt;ExceptionTypePattern&gt; : &lt;pointcut&gt;;</pre>
<p><strong>织入时声明——警告或者错误信息</strong></p>
<p>可以在织入时，根据一定的模式来生成错误或者警告信息，举例：</p>
<pre class="crayon-plain-tag">//当doAuthenticate方法在SecurityAspect以外调用时，生成警告
declare      warning: 
    call(void Authenticator.doAuthenticate()) //切入点：方法被调用的地方，与execution不同
    &amp;&amp; !within(SecurityAspect)  : //切入点，不在SecurityAspect内
        
        "Authentication should be performed only by SecurityAspect";</pre>
<div class="blog_h2"><span class="graybg">在Spring中使用AspectJ</span></div>
<div id="spring-aop-with-aspectj-config" class="blog_h3"><span class="graybg">使用Spring AOP，结合AspectJ风格的切面声明</span></div>
<p>这种场景下，本质上还是Spring基于JDK动态代理的AOP机制，只是借用AspectJ的语法。</p>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;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"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
    "&gt;
    &lt;!-- 提示Spring自动为目标对象创建代理 --&gt;
    &lt;aop:aspectj-autoproxy /&gt;
    &lt;!-- 声明普通Bean --&gt;
    &lt;bean id="messageCommunicator" class="cc.gmem.aj.MessageCommunicator" /&gt;
    &lt;!-- 声明切面Bean，注意，必须使用注解语法定义切面 --&gt;
    &lt;bean id="securityAspect" class="cc.gmem.aj.SecurityAspect" /&gt;
&lt;/beans&gt;</pre>
<div class="blog_h3"><span class="graybg">使用spring-aspects切面库</span></div>
<p>可以基于AspectJ，而不是Spring AOP来实现：事务管理、缓存、任务调度等常用AOP场景，以避免Spring AOP的固有缺陷。<br /> 特别是使用@Configurable注解来支持非Spring Bean的依赖注入，必须基于AspectJ完成。</p>
<pre class="crayon-plain-tag">&lt;!-- 支持非受管对象（Spring Bean）的依赖注入和生命周期注解 --&gt;
&lt;context:spring-configured /&gt;
&lt;!-- 支持基于AspectJ的事务管理 --&gt;
&lt;tx:annotation-driven transaction-manager="txManager" mode="aspectj" /&gt;
&lt;!-- 支持基于AspectJ的缓存机制--&gt;
&lt;cache:annotation-driven cache-manager="cacheManager" mode="aspectj" /&gt;
&lt;!-- 支持基于AspectJ的任务调度和执行 --&gt;
&lt;task:annotation-driven  scheduler="taskScheduler" executor="taskExecutor" mode="aspectj" /&gt;</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/aspectj-study-note">AspectJ编程学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/aspectj-study-note/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Spring与Quartz的任务调度比较</title>
		<link>https://blog.gmem.cc/comparision-of-spring-task-scheduler-and-quartz</link>
		<comments>https://blog.gmem.cc/comparision-of-spring-task-scheduler-and-quartz#comments</comments>
		<pubDate>Sat, 09 Apr 2011 06:01:14 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Cron]]></category>
		<category><![CDATA[Quartz]]></category>
		<category><![CDATA[Scheduler]]></category>
		<category><![CDATA[Spring]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1518</guid>
		<description><![CDATA[<p>任务调度代码比较 Spring 2.x 任务调度示例 [crayon-69e945c70f9a8239571282/] Spring 3.x 任务调度示例 配置文件方式 [crayon-69e945c70f9ac704994318/] 注解方式 如果使用该方式，必须声明task:annotation-driven，如果不使用aspectj，则mode应设置为proxy。 [crayon-69e945c70f9ae399072749/] 在Service类中，使用@Scheduled来标注需要调度的方法 [crayon-69e945c70f9b0649134722/] Quartz任务调度示例 [crayon-69e945c70f9b2094469588/] 特性比较 比较项 Spring2.x Spring3.x Quartz <a class="read-more" href="https://blog.gmem.cc/comparision-of-spring-task-scheduler-and-quartz">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/comparision-of-spring-task-scheduler-and-quartz">Spring与Quartz的任务调度比较</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">任务调度代码比较</span></div>
<div class="blog_h3"><span class="graybg">Spring 2.x 任务调度示例</span></div>
<pre class="crayon-plain-tag">&lt;bean id="demoJob" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"&gt;
    &lt;!-- 目标Bean --&gt;
    &lt;property name="targetObject"&gt;
        &lt;bean class="cc.gmem.demo.DemoService" /&gt;
    &lt;/property&gt;
    &lt;!-- 目标方法 --&gt;
    &lt;property name="targetMethod" value="doStuff" /&gt;
    &lt;!-- 防止并发执行 --&gt;
    &lt;property name="concurrent" value="false" /&gt;
&lt;/bean&gt;
&lt;!-- 简单触发器 --&gt;
&lt;bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"&gt;
    &lt;property name="jobDetail" ref="demoJob" /&gt;
    &lt;!-- 启动后，调度开始的时间 --&gt;
    &lt;property name="startDelay" value="0" /&gt;
    &lt;!-- 每隔2000ms调度一次 --&gt;
    &lt;property name="repeatInterval" value="2000" /&gt;
&lt;/bean&gt;
&lt;!-- Cron触发器 --&gt;
&lt;bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"&gt;
    &lt;property name="jobDetail" ref="demoJob" /&gt;
    &lt;property name="cronExpression" value="15 0/2 * * * ?" /&gt;
&lt;/bean&gt;
&lt;!-- 调度工厂Bean --&gt;
&lt;bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"&gt;
    &lt;property name="triggers"&gt;
        &lt;list&gt;
            &lt;ref bean="simpleTrigger" /&gt;
            &lt;ref bean="cronTrigger" /&gt;
        &lt;/list&gt;
    &lt;/property&gt;
&lt;/bean&gt;</pre>
<div class="blog_h3"><span class="graybg">Spring 3.x 任务调度示例</span></div>
<strong>配置文件方式</strong><br />
<pre class="crayon-plain-tag">&lt;!--  任务调度器配置，pool-size为线程池大小，限制了同时最多被调度的任务 --&gt;
&lt;task:scheduler pool-size="100" id="scheduler" /&gt;
&lt;!-- 任务计划列表，支持固定频率、固定延迟、Cron表达式等 --&gt;
&lt;task:scheduled-tasks scheduler="scheduler"&gt;
    &lt;task:scheduled fixed-rate="120000" method="doStuff" ref="demoService" /&gt;
    &lt;task:scheduled cron="15 0/2 * * * ?" method="doStuff" ref="demoService" /&gt;
&lt;/task:scheduled-tasks&gt;</pre><br />
<strong>注解方式</strong><br />
如果使用该方式，必须声明task:annotation-driven，如果不使用aspectj，则mode应设置为proxy。<br />
<pre class="crayon-plain-tag">&lt;task:annotation-driven scheduler="scheduler" executor="taskExecutor" mode="aspectj" /&gt;
&lt;task:scheduler pool-size="100" id="scheduler" /</pre><br />
在Service类中，使用@Scheduled来标注需要调度的方法<br />
<pre class="crayon-plain-tag">package cc.gmem.demo;
@Component("demoService") 
public class DemoService(){
    @Scheduled(cron = "15 0/2 * * * ?")  
    public void doStuff(){

    }
}</pre>
<div class="blog_h3"><span class="graybg">Quartz任务调度示例</span></div>
<pre class="crayon-plain-tag">import static org.quartz.JobBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.CronScheduleBuilder.*;

StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
Properties pros = new Properties();
pros.setProperty( "org.quartz.scheduler.instanceName", "MyScheduler" );
pros.setProperty( "org.quartz.threadPool.threadCount", "10" );//调度器线程数
pros.setProperty( "org.quartz.jobStore.class", "org.quartz.simpl.RAMJobStore" );//任务存储，可以用于持久化
schedulerFactory.initialize( pros );
Scheduler scheduler = schedulerFactory.getScheduler();
//启动调度器
scheduler.start();
JobDetail job = newJob( helloWorldJob.class ).build();
//简单触发器
Trigger trigger =  newTrigger().startNow().withSchedule( simpleSchedule().withIntervalInSeconds( 1 ).repeatForever() ).build();
//Cron表达式触发器
trigger = newTrigger().startNow().withSchedule( cronSchedule( "15 0/2 * * * ?" )).build();
scheduler.scheduleJob( job, trigger );

TimeUnit.SECONDS.sleep( 1800 );
//关闭调度器
scheduler.shutdown();</pre>
<div class="blog_h2"><span class="graybg">特性比较</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 100px; text-align: center;">比较项</td>
<td style="text-align: center;">Spring2.x</td>
<td style="text-align: center;">Spring3.x</td>
<td style="text-align: center;">Quartz</td>
</tr>
</thead>
<tbody>
<tr>
<td>优缺点</td>
<td>比较繁琐，除非是老项目维护的需要，不建议使用</td>
<td>配置简单，对注解、配置文件方式的支持均较好</td>
<td>比较强大，支持任务状态的持久化</td>
</tr>
<tr>
<td>重叠执行(overlap)</td>
<td>JobDetail中设置concurrent属性可以防止重叠执行</td>
<td>默认是不允许重叠执行的，如果需要，在目标方法上添加@Async注解</td>
<td>在Job的实现类上添加：@DisallowConcurrentExecution可以防止重叠执行</td>
</tr>
</tbody>
</table>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/comparision-of-spring-task-scheduler-and-quartz">Spring与Quartz的任务调度比较</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/comparision-of-spring-task-scheduler-and-quartz/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Spring知识集锦</title>
		<link>https://blog.gmem.cc/spring-faq</link>
		<comments>https://blog.gmem.cc/spring-faq#comments</comments>
		<pubDate>Thu, 17 Mar 2011 04:28:05 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[FAQ]]></category>
		<category><![CDATA[Spring]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1234</guid>
		<description><![CDATA[<p>配置 Spring各种注入方式的区别 注入方式  说明  @Resource 来源：JSR250 (Common Annotations for Java) 注入方式： 默认byName注入 通过@Qualifier可以强制 byQualifier。注意：如果byQualifier失败，则自动尝试byName，再次失败则抛出异常 如果byName失败，则尝试byType @Inject 来源：JSR330 (Dependency Injection for Java) 注入方式： <a class="read-more" href="https://blog.gmem.cc/spring-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/spring-faq">Spring知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">配置</span></div>
<div class="blog_h2"><span class="graybg">Spring各种注入方式的区别</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">注入方式 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 100px;">@Resource</td>
<td>来源：JSR250 (Common Annotations for Java)<br /> 注入方式：
<ol>
<li>默认byName注入</li>
<li>通过@Qualifier可以强制 byQualifier。注意：如果byQualifier失败，则自动尝试byName，再次失败则抛出异常</li>
<li>如果byName失败，则尝试byType</li>
</ol>
</td>
</tr>
<tr>
<td>@Inject</td>
<td>来源：JSR330 (Dependency Injection for Java)<br /> 注入方式：
<ol>
<li>默认byType注入</li>
<li>通过@Qualifier可以强制 byQualifier</li>
<li>如果byType注入失败（找不到依赖，或者多个依赖），则尝试byName</li>
</ol>
</td>
</tr>
<tr>
<td>@Autowired</td>
<td>来源：Spring私有<br /> 注入方式：与@Inject相同<br /> 支持required属性，可以配置为false，即使找不到依赖也不会抛出异常</td>
</tr>
<tr>
<td>@Qualifier</td>
<td>来源：Spring私有<br /> 配合上面注解之一使用，相当于一个分类器，在被依赖的Bean上、依赖字段上同时配置该注解，用于确定其关联性，例如：<br />
<pre class="crayon-plain-tag">@Service("as")
@Qualifier("default")
public class AService{

}

public class BService{
    @Resource
    @Qualifier("default")
    private AService aService;
}</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Spring事务传播属性</span></div>
<table class="full-width fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 35%; text-align: center;">传播属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>PROPAGATION_REQUIRED</td>
<td>支持当前事务，如果当前没有事务，就新建一个事务。这是最常见的选择  </td>
</tr>
<tr>
<td>PROPAGATION_SUPPORTS</td>
<td>支持当前事务，如果当前没有事务，就以非事务方式执行</td>
</tr>
<tr>
<td>PROPAGATION_MANDATORY</td>
<td>支持当前事务，如果当前没有事务，就抛出异常</td>
</tr>
<tr>
<td>PROPAGATION_REQUIRES_NEW</td>
<td>新建事务，如果当前存在事务，把当前事务挂起（方法结束后此新事务被提交）</td>
</tr>
<tr>
<td>PROPAGATION_NOT_SUPPORTED</td>
<td>以非事务方式执行操作，如果当前存在事务，就把当前事务挂起</td>
</tr>
<tr>
<td>PROPAGATION_NEVER</td>
<td>以非事务方式执行，如果当前存在事务，则抛出异常</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Bean各种Scope的区别</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">Scope</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td><span style="color: #313131;">singleton</span></td>
<td>默认Scope，这样的Bean在整个IoC容器的生命周期内仅仅有一个实例</td>
</tr>
<tr>
<td><span style="color: #313131;">prototype</span></td>
<td>每次向IoC容器请求时，都会得到一个全新的实例</td>
</tr>
<tr>
<td><span style="color: #313131;">request</span></td>
<td>仅仅在Web-aware的Application Context中才可用，在一个HTTP请求范围内，只有一个实例</td>
</tr>
<tr>
<td>session</td>
<td>仅仅在Web-aware的Application Context中才可用，在一个HTTP会话范围内，只有一个实例</td>
</tr>
<tr>
<td>global-session</td>
<td>仅仅在Web-aware的Application Context中才可用，在一个Servlet上下文范围内，只有一个实例</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">在配置时使用SpringEL</span></div>
<p>可以在基于XML、注解的配置中使用Spring EL，语法格式：</p>
<pre class="crayon-plain-tag">#{ &lt;expression string&gt; }</pre>
<p>XML配置示例：</p>
<pre class="crayon-plain-tag">&lt;!-- 调用静态方法 --&gt;
&lt;bean id="numberGuess" class="org.spring.samples.NumberGuess"&gt;
    &lt;property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/&gt;
&lt;/bean&gt;

&lt;!-- 你可以直接引用其它Bean的属性，或者调用其方法：--&gt;
&lt;property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/&gt;


&lt;!-- 变量systemProperties是预定义的，你可以通过它访问系统属性 --&gt;
&lt;property name="defaultLocale" value="#{ systemProperties['user.region'] }"/&gt;</pre>
<p>注解配置示例：</p>
<pre class="crayon-plain-tag"># 访问系统属性
@Value("#{systemProperties['user.country']}")
# 访问环境变量
#{systemEnvironment['DDEPUB_ORDINAL']}</pre>
<div class="blog_h2"><span class="graybg">Servlet Listener配置</span></div>
<div class="blog_h3"><span class="graybg">XML优先</span></div>
<pre class="crayon-plain-tag">&lt;!-- Spring --&gt;
&lt;context-param&gt;
    &lt;param-name&gt;contextClass&lt;/param-name&gt;
    &lt;param-value&gt;org.springframework.web.context.support.XmlWebApplicationContext&lt;/param-value&gt;
&lt;/context-param&gt;
&lt;context-param&gt;
    &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
    &lt;param-value&gt;classpath:application.xml&lt;/param-value&gt;
&lt;/context-param&gt;
&lt;listener&gt;
    &lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt;
&lt;/listener&gt;

&lt;!-- Spring MVC--&gt;
&lt;servlet&gt;
    &lt;servlet-name&gt;springmvc&lt;/servlet-name&gt;
    &lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;
    &lt;init-param&gt;
        &lt;param-name&gt;contextClass&lt;/param-name&gt;
        &lt;param-value&gt;org.springframework.web.context.support.XmlWebApplicationContext&lt;/param-value&gt;
    &lt;/init-param&gt;
    &lt;init-param&gt;
        &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
        &lt;param-value&gt;classpath:application-mvc.xml&lt;/param-value&gt;
    &lt;/init-param&gt;
&lt;/servlet&gt;
&lt;servlet-mapping&gt;
    &lt;servlet-name&gt;springmvc&lt;/servlet-name&gt;
    &lt;url-pattern&gt;/&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;</pre>
<p> 即，在web.xml中直接声明XML配置文件的路径，如果需要引入额外的XML配置，可以使用&lt;import&gt;指令：</p>
<pre class="crayon-plain-tag">&lt;import resource="core.xml"/&gt;</pre>
<p> 如果要引入额外的JavaConfig类，可以定义Bean：</p>
<pre class="crayon-plain-tag">&lt;bean class="cc.gmem.BeanDefinitionRegistrar" /&gt; </pre>
<div class="blog_h3"><span class="graybg">JavaConfig优先</span></div>
<pre class="crayon-plain-tag">&lt;!-- Spring --&gt;
&lt;context-param&gt;
    &lt;param-name&gt;contextClass&lt;/param-name&gt;
    &lt;param-value&gt;org.springframework.web.context.support.AnnotationConfigWebApplicationContext&lt;/param-value&gt;
&lt;/context-param&gt;
&lt;context-param&gt;
    &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
    &lt;param-value&gt;cc.gmem.SpringBeanDefinitionRegistrar&lt;/param-value&gt;
&lt;/context-param&gt;
&lt;listener&gt;
    &lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt;
&lt;/listener&gt;

&lt;!-- Spring MVC--&gt;
&lt;servlet&gt;
    &lt;servlet-name&gt;springmvc&lt;/servlet-name&gt;
    &lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;
    &lt;init-param&gt;
        &lt;param-name&gt;contextClass&lt;/param-name&gt;
        &lt;param-value&gt;org.springframework.web.context.support.AnnotationConfigWebApplicationContext&lt;/param-value&gt;
    &lt;/init-param&gt;
    &lt;init-param&gt;
        &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
        &lt;param-value&gt;cc.gmem.SpringMVCBeanDefinitionRegistrar&lt;/param-value&gt;
    &lt;/init-param&gt;
&lt;/servlet&gt;

&lt;servlet-mapping&gt;
    &lt;servlet-name&gt;springmvc&lt;/servlet-name&gt;
    &lt;url-pattern&gt;/&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;</pre>
<p>即，在web.xml中直接声明JavaConfig类的权限定名，如果需要引入额外的XML配置，需要在JavaConfig类中引入： </p>
<pre class="crayon-plain-tag">@Configuration
/* 引入额外的XML配置文件 */
@ImportResource ( "classpath:pems-application.xml" )
public class SpringBeanDefinitionRegistrar
{

    /* Auto injected spring context */
    @SuppressWarnings ( "unused" )
    @Inject
    private ApplicationContext applicationContext;

    /* 编程式的创建Bean，默认是单例的 */
    @Bean ( name = "helper" )
    public Helper helper()
    {
        Helper helper = new Helper();
        return helper;
    }

}

@Configuration
/* 引入额外的XML配置文件 */
@ImportResource ( "classpath:pems-application-mvc.xml" )
public class SpringMVCBeanDefinitionRegistrar
{

}</pre>
<p>JavaConfig方式中，尝试通过XML Bean的方式引入额外的JavaConfig类，会出现问题（3.1.2版本的Spring），行为不符合预期。</p>
<div class="blog_h2"><span class="graybg">资源路径URL</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">格式</td>
<td style="text-align: center;">含义</td>
</tr>
</thead>
<tbody>
<tr>
<td>classpath*:conf/appCtx.xml</td>
<td>类路径下所有的JAR中的conf/appCtx.xml并合并为一个上下文</td>
</tr>
<tr>
<td>classpath:conf/appCtx.xml</td>
<td>仅仅使用第一个找到的conf/appCtx.xml</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">@PostConstruct相关</span></div>
<div class="blog_h3"><span class="graybg">事务处理</span></div>
<p>即使合理配置了Hibernate Session Factory、TransactionManager，在@PostConstruct方法上也加了事务配置（@Transactional），依旧无法进行数据库操作，报错：No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here</p>
<p>原因：在此（InitializingBean.afterPropertiesSet ）阶段，无法保证所有PostProcessors 完成其工作</p>
<p>解决：有两种方案：</p>
<ol>
<li>注册 ContextRefreshedEvent 的监听器，在监听方法里，可以确保事务机制可用</li>
<li>在@PostConstruct方法里面使用TransactionTemplate，例如：<br />
<pre class="crayon-plain-tag">private PlatformTransactionManager transactionManager;
@PostConstruct
public void init() throws Throwable
{
    TransactionTemplate tt = new TransactionTemplate( transactionManager);
    tt.execute( new TransactionCallback&lt;Object&gt;() {

        @SuppressWarnings ( "unchecked" )
        public Object doInTransaction( TransactionStatus status )
        {
            List&lt;User&gt; = userService.getAllUsers();
            return null;
        }
    } );
}</pre>
</li>
</ol>
<div class="blog_h3"><span class="graybg">实例变量注入失败</span></div>
<p>如果@Configurable<span style="background-color: #c0c0c0;">无法在构造器里面</span>使用注入的实例变量的问题，是因为默认情况下，仅在构造器执行完毕后Spring才进行依赖注入，要改变此行为，可以配置：<pre class="crayon-plain-tag">@Configurable(preConstruction=true)</pre> </p>
<div class="blog_h2"><span class="graybg">动态注册Bean</span></div>
<div class="blog_h3"><span class="graybg">基于BeanFactory后处理机制</span></div>
<p>下面是一个动态注册Hibernate SessionFactory的例子：</p>
<pre class="crayon-plain-tag">ctx = new GenericApplicationContext();
   ctx.addBeanFactoryPostProcessor( new BeanFactoryPostProcessor() {
      @Override
      public void postProcessBeanFactory( ConfigurableListableBeanFactory bf ) throws BeansException
      {
          DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) bf;
          beanFactory.registerSingleton( "memDS", memDS );
          BeanDefinitionBuilder sffbBeanDefBuilder = BeanDefinitionBuilder
                  .rootBeanDefinition( TestHibernate3LocalSessionFactoryBean.class );
          sffbBeanDefBuilder.addPropertyReference( "dataSource", "memDS" );
          Properties hprops = new Properties();
          hprops.setProperty( "hibernate.dialect", "org.hibernate.dialect.HSQLDialect" );
          hprops.setProperty( "hibernate.jdbc.batch_size", "20" );
          hprops.setProperty( "hibernate.show_sql", "true" );
          hprops.setProperty( "hibernate.format_sql", "false" );
          hprops.setProperty( "hibernate.generate_statistics", "true" );
          hprops.setProperty( "hibernate.default_entity_mode", "dom4j" );
          hprops.setProperty( "hibernate.transaction.factory_class", "org.hibernate.transaction.JDBCTransactionFactory" );
          hprops.setProperty( "hibernate.current_session_context_class",
                  "org.springframework.orm.hibernate3.SpringSessionContext" );
          hprops.setProperty( "hibernate.default_entity_mode", "dom4j" );
          sffbBeanDefBuilder.addPropertyValue( "hibernateProperties", hprops );
          beanFactory.registerBeanDefinition( "sessionFactory", sffbBeanDefBuilder.getBeanDefinition() );
      }
  } );
ctx.refresh();</pre>
<div class="blog_h3"><span class="graybg">运行时注册</span></div>
<p>下面是运行时获取beanFactory引用，并注册任意Bean的例子：</p>
<pre class="crayon-plain-tag">BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition("cc.gmem.ClassQName"); 
builder.addPropertyReference("refProp", "refBeanId");
builder.addPropertyValue("prop", object);
BeanDefinitionRegistry registry  = (BeanDefinitionRegistry)this.beanFactory;
registry.registerBeanDefinition("myNewBean", builder.getBeanDefinition());   
registry.removeBeanDefinition("myNewBean");</pre>
<div class="blog_h2"><span class="graybg">如何集成Junit</span></div>
<p>参考下例：</p>
<pre class="crayon-plain-tag">import java.util.List;

import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;
/*设置单元测试运行类*/
@RunWith ( SpringJUnit4ClassRunner.class )
/*加载Spring配置文件*/
@ContextConfiguration ( { "/applicationContext.xml" } )
/*事务配置，defaultRollback 表示是否强制回滚事务 */
@TransactionConfiguration ( transactionManager = "txManager", defaultRollback = false )
public class HibernateTest
{
    private static final Logger LOGGER = LoggerFactory.getLogger( HibernateTest.class );

    @Test
    @Transactional
    public void testPolymorphicHqlQuery()
    {
        String hql = "from AbstractDevice ad where ad.status = 1";
        Session sess = Env.get( SessionFactory.class ).getCurrentSession();
        Query q = sess.createQuery( hql );
        @SuppressWarnings ( "unchecked" )
        List ads = q.list();
        for ( AbstractDevice ad : ads )
        {
            LOGGER.debug( ad.toString() );
        }
    }
}</pre>
<div class="blog_h2"><span class="graybg">Spring 4.0问题</span></div>
<div class="blog_h3"><span class="graybg">找不到类org.springframework.cache.ehcache.EhCacheCacheManager</span></div>
<p>添加依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework&lt;/groupId&gt;
    &lt;artifactId&gt;spring-context-support&lt;/artifactId&gt;
    &lt;version&gt;${spring.version}&lt;/version&gt;
&lt;/dependency&gt;</pre>
<div class="blog_h3"><span class="graybg">编写Lambda导致无法启动</span></div>
<p>报错信息：Exiting with throwable: java.lang.IllegalArgumentException: org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: URL [jar:file:]; nested exception is java.lang.ArrayIndexOutOfBoundsException: 51880</p>
<p>原因：Spring 3.x无法支持Java8的Lambda，需要升级到Spring 4.x</p>
<div class="blog_h2"><span class="graybg">零散问题</span></div>
<div class="blog_h3"><span class="graybg">spring-web版本错误导致： java.lang.NoSuchFieldError: NULL</span></div>
<p>原因：Maven传递依赖 cxf-rt-transports-http -&gt; spring-web-3.0.6，导致本来应该用的3.1.2被覆盖<br /> 解决：排除传递性依赖</p>
<div class="blog_h3"><span class="graybg">Spring注解方式报错：conflicts with existing, non-compatible bean definition of same name and class</span></div>
<div>原因：扫描目录包含@Configuration所在的包<br /> 解决：排除扫描@Configuration类，参考如下：</div>
<pre class="crayon-plain-tag">&lt;context:component-scan base-package="cc.gmem.demo"&gt;
    &lt;context:exclude-filter type="annotation" expression="org.springframework.context.annotation.Configuration" /&gt;</pre>
<div class="blog_h3"><span class="graybg">Spring AOP，如何指定通知的执行顺序</span></div>
<p>在Advice类上添加注解：数字越小，越先执行，例如：@Order(1)</p>
<div class="blog_h3"><span class="graybg">Spring AOP，通知不执行的问题</span></div>
<p>Spring AOP具有以下缺点：</p>
<ol>
<li>无法对通知类进行通知</li>
<li>目标对象调用自身的方法，则该方法上的通知不被执行（无法感知）——这是动态代理机制的本身特点决定的</li>
</ol>
<p>解决方法：</p>
<ol>
<li>避免被代理对象的自我调用</li>
<li>使用AopContext.currentProxy() 得到当前对象的代理，对代理调用被通知方法，注意，使用currentProxy时，应使用getter/setter，避免直接访问字段。需要配置：<br />
<pre class="crayon-plain-tag">&lt;!—基于AspectJ注解风格的Spring AOP事务配置--&gt; 
&lt;aop:aspectj-autoproxy expose-proxy="true"/&gt;
&lt;!—基于XML的声明式事务配置--&gt; 
&lt;aop:config expose-proxy="true"&gt;</pre>
</li>
</ol>
<div class="blog_h3"><span class="graybg">如何定义AspectJ风格的AOP切面</span></div>
<p>Spring配置文件的例子：<a href="/aspectj-study-note#spring-aop-with-aspectj-config" target="_blank">使用Spring AOP，结合AspectJ风格的切面声明</a></p>
<p>基于AspectJ注解的切面类的例子：</p>
<pre class="crayon-plain-tag">@Aspect //加这个注解表示为通知类
@Component
public class LoggerPointcutsDefinition    //切入点定义
{
    //注意：Spring AOP支持AspectJ切入点的子集
    @Pointcut ( "@annotation(cc.gmem.mgr.common.aop.AutoLogError)" )
    public void inServiceLayer(){}
}

@Aspect
@Component
public class LoggerAdvice                  //通知定义
{
    private static final Logger LOGGER = LoggerFactory.getLogger( LoggerAdvice.class );
 
    @AfterThrowing ( value = "cc.gmem.mgr.common.aop.LoggerPointcutsDefinition.inServiceLayer()", throwing = "t" )
    public void logError( Throwable t ) {
        LOGGER.error( t.getMessage(), t );
    }
}</pre>
<div class="blog_h3"><span class="graybg">FactoryBean的用法</span></div>
<p>FactoryBean接口包含方法：<pre class="crayon-plain-tag">getObject()</pre> <br /> 调用applicationContext.getBean("factoryBean")，得到的是factoryBean.getObject()方法的返回值。如果Bean是单例的，那么只会调用getObject()一次。<br /> 如果想得到<span style="background-color: #c0c0c0;">FactoryBean本身</span>，需要调用applicationContext.getBean("&amp;factoryBean")</p>
<div class="blog_h3"><span class="graybg">如何导入（import）位于Jar中的配置文件</span></div>
<pre class="crayon-plain-tag">&lt;import resource="classpath:spring-config.xml" /&gt; </pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/spring-faq">Spring知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/spring-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
