SpringBoot上传文件的临时目录无效的问题

刚启动程序, 上传图片, 一切正常, 过了几天后, 居然报错了:

org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; 
nested exception is java.io.IOException: The temporary upload location [/tmp/tomcat-docbase.4594736703313813956.8103/work/Tomcat/localhost/ROOT] is not valid
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:112)
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.(StandardMultipartHttpServletRequest.java:86)
.......

异常信息上看, 找不到目录”/tmp/tomcat-docbase.4594736703313813956.8103/work/Tomcat/localhost/ROOT”

这个目录是springboot启动后会默认创建的, 文件先上传到这个目录,再做处理, 处理完后会自动删掉.

因为在/tmp下, 所以一段时间后linux会自动清理掉这个目录, 所以就会报找不到目录的问题.

处理方案:

  1. 重启服务,重新生成目录;或者 手动创建该目录 (紧急处理)
  2. 修改配置: 在 application.yml中加入(前提是用tomcat)
server:
  tomcat:
    basedir: /data/app/tmp

3. 向spring容器中,注入自定义的MultipartConfigElement

@Inject
private Environment environment;

@Bean
@ConditionalOnProperty( {"app.dir.tmp"})
public MultipartConfigElement multipartConfigElement() {
  String baseDir = environment.getProperty("app.dir.tmp");
  MultipartConfigFactory configFactory = new MultipartConfigFactory();
  configFactory.setLocation(baseDir);
  return configFactory.createMultipartConfig();
}

Spring Boot 初始化过程

Spring Boot给我们带来最大的便捷就是很多组件都是默认装配好的, 那么SpringBooot的自动装配过程是啥样子?

看了<<SpringBoot实战>>, 了解到大概过程如下:

 1. springboot项目入口类的注解就是@SpringBootApplication, 这个注解主要包含了其他三个注解:@SpringBootConfiguration, @ComponentScan,@EnableAutoConfiguration;

其中自动装配的核心注解是@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
  //TODO:
}

2. @EnableAutoConfiguration引入了一个装配选择器: AutoConfigurationImportSelector(实现了ImportSelector接口), 通过 selectImports方法加载其他的初始化入口.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
  //TODO:
}

在SpringBoot 1.X中, 使用SpringFactoriesLoader.loadFactories方法来扫描具有配置文件 META-INF/spring.factories 的jar包;

在SpringBoot 2.X中,  使用AutoConfigurationMetadataLoader.loadMetadata方法来扫描具有配置文件 META-INF/spring-autoconfigure-metadata.properties 的jar包;

org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration.Configuration=
org.springframework.boot.devtools.remote.client.RemoteClientConfiguration.Configuration=
org.springframework.boot.devtools.autoconfigure.RemoteDevToolsAutoConfiguration.Configuration=
org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration=
org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration.Configuration=
org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration=
org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.devtools.remote.client.RemoteClientConfiguration=
org.springframework.boot.devtools.autoconfigure.RemoteDevToolsAutoConfiguration.ConditionalOnClass=javax.servlet.Filter,org.springframework.http.server.ServerHttpRequest
org.springframework.boot.devtools.autoconfigure.RemoteDevToolsAutoConfiguration=

3. 配置文件中的每一项都是模块的初始化入口, 这里举CacheAutoConfiguration为例:

@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
		RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class)
public class CacheAutoConfiguration {
  //TODO:
}

同样的, 它也引入了一个装配选择器:CacheConfigurationImportSelector, 通过 selectImports方法, 加载不同cache框架的初始化配置.

final class CacheConfigurations {

	private static final Map<CacheType, Class<?>> MAPPINGS;

	static {
		Map<CacheType, Class<?>> mappings = new EnumMap<>(CacheType.class);
		mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class);
		mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class);
		mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class);
		mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class);
		mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class);
		mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class);
		mappings.put(CacheType.REDIS, RedisCacheConfiguration.class);
		mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class);
		mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);
		mappings.put(CacheType.NONE, NoOpCacheConfiguration.class);
		MAPPINGS = Collections.unmodifiableMap(mappings);
	}
  ......
}

这里以redis为例, 加载了RedisCacheConfiguration配置

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
  //TODO:
}

4. 初始化配置一般都含有@Conditional 注解(ConditionalOnBean, ConditionalOnMissingBean都是基于Conditional), 用于判断当前类是否需要初始化到spring容器中.

这里举CacheCondition为例:

class CacheCondition extends SpringBootCondition {

	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context,
			AnnotatedTypeMetadata metadata) {
		String sourceClass = "";
		if (metadata instanceof ClassMetadata) {
			sourceClass = ((ClassMetadata) metadata).getClassName();
		}
		ConditionMessage.Builder message = ConditionMessage.forCondition("Cache",
				sourceClass);
		Environment environment = context.getEnvironment();
       .........
}

CacheCondition继承自SpringBootCondition(实现了Condition接口), 实现getMatchOutcome方法, 返回ConditionOutcome, 用于判断是否初始化.

public class ConditionOutcome {

  private final boolean match;

  private final ConditionMessage message;
  ...........
}

match为true, 则初始化; 否则, springboot不会初始化RedisCacheConfiguration.

5. @Conditional由ConditionEvaluator处理, 而ConditionEvaluator和ImportSelector, 最终被ConfigurationClassParser调用

(processConfigurationClass->doProcessConfigurationClass->processImports)

  不对之处, 敬请指点.

Springboot配置使用ssl,Nginx配置https

SSL(Secure Sockets Layer 安全套接层)是为网络通信提供安全及数据完整性的一种安全协议,SSL在网络传输层对网络连接进行加密,SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通信提供安全支持。SSL协议分为两层,SSL记录协议建立在TCP之上,为高层协议提供数据封装、压缩、加密等基本功能支持。SSL握手协议建立在SSL记录协议之上,用户实际数据传输开始前进行身份验证、协商加密算法、交换加密秘钥。

1.生成证书,可以使自签名或者从SSL证书授权中心获得的。

JDK中keytool是一个证书管理工具,可以生成自签名证书。 
本人这里用的系统是deepin,然后生成命令如下(找不到keytoo命令的先去配置java环境) 
我指定的名字叫tomcat.keystore 别名叫tomcat,密码自己设置,我这里用的tomcat,最后那个直接按得回车

keytool -genkey -alias tomcat -keyalg RSA -keystore /home/gzr/tomcat.keystore

2.SpringBoot配置SSL

1. 将这个tomcat.keystore拷贝到项目资源目录resources下

2. 然后配置application.yml

server:
  port: 8443
  session:
    timeout: 3000
  ssl:
    key-store: classpath:tomcat.keystore
    key-store-password: 123456
    key-store-type: PKCS12
    key-alias: tomcat

此刻启动项目,输入 https://localhost:8443/即可

3.Http自动跳转到Https

看下实现吧,在运行主类里面,加入如下代码

package com.caul;

import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class SpringbootmyApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootmyApplication.class, args);
    }

    /**
     * it's for set http url auto change to https
     */
    @Bean
    public EmbeddedServletContainerFactory servletContainer(){
        TomcatEmbeddedServletContainerFactory tomcat=new TomcatEmbeddedServletContainerFactory(){
            @Override
            protected void postProcessContext(Context context) {
                SecurityConstraint securityConstraint=new SecurityConstraint();
                securityConstraint.setUserConstraint("CONFIDENTIAL");//confidential
                SecurityCollection collection=new SecurityCollection();
                collection.addPattern("/*");
                securityConstraint.addCollection(collection);
                context.addConstraint(securityConstraint);
            }
        };
        tomcat.addAdditionalTomcatConnectors(httpConnector());
        return tomcat;
    }

    @Bean
    public Connector httpConnector(){
        Connector connector=new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        connector.setPort(8080);
        connector.setSecure(false);
        connector.setRedirectPort(8443);
        return connector;
    }

}

此时运行http://localhost:8080,会自动跳转到https://localhost:8443

4.Nginx的配置

  • 先签发证书
############ 证书颁发机构
# CA机构私钥
openssl genrsa -out ca.key 2048
# CA证书
openssl req -x509 -new -key ca.key -out ca.crt
############ 服务端
# 生成服务端私钥
openssl genrsa -out server.key 2048
# 生成服务端证书请求文件
openssl req -new -key server.key -out server.csr
# 使用CA证书生成服务端证书  关于sha256,默认使用的是sha1,在新版本的chrome中会被认为是不安全的,因为使用了过时的加密算法。
openssl x509 -req -sha256 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out server.crt    
# 打包服务端的资料为pkcs12格式(非必要,只是换一种格式存储上一步生成的证书) 生成过程中,需要创建访问密码,请记录下来。
openssl pkcs12 -export -in server.crt -inkey server.key -out server.pkcs12

新建任意名的配置文件在/etc/nginx/conf.d/下

upstream test {
  server 127.0.0.1:8443;
}

server {
  listen 443;
  server_name wx.baidu.com;
  ssl on;
  ssl_certificate  /home/er/https/server.crt;
  ssl_certificate_key  /home/er/https/server.key;

  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-Nginx-Proxt true;

    proxy_pass https://test;
    proxy_redirect off;
  }
}

重启nginx即可:

sudo nginx -s reload

Spring–AOP原理及拦截器

原理

AOP(Aspect Oriented Programming),也就是面向方面编程的技术。AOP基于IoC基础,是对OOP的有益补充。

  AOP将应用系统分为两部分,核心业务逻辑(Core businessconcerns)及横向的通用逻辑,也就是所谓的方面Crosscutting enterprise concerns,例如,所有大中型应用都要涉及到的持久化管理(Persistent)、事务管理(TransactionManagement)、安全管理(Security)、日志管理(Logging)和调试管理(Debugging)等。

  AOP正在成为软件开发的下一个光环。使用AOP,你可以将处理aspect的代码注入主程序,通常主程序的主要目的并不在于处理这些aspect。AOP可以防止代码混乱。

  Springframework是很有前途的AOP技术。作为一种非侵略性的、轻型的AOP framework,你无需使用预编译器或其他的元标签,便可以在Java程序中使用它。这意味着开发团队里只需一人要对付AOP framework,其他人还是像往常一样编程。

  AOP概念

  让我们从定义一些重要的AOP概念开始。

  — 方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的Advisor或拦截器实现。

  — 连接点(Joinpoint):程序执行过程中明确的点,如方法的调用或特定的异常被抛出。

  — 通知(Advice):在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。

  — 切入点(Pointcut):指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点,例如,使用正则表达式。

  — 引入(Introduction):添加方法或字段到被通知的类。Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现IsModified接口,来简化缓存。

  — 目标对象(Target Object):包含连接点的对象,也被称作被通知或被代理对象。

  — AOP代理(AOP Proxy):AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。

  — 编织(Weaving):组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

  各种通知类型包括:

  —  Around通知:包围一个连接点的通知,如方法调用。这是最强大的通知。Aroud通知在方法调用前后完成自定义的行为,它们负责选择继续执行连接点或通过返回它们自己的返回值或抛出异常来短路执行。

  —  Before通知:在一个连接点之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。

  —  Throws通知:在方法抛出异常时执行的通知。Spring提供强制类型的Throws通知,因此你可以书写代码捕获感兴趣的异常(和它的子类),不需要从Throwable或Exception强制类型转换。

  —  After returning通知:在连接点正常完成后执行的通知,例如,一个方法正常返回,没有抛出异常。

  Around通知是最通用的通知类型。大部分基于拦截的AOP框架(如Nanning和Jboss 4)只提供Around通知。

  如同AspectJ,Spring提供所有类型的通知,我们推荐你使用最为合适的通知类型来实现需要的行为。例如,如果只是需要用一个方法的返回值来更新缓存,你最好实现一个after returning通知,而不是around通知,虽然around通知也能完成同样的事情。使用最合适的通知类型使编程模型变得简单,并能减少潜在错误。例如,你不需要调用在around通知中所需使用的MethodInvocation的proceed()方法,因此就调用失败。

  切入点的概念是AOP的关键,它使AOP区别于其他使用拦截的技术。切入点使通知独立于OO的层次选定目标。例如,提供声明式事务管理的around通知可以被应用到跨越多个对象的一组方法上。 因此切入点构成了AOP的结构要素。

 拦截器(也称拦截机)

    拦截机 (Interceptor), 是 AOP (Aspect-Oriented Programming) 的另一种叫法。AOP本身是一门语言,只不过我们使用的是基于JAVA的集成到Spring 中的 SpringAOP。同样,我们将通过我们的例子来理解陌生的概念。

   接口类

 

package com.test.TestSpring3; 

 

public interface UserService // 被拦截的接口 

...{ 

   public void printUser(String user); 

} 

 实现类

 

package com.test.TestSpring3; 

 

public class UserServiceImp implements UserService// 实现UserService接口 

...{ 

   public void printUser(String user) ...{ 

       System.out.println("printUser user:" + user);// 显示user 

   } 

} 

  

 AOP拦截器

package com.test.TestSpring3; 

 

import org.aopalliance.intercept.MethodInterceptor; 

importorg.aopalliance.intercept.MethodInvocation; 

 

public class UserInterceptor implementsMethodInterceptor 

// AOP方法拦截器 

...{ 

 

   public Object invoke(MethodInvocation arg0) throws Throwable ...{ 

 

       try ...{ 

 

           if(arg0.getMethod().getName().equals("printUser")) 

           // 拦截方法是否是UserService接口的printUser方法 

           ...{ 

                Object[] args =arg0.getArguments();// 被拦截的参数 

               System.out.println("user:" + args[0]); 

               arg0.getArguments()[0] ="hello!";// 修改被拦截的参数 

 

           } 

 

           System.out.println(arg0.getMethod().getName() + "---!"); 

           return arg0.proceed();// 运行UserService接口的printUser方法 

 

       } catch (Exception e) ...{  

           throw e; 

       } 

   } 

}

 

 测试类

 

package com.test.TestSpring3; 

 

importorg.springframework.beans.factory.BeanFactory; 

 

importorg.springframework.beans.factory.xml.XmlBeanFactory; 

import org.springframework.context.ApplicationContext; 

importorg.springframework.context.support.ClassPathXmlApplicationContext; 

importorg.springframework.context.support.FileSystemXmlApplicationContext; 

importorg.springframework.core.io.ClassPathResource; 

import org.springframework.core.io.Resource; 

importorg.springframework.web.context.support.WebApplicationContextUtils; 

 

public class TestInterceptor ...{ 

 

   public static void main(String[] args) ...{ 

       ApplicationContext ctx = new FileSystemXmlApplicationContext( 

               "classpath:applicationContext.xml"); 

//       ApplicationContext ctx = newClassPathXmlApplicationContext("applicationContext.xml");     

         

       UserService us = (UserService)ctx.getBean("userService"); 

       us.printUser("shawn"); 

 

   } 

}

 

 配置文件

<?xml version="1.0"encoding="UTF-8"?> 

<!DOCTYPE beans PUBLIC"-//SPRING//DTD BEAN//EN""http://www.springframework.org/dtd/spring-beans.dtd"> 

<beans> 

   <bean id="userServiceImp" 

       class="com.test.TestSpring3.UserServiceImp" /> 

 

   <bean id="userInterceptor"class="com.test.TestSpring3.UserInterceptor" /> 

 

   <bean id="userService" 

       class="org.springframework.aop.framework.ProxyFactoryBean"> 

     <!-- 代理接口--> 

        <propertyname="proxyInterfaces"> 

           <value>com.test.TestSpring3.UserService</value> 

       </property> 

      <!-- 目标实现类--> 

       <property name="target"> 

           <ref local="userServiceImp" />  

     </property> 

        <!-- 拦截器 --> 

       <property name="interceptorNames"> 

           <list> 

               <value>userInterceptor</value> 

           </list> 

       </property> 

   </bean> 

 

</beans>

  

 输出:

 user:shawn

  printUser—!

 printUser user:hello!

 

结论:调用方法的时候 传入的值被拦截修改了.

拦截器中的事务管理(事务拦截机)

 如果不采用拦截机的机制时,在使用JDBC进行数据库访问时,存在两种情况:

自动提交        这是JDBC驱动默认的模式,每次数据库操作(CRUD)成功完成后,都作为一个单独的事务自动提交,如果未成功完成,即抛出了 SQLException 的话,仅最近的一个操作将回滚。

非自动提交    这是想更好的控制事务时需要程序地方式进行控制:

在进行该事务单元的任何操作之前 setAutoCommit(false)

在成功完成事务单元后commit()

在异常发生后rollback()

自动提交模式是不被推荐的,因为每个操作都将产生一个事务点,这对于大的应用来说性能将受到影响;再有,对于常见的业务逻辑,这种模式显得无能为力。比如:

转帐,从A帐户取出100元,将其存入B帐户;如果在这两个操作之间发生了错误,那么用户A将损失了100元,而本来应该给帐户B的,却因为失败给了银行。

所以,建议在所有的应用中,如果使用 JDBC 都将不得不采用非自动提交模式(你们要能发现了在我们的 JDBC 那个例子中,我们采用的就是自动提交模式,我们是为了把精力放在JDBC上,而不是事务处理上),即我们不得不在每个方法中:

try {     

 // 在获得连接后,立即通过调用 setAutoCommit(false) 将事务处理置为非自动提交模式  // Prepare Query to fetch the userInformation        

    pst = conn.prepareStatement(findByName);                  

      // ...           conn.commit();        

 } catch(Exception ex) {        

    conn.rollback();       

     throw ex;        

 }finally {    

        try {     

          // Close Result Set and Statement   

         if (rset != null) rset.close();                

        if (pst != null) pst.close();                         

    }catch (Exception ex) {                

      ex.printStackTrace();                 

      throw new Exception("SQL Error while closing objects = " +ex.toString());             

}   

}

  

 这样代码在AOP的倡导者看来是“肮脏”的代码。他们认为,所有的与事务有关的方法都应当可以集中配置(见声明性事务控制),并自动拦截,程序应当关心他们的主要任务,即商业逻辑,而不应和事务处理的代码搅和在一起。

 

我先看看 Spring 是怎么做到拦截的:

Spring 内置支持的事务处理拦截机

这里因为要用到JpetStore项目中的代码,我们将 applicationContext.xml 全部内容列出:

 

<?xml version="1.0" encoding="UTF-8"?>

<!--

  -Application context definition for JPetStore's business layer.

  -Contains bean references to the transaction manager and to the DAOs in

  -dataAccessContext-local/jta.xml (see web.xml's"contextConfigLocation").
   Jpetstore 的应用上下文定义,包含事务管理和引用了在 dataAccessContext-local/jta.xml(具体使用了哪个要看 web.xml 中的 'contextConfigLocation' 的配置)中注册的DAO

-->

<beans xmlns="http://www.springframework.org/schema/beans"

      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

      xmlns:aop="http://www.springframework.org/schema/aop"

      xmlns:tx="http://www.springframework.org/schema/tx"

      xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.0.xsd

      http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.0.xsd

http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

   <!-- ========================= GENERAL DEFINITIONS========================= -->

 

   <!-- Configurer that replaces ${...} placeholders with values fromproperties files

        占位符的值将从列出的属性文件中抽取出来

    -->

   <!-- (in this case, mail and JDBC related properties) -->

   <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

       <property name="locations">

           <list>

               <value>WEB-INF/mail.properties</value>

               <value>WEB-INF/jdbc.properties</value>

           </list>

       </property>

   </bean>

   <!-- MailSender used by EmailAdvice

        指定用于发送邮件的javamail 实现者,这里使用了spring 自带的实现。此 bean 将被 emailAdvice 使用
    -->

   <bean id="mailSender"class="org.springframework.mail.javamail.JavaMailSenderImpl">

       <property name="host" value="${mail.host}"/>

   </bean>
 

   <!-- ========================= BUSINESS OBJECT DEFINITIONS======================== -->

 

   <!-- 不需要,因为被SpringMVC 的实现使用 Genericvalidator for Account objects, to be used for example by the Spring web tier-->

   <bean id="accountValidator"class="org.springframework.samples.jpetstore.domain.logic.AccountValidator"/>
  

   <!-- 不需要,因为被SpringMVC 的实现使用 Genericvalidator for Order objects, to be used for example by the Spring web tier-->

   <bean id="orderValidator"class="org.springframework.samples.jpetstore.domain.logic.OrderValidator"/>
   

   <!--

       主要的商业逻辑对象,即我们所说的门面对象

       注入了所有的DAO,这些DAO是引用了 dataAccessContext-xxx.xml 中定义的DAO

       门面对象中的所有方法的事务控制将通过下面的 aop:config 来加以控制

     - JPetStore primary business object (default implementation).

     - Transaction advice gets applied through the AOP configuration below.

  -->

   <bean id="petStore"class="org.springframework.samples.jpetstore.domain.logic.PetStoreImpl">

       <property name="accountDao" ref="accountDao"/>

       <property name="categoryDao"ref="categoryDao"/>

       <property name="productDao" ref="productDao"/>

       <property name="itemDao" ref="itemDao"/>

       <property name="orderDao" ref="orderDao"/>

   </bean>
  

   <!-- ========================= ASPECT CONFIGURATION======================== -->

   <!-- AOP配置,用来控制哪些方法将需要进行事务处理,采用了AspectJ 的语法 -->

   <aop:config>

       <!--

       This definition creates auto-proxy infrastructure based on the givenpointcut,

       expressed in AspectJ pointcut language. Here: applying the advice named

       "txAdvice" to all methods on classes named PetStoreImpl.
   -->

       <!-- 指出在PetStoreFacade 的所有方法都将采用txAdvice(在紧接着的元素中定义了)事务方针,注意,我们这里虽然指定的是接口 PetStoreFacace, 但其暗示着其所有的实现类也将

        同样具有这种性质,因为本身就是实现类的方法在执行的,接口是没有方法体的。 -->

       <aop:advisor pointcut="execution(**..PetStoreFacade.*(..))" advice-ref="txAdvice"/>

       

       <!--

           This definition creates auto-proxy infrastructure based on the givenpointcut,

           expressed in AspectJ pointcut language. Here: applying the advice named

           "emailAdvice" to insertOrder(Order) method of PetStoreImpl

   -->

       <!-- 当执行PetStoreFacade.insertOrder方法,该方法最后一个参数为Order类型时(其实我们的例子中只有一个 insertOrder 方法,但这告诉了我们,当我们的接口或类中有重载了的方法,

        并且各个重载的方法可能使用不同的拦截机机制时,我们可以通过方法的参数加以指定),将执行emailAdvice(在最后定义的那个元素)-->

       <aop:advisor pointcut="execution(**..PetStoreFacade.insertOrder(*..Order))" advice-ref="emailAdvice"/>
   </aop:config>

   <!--
  事务方针声明,用于控制采用什么样的事务策略

       Transaction advice definition, based on method name patterns.

       Defaults to PROPAGATION_REQUIRED for all methods whose name starts with

       "insert" or "update", and to PROPAGATION_REQUIREDwith read-only hint

       for all other methods.
   -->

   <tx:advice id="txAdvice">

       <tx:attributes>

           <tx:method name="insert*"/>

           <tx:method name="update*"/>

           <tx:method name="*" read-only="true"/>

       </tx:attributes>

   </tx:advice>


   <!-- 拦截机,用于在适当的时机(通过AOP配置,如上面)在方法执行成功后发送邮件

     AOP advice used to send confirmation email after order has beensubmitted -->

   <!-- -->

   <bean id="emailAdvice" class="org.springframework.samples.jpetstore.domain.logic.SendOrderConfirmationEmailAdvice">

       <property name="mailSender" ref="mailSender"/>

   </bean>

   <!-- ========================= 忽略 REMOTE EXPORTER DEFINITIONS ======================== -->

</beans>

 

这个配置比想象的要简单的多:

<aop:config>        

 <!-- This definition creates auto-proxyinfrastructure based on the given pointcut, expressed in AspectJ pointcutlanguage.  

Here: applying the advice named        "txAdvice" to all methods onclasses named PetStoreImpl. 指出在 PetStoreFacade  

的所有方法都将采用txAdvice(在紧接着的元素中定义了)事务方针,注意,我们这里虽然指定的是接口 PetStoreFacace,         

 但其暗示着其所有的实现类也将同样具有这种性质,因为本身就是实现类的方法在执行的,接口是没有方法体的。    -->    

      <aop:advisor pointcut="execution(* *..PetStoreFacade.*(..))"advice-ref="txAdvice"/>                

 <!-- 其它拦截机-->   

</aop:config>

 

1. 所有的拦截机配置都放在 <aop:config> 配置元素中.

2. 下面还是需要理解一下几个有关AOP的专用名词,不过,是挺抽象的,最好能会意出其的用意

pointcut 切入点,比如:updateAccount 方法需要进行事务管理,则这个切入点就是“执行方法体”(execution)。Spring 所有支持的切入点类型在都在 Spring reference: 6.2.3.1. Supported Pointcut Designators 中列出了。

advice   要对这个切入点进行什么操作,比如事务控制

advisor  Spring 特有的概念,将上两个概念合到一个概念中来,即一个 advisor 包含了一个切入点及对这个切入点所实施的操作。

因为 方法执行切入点 execution 为最常见的切入点类型,我们着重介绍一下,execution 的完全形式为:

execution(modifiers-pattern?ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)

这是一个正则表达式,其中由’?’ 结尾的部分是可选的。翻译过来就是:

执行(方法访问修饰符? 方法返回类型 声明类型? 方法名(方法参数类型) 抛出异常?)

所有的这些都是用来定义执行切入点,即那些方法应该被侯选为切入点:

方法访问修饰符   即 public, private 等等

方法返回类型       即方法返回的类型,如 void, String 等等

声明类型                1.5的语法,现在可以先忽略它

方法名                    方法的名字

方法参数类型       方法的参数类型

抛出异常                方法声明的抛出的异常

 

例如,所有dao代码被定义在包 com.xyz.dao 及子包 com.xyz.dao.hibernate, 或者其它,如果还有的话,子包中, 里面定义的是提供DAO功能的接口或类,那么表达式:

execution(* com.xyz.dao..*.*(..))

表示切入点为:执行定义在包com.xyz.dao 及其子包(因为 .. 所致) 中的任何方法

 

详细情况可以参见Spring refernce: 6.2.3.4. Examples

 

因此这个表达式为执行定义在类PetStoreFacade 及其实现类中的所有方法,采取的动作定义在 txAdvice 中. 关于该 advice 的定义,(见声明性事务控制)一节

<aop:advisor pointcut=”execution(**..PetStoreFacade.*(..))” advice-ref=”txAdvice”/>

 

Spring 自定拦截机

来为了进行事务控制,我们只需简单地配置几下,所有的工作都由 Spring 来做。这样固然很好,但有时我们需要有我们特有的控制逻辑。因为Spring 不可能包含所有人需要的所有拦截机。所以它提供了通过程序的方式加以定制的方式。我们的项目中就有这么一个拦截机,在用户确认付款后,将定单信息通过 email 的方式发送给注册用户的邮箱中。

 

<aop:config>

  ...

 

       <!-- 当执行 PetStoreFacade.insertOrder方法,该方法最后一个参数为Order类型时(其实我们的例子中只有一个 insertOrder 方法,但这告诉了我们,当我们的接口或类中有重载了的方法,

        并且各个重载的方法可能使用不同的拦截机机制时,我们可以通过方法的参数加以指定),将执行emailAdvice(在最后定义的那个元素)-->

       <aop:advisor pointcut="execution(**..PetStoreFacade.insertOrder(*..Order))"advice-ref="emailAdvice"/>

       

   </aop:config>

红色的注释已经说的很清楚这个Advisor 了,它的切入点(pointcut)为PetStoreFacade 的 voidinsertOrder(Order order) 方法,采取的动作为引用的 emailAdvice, 下面我们就来看看 emailAdvice:

 

   <bean id=”emailAdvice”class=”org.springframework.samples.jpetstore.domain.logic.SendOrderConfirmationEmailAdvice”>

       <property name=”mailSender” ref=”mailSender”/>

   </bean>

它给了这个 advice 的实现类为 logic 包中 SendOrderConfirmationEmailAdvice,该Bean 引用了我们前面定义的邮件发送器(一个 Spring 内置的邮件发送器).

 

下面看看这个实现类:

 

public classSendOrderConfirmationEmailAdvice implements AfterReturningAdvice,InitializingBean {

   // user jes on localhost

   private static final String DEFAULT_MAIL_FROM ="test@pprun.org";

   

   private static final String DEFAULT_SUBJECT = "Thank you for yourorder!";

   

   private final Log logger = LogFactory.getLog(getClass());

   

   private MailSender mailSender;

   

   private String mailFrom = DEFAULT_MAIL_FROM;

   

   private String subject = DEFAULT_SUBJECT;

   

   public void setMailSender(MailSender mailSender) {

       this.mailSender = mailSender;

    }

   

   public void setMailFrom(String mailFrom) {

       this.mailFrom = mailFrom;

    }

   

   public void setSubject(String subject) {

       this.subject = subject;

    }

    

   public void throws Exception {

       if (this.mailSender == null) {

           throw new IllegalStateException("mailSender is required");

       }

    }

   

   /**

    *

    * @param returnValue 被拦截的方法的返回值

    * @param m 被拦截的方法的所有信息(Method类封装了这些信息)

    * @param args 被拦截的方法的所有参数组成的数组

    * @param target 目标对象,对于方法执行来说,即是方法所在的类的实例(与 this 同,批当前对象)

    * @throws java.lang.Throwable

    */

   public void afterReturning(Object returnValue, Method m, Object[] args,Object target) throws Throwable {

       // 我们被拦截的方法为 voidinsertOrder(Order order),方法只有一个参数,所以可知数据的第1个元素即是被传进的 order对象

       // 得到了order 对象,就可以将 order 对应的帐户名及帐单号发送到邮件中,以便确认无误。

       Order order = (Order) args[0];

       Account account = ((PetStoreFacade) target).getAccount(order.getUser().getUsername());

       

       // don't do anything if email address is not set

       if (account.getEmail() == null || account.getEmail().length() == 0) {

           return;

       }

       

       StringBuffer text = new StringBuffer();

       text.append("Dear ").append(account.getFirstname()).

                append('').append(account.getLastname());

       text.append(", thank your for your order from JPetStore. " +

                "Please note that yourorder number is ");

        text.append(order.getId());

       

       SimpleMailMessage mailMessage = new SimpleMailMessage();

       mailMessage.setTo(account.getEmail());

       mailMessage.setFrom(this.mailFrom);

       mailMessage.setSubject(this.subject);

       mailMessage.setText(text.toString());

       try {

           this.mailSender.send(mailMessage);

       } catch (MailException ex) {

           // just log it and go on

           logger.warn("An exception occured when trying to send email",ex);

       }

    }

   

}

 

1. 红色的内容即为反向注入的 mailSender 属性

 

2. 蓝色的内容为 Spring Bean 的一个通用的接口 InitializingBean ,实现类需要实现该接口定义的方法 afterPropertiesSet() ,该方法中一般是在Bean 被初始化后并设置了所有的setter 注入后调用的。所以这里是保证邮件发送器配置正确。因为如果没有配置正确,下面的工作是无法进行的,所以与其等那时抛出异常,还不如早早地在部署时就告知(通过抛出 IllegalStateException 来提示)

 

3. 绿色的内容为这个 Advise 的核心,即在切入点被切入后将采用的动作。因为 Advise 也同样有多种类型,比如我们这里的“方法正常返回”,“方法执行前”,“方法执行后”,“环绕在方法执行前后”,“方法抛出异常时”等等(详情参见 SpringReference: 6.2.4. Declaring advice)。但是我们的逻辑为在用户确认定单并且执行成功(所谓的成功是指将这一定单插入到了表 Order 中了)后,将发送一确认信。所以”方法正常返回“完全符合我们的要求。

接口AfterReturningAdvice即是 Spring中表示”方法正常返回“ 这一语义的 Advice, 所以我们实现这个接口及其必须的方法 afterReturning.

方法代码的工作其实并不重要,只要我们理解这些“魔法”一样的技术后,实现代码是很简单的。值得提及的是这个方法的参数,这些参数是封装了切入点的所有信息,请见上面的注释。在我们的实现中只使用了被拦截方法的参数,在复杂的 Advice 实现中可能会用到切入点所有信息。