spring4bak

     2022-03-13     464

关键词:

IOC

(参考《Spring企业开发》、《Spring实战 第三版  第四版》)

IoC概述

1、           控制反转

2、依赖注入

 

控制反转:大多数情况下,想要完成一个功能,都需要对象与对象之间相互配合(相互调用)。在最开始的时候,我们都是在哪里需要使用对象,就在哪里new一个对象出来。也就是说,调用者自己维护被调用对象的生命周期。

控制反转的作用,就是将这些对象统一进行初始化,由Spring容器进行管理。并且维护对象之间的关系

 

依赖注入:如果对象之间存在依赖关系,则由Spring负责将被调用的对象注入到调用对象上。Spring支持构造函数注入和成员变量注入。注入的内容可以是字面值常量、引用。

 

描述方式:

     ·怎么将对象纳入Spring管理

1、           XML配置Bean

2、           XML配置扫描,Bean中加合适的注解

3、           Java配置Bean

4、           Java配置扫描,Bean中加合适的注解

·怎么描述对象之间的关系

1、           XML中配置

2、           注解

 

Spring初始化

1、           使用Java的Main初始化

2、           配置在Web容器中,由容器初始化

 

 

简单案例

 

案例一:XML配置Bean

普通的Bean通过XML配置,交由Spring管理

初始化Spring容器,从容器中获取Bean,执行其方法

 

 

需要被管理的Bean

package org.zln.spring4.core.ioc.bean;

/**
 * Created by sherry on 16/11/22.
 */
public class HelloWorldBean {
    public String sayHello(String name) {
        return "Hello " + name;
    }
}

 

 

Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

    <bean id="helloWorldBean" class="org.zln.spring4.core.ioc.bean.HelloWorldBean"/>

</beans>

 

 

Spring初始化并使用容器中的Bean

package org.zln.spring4.core.ioc.bean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by sherry on 16/11/22.
 */
public class SpringMain {

    /**
     *
日志
     */
   
private static Logger logger = LoggerFactory.getLogger(SpringMain.class);

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        HelloWorldBean helloWorldBean = (HelloWorldBean) applicationContext.getBean("helloWorldBean");
        logger.debug("Bean执行结果:"+helloWorldBean.sayHello(" ZLN "));

    }

}

 

 

案例二:p命名空间注入

在案例一的基础上,使用Spring,通过成员变量注入字面值

 

HelloWorldBean

package org.zln.spring4.core.ioc.bean;


import org.apache.commons.lang3.StringUtils;

/**
 * Created by sherry on 16/11/22.
 */
public class HelloWorldBean {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String sayHello(String name) {
        if (StringUtils.isEmpty(name)){
            name = this.name;
        }
        return "Hello " + name;
    }
}

 

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

    <bean id="helloWorldBean" class="org.zln.spring4.core.ioc.bean.HelloWorldBean"
          p:name="默认姓名"/>

</beans>

这里使用p命名空间,将字符串字面值注入到了name成员变量上

如果想要注入的是一个引用,则使用 p:name-ref=”id”

 

案例三:XML配置  通过构造函数注入

<bean id="helloWorldBean" class="org.zln.spring4.core.ioc.bean.HelloWorldBean">
    <property name="name" value="默认姓名" />
    <constructor-arg index="0" value="25"/>
</bean>

 

 

案例四:Java配置

package org.zln.spring4.core.ioc.bean;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Created by sherry on 16/11/22.
 */
@Configuration
public class ApplicationContextBeans {

    @Bean
    public HelloWorldBean helloWorldBean(){
        HelloWorldBean helloWorldBean = new HelloWorldBean(25);
        helloWorldBean.setName("张柳宁");
        return helloWorldBean;
    }

}

这里的class类,就充当了XML的功能,需要添加 @Configuration 注解

而添加了@bean 的方法,就相当于XML中的bean 标签。默认情况下,Bean的id就是方法名,如果想要另外设置,可以@Bean(“beanName”)这样子进行配置

 

public static void main(String[] args) {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ApplicationContextBeans.class);
    HelloWorldBean helloWorldBean = applicationContext.getBean(HelloWorldBean.class);
    logger.debug("Bean执行结果:" + helloWorldBean.sayHello(" ZLN "));
    logger.debug("Bean执行结果:" + helloWorldBean.sayHello(""));
    logger.debug(helloWorldBean.toString());
    helloWorldBean = applicationContext.getBean(HelloWorldBean.class);
    logger.debug(helloWorldBean.toString());
}

初始化Java类配置的Bean,需要使用AnnotationConfigApplicationContext

通过两次获取对象并打印来看,获取到的是同一个对象

 

 

案例五:使用class对象从Spring容器中获取对象

HelloWorldBean helloWorldBean = applicationContext.getBean(HelloWorldBean.class);

 

 

 

初始化Spring容器

AnnotationConfigApplicationContext/ AnnocactionConfigWebApplicationContext

从一个或多个基于Java的配置类中加载Spring/Spring Web应用上下文

 

ClassPathXmlApplicationContext

从类路径加载Spring应用上下文

FileSystemXmlApplicationContext

从文件系统加载

XmlWebApplicationContext

从Web应用下加载XML配置文件

 

Bean的生命周期

传统:从new开始被创建,不再使用后,由垃圾回收器决定什么时候回收

Spring:复杂

1、           实例化Bean

2、           属性注入

3、           设置bean的id

4、           设置BeanFactory

5、           设置上下文

6、           设置Before方法

7、           设置init方法

8、           设置After方法

9、           一直存在上下文中,直到上下文销毁。并调用destory方法

 

配置Spring的几种方案

XML

Java

扫描

 

Spring的配置风格是可以互相搭配的

尽量使用扫描的机制,显示配置的越少越好。

 

XML中配置扫描

<context:component-scan base-package="org.zln.spring4" resource-pattern="**/*Bean"/>

 

Java中配置扫描

package org.zln.spring4.core.ioc.bean;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * Created by sherry on 16/11/22.
 */
@Configuration
@ComponentScan("org.zln.spring4.core.ioc")
public class ApplicationContextBeans {
}

Java中引用XML

package org.zln.spring4.core.ioc.bean;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

/**
 * Created by sherry on 16/11/22.
 */
@Configuration
@ComponentScan(basePackages = {"org.zln.spring4.core"},resourcePattern = "**/*Bean.class")
@ImportResource("applicationContext.xml")
public class ApplicationContextConfig {
}

 

如果在Java中引用Java,就应该使用@Import

 

XML中引用Java配置

直接将Java配置类像普通Bean一样,使用<bean>标签进行配置即可

如果在XML中引用其他XML配置信息,则使用<import>标签

 

 

 

Spring测试

package org.zln.spring4.core.ioc.bean;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.*;

/**
 * Created by sherry on 16/11/22.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationContextBeans.class)
public class HelloWorldBeanTest {

    @Autowired
    private HelloWorldBean helloWorldBean;

    /**
     *
日志
     */
   
private Logger logger = LoggerFactory.getLogger(HelloWorldBeanTest.class);

    @Test
    public void testSayHello() throws Exception {
        logger.debug(helloWorldBean.sayHello("张柳宁"));
    }
}

 

 

环境与profile

同一个bean,在不同环境中,其初始化方式是不一样的

如:数据源,开发、测试、迁移、生产上的配置信息是不一样的。

这个时候就需要根据当前的环境,选择不同的方式进行实例化

Java中配置环境

package org.zln.spring4.core.ioc.bean;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.Profile;

/**
 * Created by sherry on 16/11/22.
 */
//指明这是一个Spring配置类
@Configuration
//扫描路径
@ComponentScan(basePackages = {"org.zln.spring4.core"},resourcePattern = "**/*Bean.class")
//引用的XML配置
@ImportResource("applicationContext.xml")
//进行实例化的环境  dev-开发  prod-生产
@Profile("dev")
public class ApplicationContextConfig {
}

 

@Profile 也可以配置在Bean上,单独为Bean设置环境

XML中配置环境

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
       profile="dev">


</beans>

同样的,也可以为每个Bean配置profile属性

 

激活profile

Spring在确定哪个proof处于激活状态时,需要依赖两个独立的属性  spring.profiles.active 和 spring.profiles.default

如果设置了  spring.profiles.active  就采用 spring.profiles.active,否则就采用spring.profiles.default

如果两个都没设置,那就是说没有激活的profile,那就只会创建没有定义过profile的Bean

 

那么怎么配置这两个属性呢?

如果是在web中,可以在web.xml中配置DispatchServlet的初始化参数(用init-param配置)或web应用的上下文参数(用context-param配置)


<servlet>
    <servlet-name>webDemo</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>spring.profiles.default</param-name>
        <param-value>dev</param-value>
    </init-param>
</servlet>

 


<context-param>
    <param-name>spring.profiles.default</param-name>
    <param-value>dev</param-value>
</context-param>

 

作为JNDI条目

 

作为环境变量

 

作为JVM的系统属性

 

在集成测试类上,使用@ActiveProfiles注解

@ActiveProfiles("dev")

 

注意:

1、           激活的环境的名称是随自己定义的

2、           配置的时候可以同时配置激活多个环境

 

条件化Bean

这是在Spring4开始提供的一种更细粒度的决定是否提供初始化Bean的方案

package org.zln.spring4.core.ioc.bean;

import org.springframework.context.annotation.*;

/**
 * Created by sherry on 16/11/22.
 */
//指明这是一个Spring配置类
@Configuration
//扫描路径
@ComponentScan(basePackages = {"org.zln.spring4.core"},resourcePattern = "**/*Bean.class")
//引用的XML配置
@ImportResource("applicationContext.xml")
public class ApplicationContextConfig {

    @Bean
    @Conditional(value = MagCondition.class)
    public String string(){
        return new String("哇哈哈");
    }

}

 

package org.zln.spring4.core.ioc.bean;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * Created by sherry on 16/11/22.
 */
public class MagCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return true;
    }
}

如果返回true,就实例化,返回false,就不实例化

可以通过matches方法的两个参数实现很复杂的判断

 

首选Bean

当在选择哪个Bean进行注入的时候产生了歧义,可以使用首选方案

1、           在Bean上配置@Primary

2、           在XML中配置 primary=”true”

显然,相同类型只运行有一个Bean配置了Primary

另一种更简单的方式,就是除了使用@Autowired外,还要使用@Qualifier设置依赖的Bean的id

Qualifier也可以与@Bean一同使用,配置在生成Bean的方法上,并且可以设置多个,用于为Bean指定多个名称

 

Bean的作用域

默认情况下,Spring应用上下文中的Bean都是以单例的形式创建的。

也就是说,不管给定的一个Bean被注入到其他Bean多少次,每次所注入的都是同一个实例

 

Spring为Bean定义了多种作用域

     单例 – Singleton,整个应用中,只创建一个bean实例

     原型 – Prototype,每次注入或者通过Spring去获取的时候,都创建一个新实例

     会话 – Session,在Web应用中,为每个会话创建一个实例

     请求 – Request,在Web应用中,为每个请求创建一个实例

 

可以在Java中使用

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

配置Bean的作用域,如果是使用XML配置的Bean,可以使用scope属性

 

web作用域

session和request比较特殊。

Session作用域的应用:购物车。如果按照默认情况,购物车是单例的话,就会导致所有人都往同一个购物车中添加商品。如果是原型,就会导致一个用户在一次浏览网页并添加商品到购物车的过程中,购物车中的商品无法合并。可以使用如下方式进行配置

@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.INTERFACES)

 

proxyMode = ScopedProxyMode.INTERFACES解决了将Session作用域的Bean注入到单例Bean中所遇到的问题。

对于单例的Bean,在程序启动的时候,就会被创建,而Session作用域的Bean,是在用户使用过程中被创建的。如果一个单例的Bean依赖了Session作用域的Bean该怎么办呢?我们希望对于一个单例的实例,它所依赖的那个Session作用域的Bean,正好是当前会话的那个Bean。Spring通过代理解决这个问题。一开始注入的只不过是一个代理类,真正运行的时候,代理会对其进行懒解析,并调用委托给会话作用域内真正的Bean。

注意:如果@Bean方法返回的是一个接口,那是没问题的,可如果是一个实例的话,需要这样配置

proxyMode = ScopedProxyMode.TARGET_CLASS

以此来表明要以生成目标类扩展的方式创建代理

 

运行时注入

Spring提供了两种在运行时2注入的方式

1、           属性占位符

2、           SpEL表达式语言

 

注入外部的值

 

package org.zln.spring4.core.ioc.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;

import java.io.UnsupportedEncodingException;

/**
 * Created by sherry on 16/11/22.
 */
//指明这是一个Spring配置类
@Configuration
//扫描路径
@ComponentScan(basePackages = {"org.zln.spring4.core"}, resourcePattern = "**/*Bean.class")
@PropertySource("classpath:/app.properties")
public class ApplicationContextConfig {

    @Autowired
    Environment environment;

    @Bean
    public String string() throws UnsupportedEncodingException {
        return new String(environment.getProperty("title","哇哈哈").getBytes("UTF-8"),"UTF-8");
    }

}

 

哇哈哈  是当从app.properties 中取不到值时赋给的默认值

 

使用占位符

先配置一个

@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
    return new PropertySourcesPlaceholderConfigurer();
}

 

然后


@Bean
public String string(@Value("${title}") String title) {

    return title;
}

通过@Value(“${title}”)获取app.properties中配置的数据

 

完整代码如下

package org.zln.spring4.core.ioc.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.Environment;


/**
 * Created by sherry on 16/11/22.
 */
//指明这是一个Spring配置类
@Configuration
//扫描路径
@ComponentScan(basePackages = {"org.zln.spring4.core"}, resourcePattern = "**/*Bean.class")
@PropertySource("classpath:/app.properties")
public class ApplicationContextConfig {

    @Autowired
    Environment environment;

    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public String string(@Value("${title}") String title) {

        return title;
    }

}

 

如果希望实现对外部属性文件的加密解密,可以自定义实现  PropertySourcesPlaceholderConfigurer

 

 

SpEL表达式

SpEL是Spring3引入的

特性

1、           使用Bean的ID来引用Bean

2、           调用方法和访问对象的属性

3、           对值进行算术、关系和逻辑运算

4、           正则表达式匹配

5、           集合操作

 

形式:#{SpEl表达式}

 

使用举例

#{T(System).currentTimeMilles()}   T(System)表示java.lang.System类

#{bean1.pro1}     bean1的pro1属性

#{bean1[‘pro1’]} 同上

 

 

总之:如果想要引入外部文件,就使用占位符,其他时候看需求来决定是否需要使用SpEL表达式

 

 

 

IoC最佳实践

 

一个根配置(不管是XML还是Java都行)

在根配置中设置扫描

在跟配置中引入其他各个模块的配置

 

灵活使用  profile、运行时注入等高级内容

 

小结

IOC做的事情,万变不离其宗,就是为了更好的维护好对象之间的关系,由Spring统一管理各个对象

重点有这么几个:

1、怎么管理Bean

2、怎么按照要求设置Bean之间的关系

3、怎么初始化Bean

4、更加灵活的对Bean进行初始化

 

AOP

AOP概述

AOP是面向切面的编程方式,有其特定的适用场景。

所谓面向切面,就是抽象出一些关注点,将这些关注点中重复做的事情从业务代码中分离开来

 

如果说DI有助于应用对象之间的解耦,那么AOP则有助于实现横切关注点与他们所影响的对象之间的解耦。

 

AOP术语

Aspect – 切面

描述哪些类的哪些方法我们需要做什么的地方

如果是注解,那么切面就是个类,如果采用XML的配置,那么切面就是一段XML配置

Pointcut – 切点

Join point - 连接点

连接点是应用执行过程中能够切入切面的一个点

Advice – 通知

Before – 前置通知:目标方法被调用前执行

After – 后置通知,目标方法完成后被调用执行

After-returning – 返回通知,目标方法成功执行后调用

After-throwing – 异常通知,目标方法抛出异常后调用

Around – 环绕通知,目标方法调用前调用后执行自定义行为

Introduction – 引入

往现有类中添加新的方法或属性

Weaving – 织入

把切面应用到目标并创建新的代理对象的过程

 

Spring提供的AOP支持

1、           基于代理的经典Spring AOP

2、           纯POJO切面

3、           @AspectJ注解驱动的切面

4、           注入式AspectJ切面(适用于Spring各个版本)

 

 

Spring AOP构建在动态代理基础之上,所以Spring对AOP的支持局限于方法拦截

如果需要方法之外的拦截点,如字段、构造器等,可以考虑使用AspectJ

 

设置连接点

Spring AOP支持部分AspectJ的切点指示器

AspectJ指示器

描述

arg()

限制连接点匹配参数为指定类型的执行方法

@args()

限制连接点匹配参数由指定注解标注的执行方法

execution()

匹配连接点的执行方法

this()

限制连接点匹配AOP代理的bean引用为指定类型的类

target

限制连接点匹配目标对象为指定的类

@target()

限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解

within()

限制连接点匹配指定的类型

@within()

限制连接点匹配指定注解所标注的类型

@annotation

限制匹配带有指定注解的连接点

 

 

AOP简单示例

切点

package org.zln.spring4.core.aop;

/**
 * Created by sherry on 16/11/23.
 */
//切点
public interface Performance {
    void perform();
}

 

package org.zln.spring4.core.aop;

import org.springframework.stereotype.Component;

/**
 * Created by sherry on 16/11/23.
 */

@Component
public class PerformanceImpl implements Performance {
    @Override
    public void perform() {
        System.out.println("我是perfom方法");
    }
}

 

切点就是要被环切的对象,这里用一个简单的接口和它的一个实现类来代理

在实现类上添加了@Component注解,这是为了被扫描到

 

切面

package org.zln.spring4.core.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * Created by sherry on 16/11/23.
 */

//定义一个切面
@Aspect
@Component
public class Audience {

    //定义切点
    @Pointcut("execution(* org.zln.spring4.core.aop.Performance.perform(..))")
    public void performance(){}

    @Before("performance()")
    public void silenceCellPhones(){
        System.out.println("调用方法前1");
    }

    @Before("performance()")
    public void takeSeats(){
        System.out.println("调用方法前2");
    }

    @After("performance()")
    public void applause(){
        System.out.println("调用方法后1");
    }

    @AfterReturning("performance()")
    public void app1(){
        System.out.println("调用方法后2");
    }

    @AfterThrowing("performance()")
    public void demandRefund(){
        System.out.println("抛出异常");
    }

    @Around("performance()")
    public void watchPerform(ProceedingJoinPoint proceedingJoinPoint){
        System.out.println("环绕通知1");
        try {
            proceedingJoinPoint.proceed();
            System.out.println("环绕通知2");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

}

@Ascept表明当前类是一个切面

@Component是为了被扫描到

@Pointcut定义了一个切点

Java配置Spring

package org.zln.spring4.core.aop;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * Created by sherry on 16/11/23.
 */
@Configuration
@ComponentScan(basePackages = {"org.zln.spring4.core.aop"})
//启用AspectJ自动代理
@EnableAspectJAutoProxy
public class SpringConf {
}

 

测试

package org.zln.spring4.core.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * Created by sherry on 16/11/23.
 */
public class AopMain {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConf.class);
        Performance performance = applicationContext.getBean(Performance.class);
        performance.perform();
    }

}

 

运行结果

 

环绕通知1

调用方法前1

调用方法前2

我是perfom方法

环绕通知2

调用方法后1

调用方法后2

 

 

小结

AOP是一种面向切面的思想,如果在实际开发过程中,发现大量的属于面向切面的活儿,就可以考虑使用Spring AOP

如果Spring AOP无法满足要求,则考虑AspectJ等其他AOP框架

 

Spring MVC

(参考 开涛、《Spring MVC学习指南 》、《看透Spring MVC源代码分析与实践》)

框架简介

Spring MVC是Spring团队实现的一个MVC框架

由DispatcherServlet作为前端控制器分派请求,

各种注解配置大大简化了开发

同时返回视图解析器也是配置化的

 

DispatcherServlet

DispatcherServlet是前端控制器设计模式的实现,提供Spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得Spring的所有好处。 

 

DispatcherServlet主要用作职责调度工作,本身主要用于控制流程,主要职责如下:

1、文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;

2、通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);

3、通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);

4、通过ViewResolver解析逻辑视图名到具体视图实现;

5、本地化解析;

6、渲染具体的视图等;

7、如果执行过程中遇到异常将交给HandlerExceptionResolver来解析。

 

框架搭建

在web项目中使用Spring和Spring MVC,需要创建两个Spring上下文

可以使用xml和java进行配置

xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <!--① 启动Spring-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext*.xml</param-value>
    </context-param>

    <!--配置日志-->
    <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>WEB-INF/log4j2.xml</param-value>
    </context-param>
    <!--通过监听装载日志配置文件-->
    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>

    <!--过滤器设置请求编码-->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--② 启动Spring容器的监听,引用①处的上下文参数获取Spring配置文件地址-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!--③ 配置Spring MVC地址映射-->
    <servlet>
        <servlet-name>spring4-mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/springServlet/*-servlet.xml</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring4-mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

</web-app>

 

/Users/sherry/WorkPath/Git/Spring/spring4/spring4-mvc-demo01/src/main/webapp/WEB-INF/springServlet/app-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.1.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">

    <!--各种组件注册-->
    <mvc:annotation-driven/>
    <!--只扫描 @Controller 的类-->
    <context:component-scan base-package="org.zln" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--静态资源-->
    <mvc:resources mapping="/css/**" location="/WEB-INF/css/"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:viewClass="org.springframework.web.servlet.view.JstlView"
          p:prefix="/WEB-INF/jsp/"
          p:suffix=".jsp"/>
</beans>

 

java

Servlet3.0开始,可以在java中配置两个Spring上下文,不再需要在web.xml中进行配置

主配置文件

package org.zln.spring4.mvc.conf;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
 *
配置DispatcherServlet
 * Created by sherry on 16/11/23.
 *
 *
继承了AbstractAnnotationConfigDispatcherServletInitializer的类都会自动地配置DispatcherServletSpring应用上下文
 * Spring
应用上下文会位于程序的Servlet上下文之中
 *
其效果与配置在web.xml中一致
 *
 *
注意:这是在Servlet3Spring3.1之后才有的功能
 *
 *
Servlet3.0环境下,容器会在类路径查找
 *
 */
public class WebAppInit extends AbstractAnnotationConfigDispatcherServletInitializer {

    /**
     *
配置映射路径
     * @return
    
*/
   
@Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
     *
配置Web组件相关的Bean  控制器视图解析器  处理器映射等
     * bean
DispatcherServlet上下文中
     * @return
    
*/
   
@Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{WebConfig.class};
    }

    /**
     *
驱动应用后端的中间层和数据层组件
     * bean
ContextLoaderListener上下文中
     * @return
    
*/
   
@Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{RootConfig.class};
    }

}

 

Spring MVC配置文件

package org.zln.spring4.mvc.conf;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

/**
 * web
Spring配置
 * Created by sherry on 16/11/23.
 */

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "org.zln.spring4.mvc",resourcePattern = "**/*Controller.class")
public class WebConfig extends WebMvcConfigurerAdapter{

    /**
     *
配置JSP视图解析器
     * @return
     
*/
   
@Bean
    public ViewResolver viewResolver(){
        InternalResourceViewResolver resourceViewResolver = new InternalResourceViewResolver();
        resourceViewResolver.setPrefix("/WEB-INF/views");
        resourceViewResolver.setSuffix(".jsp");
        resourceViewResolver.setExposeContextBeansAsAttributes(true);
        return resourceViewResolver;
    }

    /**
     *
配置静态资源的处理:要求DispatcherServlet将对静态资源的请求转发到Servlet容器默认的Servlet,而不是使用DispatcherServlet本身来处理
     * @param
configurer
    
*/
   
@Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

 

 

Spring 后端Bean配置文件

package org.zln.spring4.mvc.conf;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

/**
 *
配置处理web bean外的所有需要纳入Spring管理的Bean
 * Created by sherry on 16/11/23.
 */
@Configuration
@ComponentScan(basePackages = {"org.zln.spring4"}, excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)})
public class RootConfig {
}

 

一个简单的控制器

package org.zln.spring4.mvc.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Created by sherry on 16/11/23.
 */
@Controller
public class HomeController {

    /**
     *
日志
     */
   
private Logger logger = LoggerFactory.getLogger(HomeController.class);

    @RequestMapping(value = {"/home","/"}, method = RequestMethod.GET)
    public String home() {
        logger.debug("进入home");
        return "/home";
    }

}

 

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">


    <!--过滤器设置请求编码-->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>


</web-app>

 

小结

我觉得比较好的方式是,主要使用Java进行配置,Java配置作为入口,配置好两个Spring上下文的Java类,在这两个类中再去引入必要的xml配置文件。再由XML去引入其它各个子模块的配置

请求映射

@RequestMapping

@RequestMapping可以注解在类上或方法上,也可以同时标注。两个共同决定了类能够处理的请求

举例如下


@Controller
@RequestMapping("/")
public class HomeController {
    @RequestMapping(value = "home/list")
    public String home() {
        return "/home";
    }

}

home方法能够处理   /home/list的请求

value属性

value属性是一个数组,@RequestMapping(value = {“/home1”,”/home2”}) 说明方法能够处理多种URL的请求

@RequestMapping(value="/users/**"):可以匹配“/users/abc/abc”,但“/users/123”将会被【URI模板模式映射中的“/users/{userId}”模式优先映射到】

@RequestMapping(value="/product?"):可匹配“/product1”或“/producta”,但不匹配“/product”或“/productaa”;

@RequestMapping(value="/product*"):可匹配“/productabc”或“/product”,但不匹配“/productabc/abc”;

@RequestMapping(value="/product/*"):可匹配“/product/abc”,但不匹配“/productabc”;

@RequestMapping(value="/products/**/{productId}"):可匹配“/products/abc/abc/123”或“/products/123”,也就是Ant风格和URI模板变量风格可混用

@RequestMapping(value="/products/{categoryCode:\d+}-{pageNumber:\d+}"):可以匹配“/products/123-1”,但不能匹配“/products/abc-1”,这样可以设计更加严格的规则

 

method属性

method属性是一个数组,@RequestMapping(value = “/home”.method = {RequestMethod.POST,RequestMethod.GET})

params属性

@RequestMapping(params="create", method=RequestMethod.GET) :表示请求中有“create”的参数名且请求方法为“GET”即可匹配,如可匹配的请求URL“http://×××/parameter1?create”;

@RequestMapping(params="create", method=RequestMethod.POST):表示请求中有“create”的参数名且请求方法为“POST”即可匹配;

@RequestMapping(params="!create", method=RequestMethod.GET):表示请求中没有“create”参数名且请求方法为“GET”即可匹配,如可匹配的请求URL“http://×××/parameter1?abc”。

@RequestMapping(params="submitFlag=create", method=RequestMethod.GET):表示请求中有“submitFlag=create”请求参数且请求方法为“GET”即可匹配,如请求URL为http://×××/parameter2?submitFlag=create

@RequestMapping(params="submitFlag!=create", method=RequestMethod.GET):表示请求中的参数“submitFlag!=create”且请求方法为“GET”即可匹配,如可匹配的请求URL“http://×××/parameter1?submitFlag=abc”。

@RequestMapping(params={"test1", "test2=create"}):表示请求中的有“test1”参数名 且 有“test2=create”参数即可匹配,如可匹配的请求URL“http://×××/parameter3?test1&test2=create。

headers属性

@RequestMapping(value="/header/test1", headers = "Accept"):表示请求的URL必须为“/header/test1”

且 请求头中必须有Accept参数才能匹配。

@RequestMapping(value="/header/test1", headers = "abc"):表示请求的URL必须为“/header/test1”

且 请求头中必须有abc参数才能匹配,如图6-8时可匹配。

@RequestMapping(value="/header/test2", headers = "!abc"):表示请求的URL必须为“/header/test2”

且 请求头中必须没有abc参数才能匹配。(将Modify Header的abc参数值删除即可)。

@RequestMapping(value="/header/test3", headers = "Content-Type=application/json"):表示请求的URL必须为“/header/test3” 且 请求头中必须有“Content-Type=application/json”参数即可匹配。

当你请求的URL为“/header/test3” 但 如果请求头中没有或不是“Content-Type=application/json”参数(如“text/html”其他参数),将返回“HTTP Status 415”状态码【表示不支持的媒体类型(Media Type),也就是MIME类型】,即我们的功能处理方法只能处理application/json的媒体类型。

@RequestMapping(value="/header/test4", headers = "Accept=application/json"):表示请求的URL必须为“/header/test4” 且 请求头中必须有“Accept =application/json”参数即可匹配。(将Modify Header的Accept参数值改为“application/json”即可);

@RequestMapping(value="/header/test5", headers = "Accept=text/*") :表示请求的URL必须为“/header/test5” 且 请求头中必须有如“Accept=text/plain”参数即可匹配。(将Modify Header的Accept参数值改为“text/plain”即可);

@RequestMapping(value="/header/test6", headers = "Accept=*/*") :表示请求的URL必须为“/header/test6” 且 请求头中必须有任意Accept参数即可匹配。Accept=*/*:表示主类型任意,子类型任意,如“text/plain”、“application/xml”等都可以匹配。

@RequestMapping(value="/header/test7", headers = "Accept!=text/vnd.wap.wml"):表示请求的URL必须为“/header/test7” 且 请求头中必须有“Accept”参数但值不等于“text/vnd.wap.wml”即可匹配。

@RequestMapping(value="/header/test8", headers = {"Accept!=text/vnd.wap.wml","abc=123"}):表示请求的URL必须为“/header/test8” 且 请求头中必须有“Accept”参数但值不等于“text/vnd.wap.wml”且 请求中必须有参数“abc=123”即可匹配。

 

注:Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

如果您的请求中含有Accept:“*/*”,则可以匹配功能处理方法上的如“text/html”、“text/*”,“application/xml”等。

 

①服务器端可以通过指定【headers = "Content-Type=application/json"】来声明可处理(可消费)的媒体类型,即只消费Content-Type指定的请求内容体数据;

②客户端如何告诉服务器端它只消费什么媒体类型的数据呢?即客户端接受(需要)什么类型的数据呢?服务器应该生产什么类型的数据?此时我们可以请求的Accept请求头来实现这个功能。

 

 

获取请求参数

@RequestParam

注解在控制器方法的参数上,如: @RequestParam String id,就会把请求参数中名为id的数据注入给id参数。如果请求参数和方法参数名称不一致,则可以手动指定。@RequestParam(“id”) String name

required属性

请求中是否必须有这个参数。默认是true。如果没有是会报错的

defaultValue属性

为请求参数设置默认值,如果没有这个请求参数,就将默认值注入到控制器的方法参数上

@ModelAttribute

请求参数到命令对象的绑定;

@CookieValue

cookie数据到处理器功能处理方法的方法参数上的绑定;

@RequestHeader

请求头(header)数据到处理器功能处理方法的方法参数上的绑定;

@RequestBody

请求的body体的绑定(通过HttpMessageConverter进行类型转换);

@PathVariable

请求URI中的模板变量部分到处理器功能处理方法的方法参数上的绑定,从而支持RESTful架构风格的URI;

@RequestPart

绑定“multipart/data”数据,除了能绑定@RequestParam能做到的请求参数外,还能绑定上传的文件等。

 

 

POJO

对于一个普通的,带有setter方法的类,作为控制器方法的参数,会将请求中的请求参数按照名称注入到POJO的成员变量上

 

表单校验

表单校验只做和业务逻辑无关的输入校验,一般如 非空、长度、合法性等,如果正常,就将请求参数赋值给命令对象或者其他对象,在处理方法中继续处理,否则就直接返回给请求页面,在请求页面中显示错误提示。

有两种方式做表单的后端校验

1、自己编写验证器。在Controller方法中进行调用(见Spring MVC学习指南 p106--108)

2、JSR 303  Hibernate中有实现。

过滤器

以下代码是继承OncePerRequestFilter实现登录过滤的代码:

 

 1 package com.test.spring.filter;

 2  

 3 import java.io.IOException;

 4 import java.io.PrintWriter;

 5  

 6 import javax.servlet.FilterChain;

 7 import javax.servlet.ServletException;

 8 import javax.servlet.http.HttpServletRequest;

 9 import javax.servlet.http.HttpServletResponse;

10  

11 import org.springframework.web.filter.OncePerRequestFilter;

12  

13 /**

14  * 登录过滤

15  *

16  * @author geloin

17  * @date 2012-4-10 下午2:37:38

18  */

19 public class SessionFilter extends OncePerRequestFilter {

20  

21     /*

22      * (non-Javadoc)

23      *

24      * @see

25      * org.springframework.web.filter.OncePerRequestFilter#doFilterInternal(

26      * javax.servlet.http.HttpServletRequest,

27      * javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain)

28      */

29     @Override

30     protected void doFilterInternal(HttpServletRequest request,

31             HttpServletResponse response, FilterChain filterChain)

32             throws ServletException, IOException {

33  

34         // 不过滤的uri

35         String[] notFilter = new String[] { "login.html", "index.html" };

36  

37         // 请求的uri

38         String uri = request.getRequestURI();

39  

40         // uri中包含background时才进行过滤

41         if (uri.indexOf("background") != -1) {

42             // 是否过滤

43             boolean doFilter = true;

44             for (String s : notFilter) {

45                 if (uri.indexOf(s) != -1) {

46                     // 如果uri中包含不过滤的uri,则不进行过滤

47                     doFilter = false;

48                     break;

49                 }

50             }

51             if (doFilter) {

52                 // 执行过滤

53                 // 从session中获取登录者实体

54                 Object obj = request.getSession().getAttribute("loginedUser");

55                 if (null == obj) {

56                     // 如果session中不存在登录者实体,则弹出框提示重新登录

57                     // 设置request和response的字符集,防止乱码

58                     request.setCharacterEncoding("UTF-8");

59                     response.setCharacterEncoding("UTF-8");

60                     PrintWriter out = response.getWriter();

61                     String loginPage = "....";

62                     StringBuilder builder = new StringBuilder();

63                     builder.append("<script type="text/javascript">");

64                     builder.append("alert(‘网页过期,请重新登录!‘);");

65                     builder.append("window.top.location.href=‘");

66                     builder.append(loginPage);

67                     builder.append("‘;");

68                     builder.append("</script>");

69                     out.print(builder.toString());

70                 } else {

71                     // 如果session中存在登录者实体,则继续

72                     filterChain.doFilter(request, response);

73                 }

74             } else {

75                 // 如果不执行过滤,则继续

76                 filterChain.doFilter(request, response);

77             }

78         } else {

79             // 如果uri中不包含background,则继续

80             filterChain.doFilter(request, response);

81         }

82     }

83  

84 }

 

写完过滤器后,需要在web.xml中进行配置:

 

<filter>

    <filter-name>sessionFilter</filter-name>

    <filter-class>com.test.spring.filter.SessionFilter</filter-class>

</filter>

<filter-mapping>

    <filter-name>sessionFilter</filter-name>

    <url-pattern>/*</url-pattern>

</filter-mapping>

 

 文件上传

Spring MVC学习指南 p174---179

思路:命令对象中有一个List<MultipartFile>类型的对象来接收前端发起的文件上传请求

 

在配置文件中,可以通过对CommonMultipartResolver的属性的配置,配置文件上传的最大值。默认是没有容量限制的。在上传超大文件的时候,一般要使用 HTML5 的File API将文件切割,再分别上传。

 

在Servlet 3之前,文件上传需要借助组件,一般是 common-fileupload,在Servlet 3以后,就不需要了。见 《Spring MVC学习指南》 page 185

响应请求

@ResponseBody

处理器功能处理方法的返回值作为响应体(通过HttpMessageConverter进行类型转换);

@ResponseStatus

定义处理器功能处理方法/异常处理器返回的状态码和原因;

redirect

return “redirect:/web/home.jsp”

表示重定向到指定页面,相当于在浏览器输入地址

 

Flash属性

在控制器方法参数中添加 RedirectAtrributes类型的参数,调用其addFlashAtteribute()方法,就可以把当前的数据传递给重定向后的页面。如果只是将数据存储在request的话,重定向后就消失了

 

字符串

跳转到字符串指定的页面上

响应视图

测试

使用MockMvc对Controller进行简单的测试

package org.zln.spring4.mvc.controller;

import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

/**
 * Created by sherry on 16/11/24.
 */
public class HomeControllerTest {

    @Test
    public void testHome() throws Exception {
        HomeController homeController = new HomeController();
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(homeController).build();
        mockMvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.view().name("/home"));
    }
}

 

Spring 提供了强大的MockMvc功能用于对Spring MVC的控制器进行测试。基本上可以模拟网页请求。

 

Spring视图

Apache Tiles

Thymeleaf

类型转换器

因为HTTP的特性,请求数据过来的时候,都是字符串,这个时候如果命令对象的某个属性是其他类型的,如日期类型,就需要进行类型转换。Spring MVC默认已经提供了很多类型转换器,我们也可以自己实现。

步骤如下:

1、实现Converter<S,T>接口,S表示源类型,T表示目标类型

2、注册自定义的类型转换器

<bean id=”cs” class=”org.springframework.context.support.ConversionServiceFactoryBean”>

       <property name=”converters”>

              <list>

                     <bean class=”自定义实现的转换器” />

              </list>

       </property>

</bean>

<mvc:annotation-drvien conversion-service=”cs”/>

 

另一种方式是实现Formatter,Formatter更适用于Web层,其源类型必须是String。注册过程如下

<bean id=”cs” class=”org.springframework.format.support.FormattingConversionServiceFactoryBean”>

       <property name=””formatters”>

              <set>

                     <bean class=自定义实现的Formatter/>

              </set>

       </property>

</bean>

REST支持

什么是REST?

Spring MVC对REST的支持

数据库支持

数据源配置

 

Drud数据源

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"
      p:driverClassName="com.mysql.jdbc.Driver"
      p:url="jdbc:mysql://localhost:3306/zln?useSSL=false"
      p:username="root"
      p:password="123456"/>

 

JNDI数据源

如果应用程序部署在web容器中,则数据源一般采用JNDI的方式

<jee:jndi-lookup jndi-name="/jdbc/bdrisk" id="dataSource"
                 resource-ref="true"/>

resource-ref=”true”,则jndi-name会自动添加 java:comp/env 前缀

如果用java配置

@Bean
public JndiObjectFactoryBean dataSource(){
    JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
    jndiObjectFactoryBean.setJndiName("jdbc/bdrisk");
    jndiObjectFactoryBean.setResourceRef(true);
    jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
    return jndiObjectFactoryBean;
}

 

Spring自身也提供了线程连接功能,但性能不佳,故不推荐

整合JdbcTemplate

配置数据源后

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
      p:dataSource-ref="dataSource"/>

 

就可以将JDBCTemplate注入给Dao访问数据库了

或者使用NamedJdbcTemplate

<bean id="namedJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"
      c:dataSource-ref="dataSource"/>

NamedJdbcTemplate是一种特殊的JDBC模板,支持命名参数功能

整合MyBatis

(这些的集成方式,等学具体的持久层的时候再学)

整合Hibernate

整合JPA

Spring Date

使用NoSQL数据库

MongoDB

Neo4j

Redis

缓存

远程服务

远程调用类似于调用一个本地对象的方法,是同步操作,会阻塞调用代码的执行,知道被调用的过程执行完毕。

 

RMI

适用场景:不考虑网络限制(如 防火墙),访问/发布基于Java的服务

 

RMI很难穿透防火墙,因为RMI使用任意端口进行交互,这通常是防火墙所不允许的。

RMI是基于Java的,意味着客户端和服务端都要使用Java开发

RMI使用Java的序列化机制,所以通过网络传输的对象类型必须保证在调用两端的Java运行时中是完全相同的版本

 

服务端

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.2.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">

    <bean id="hello" class="org.zln.bdrisk.col.HelloImpl"/>

    <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
        <!-- RMI服务名称,可自定义服务名称 -->
        <property name="serviceName" value="helloService" />
        <!-- 导出实体 -->
        <property name="service" ref="hello" />
        <!-- 导出接口 -->
        <property name="serviceInterface" value="org.zln.bdrisk.col.IHello" />
        <!-- spring默认使用1099端口 -->
        <property name="registryPort" value="8888" />
    </bean>


</beans>

 

客户端

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.2.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">

    <bean id="hello" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
        <property name="serviceUrl" value="rmi://localhost:8888/helloService" />
        <property name="serviceInterface" value="org.zln.bdrisk.col.IHello" />
    </bean>
</beans>

 

 

服务端只要Spring实例化了,RMI服务对象就会注册好等着被调用,客户端上填写好服务端的地址和服务名,然后从Spring容器中获取远程对象即可

RMI与Spring集成后,编写RMI服务类和普通的接口、实现没有区别,不再需要集成、抛出指定异常等。

Hessian和Burlap

谷歌一下

 

适用场景:考虑网络限制时,通过HTTP访问/发布基于Java的服务。Hessian是基于二进制协议,Burlap基于XML

Spring的HttpInvoker

谷歌一下

 

适用场景:考虑网络限制,并希望使用基于XML或专有的序列化机制实现Java序列化,访问/发布基于Spring的服务

JAX-RPC和JAX-WS

谷歌一下

 

适用场景:访问/发布平台独立的,基于SOAP的Web服务

 

异步消息

消息代理和目的地

当应用发送消息的时候,会将消息交给消息代理,由消息代理确保将消息投递到指定的目的地。同时解放发送者,可以进行其他业务

点对点消息模型:

       每条消息都有一个发送者和接受者

发布—订阅模型:

       消息发送给一个主题,多个接收者可以订阅监听这一主题。所有订阅了这个主题的接收者都能收到消息的副本

JMS

Java Message Service

ActiveMQ是一款比较好的JMS异步消息传递产品

ActiveMQ充当的就是消息代理的角色

 

示例

先从官网下载二进制发行包:http://activemq.apache.org

启动ActiveMQ服务

 

配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jms="http://www.springframework.org/schema/jms"
       xmlns:amq="http://activemq.apache.org/schema/core"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://activemq.apache.org/schema/core
       http://activemq.apache.org/schema/core/activemq-core.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
       http://www.springframework.org/schema/jms
       http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">

    <!--这个包下的,所有加过特殊注解的类,都被Spring管理-->
    <context:component-scan base-package="org.zln" resource-pattern="**/*Dao.class"/>
    <context:component-scan base-package="org.zln" resource-pattern="**/*Service.class"/>

    <!--开启注解注入-->
    <context:annotation-config/>

    <!--
    配置连接工厂,默认监听tcp://localhost:61616.可以根据实际情况进行修改
    -->
    <!--<bean id="connectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory"-->
          <!--p:brokerURL="tcp://localhost:61616"/>-->
    <!--上下两段配置等效,下面的配置更简洁-->
    <!--如果我们使用不同的消息代理实现,他们不一定提供Spring配置命名空间,那么就需要使用bean来装配连接工厂-->
    <amq:connectionFactory id="connectionFactory" brokerURL="tcp://localhost:61616"/>


    <!--配置ActiveMQ消息的目的地
    目的地可以是一个队列,也可以是一个主题
    -->
    <!--队列-->
    <!--<bean id="queue" class="org.apache.activemq.command.ActiveMQQueue"-->
          <!--c:name="spitter.queue"/>-->
    <!--主题-->
    <!--<bean id="topic" class="org.apache.activemq.command.ActiveMQTopic"-->
          <!--c:name="spitter.topic"/>-->
    <!--使用amp命名空间简化配置-->
    <!--physicalName指定队列名称-->
    <amq:queue id="queue" physicalName="spitter.queue"/>
    <!--physicalName指定消息通道名称-->
    <amq:topic id="topic" physicalName="spitter.topic"/>


    <!--Spring对JMS的支持  defaultDestination 指定默认发送目的地-->
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"
          c:connectionFactory-ref="connectionFactory"
          p:defaultDestination-ref="queue"/>

</beans>

 

发送端代码

package org.zln.spring4.mq;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsOperations;
import org.springframework.jms.core.MessageCreator;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;

/**
 * Created by sherry on 16/11/27.
 */
public class AlertServiceImpl implements AlertService {

//    JmsOperations是JmsTemplate所实现的接口
    @Autowired
    private JmsOperations jmsOperations;

    @Override
    public void sendJmsMsg(String msg) {
        jmsOperations.send("spitter.queue",//指定消息发送的目的地,如果不传,就发送给JmsTemplate配置的默认目的地
                new MessageCreator() {//构造要发送的消息
                    @Override
                    public Message createMessage(Session session) throws JMSException {
                        return session.createObjectMessage(msg);
                    }
                });
        jmsOperations.convertAndSend(msg);//使用convertAndSend发送消息,就会自动将要发送的数据转化为消息对象进行发送
        /*
        Spring已经提供了多种转化器,默认使用SimpleMessageConverter
        如果想要使用其他的转化器,则将转化器声明为bean,然后配置给JmsTemplate即可
         */
    }
}

接收

如果直接接收,那么是同步的,我们一般会配置一个监听器,

<bean id="alertHandler" class="org.zln.spring4.mq.AlertHandler"/>
<jms:listener-container>
    <jms:listener destination="queue" ref="alertHandler" method="handlerAlert"/>
</jms:listener-container>

 

package org.zln.spring4.mq;

/**
 * Created by sherry on 16/11/27.
 */
public class AlertHandler {

    public void handlerAlert(String msg){//处理接收到的消息
        System.out.println("收到JMS消息:"+msg);
    }

}

 

 

AMQP

AMQP是高级消息队列协议,能够跨不同AMQP实现、跨语言和平台

RabbitMQ是一个流行的开源消息代理,它实现了AMQP。Spring AMQP为RabbitMQ提供了支持,包括RabbitMQ连接工厂、模板以及Spring配置命名空间

具体使用上和ActiveMQ很像,就不重复了,有需要在Google一下

WebSocket和STOMP

JMS和AMQP是在应用程序之间发送消息,但是如果应用是运行在Web浏览器中,就需要使用WebSocket技术了

Spring 4.0为WebSocket提供了技术支持,包括:

1、发送和接收消息的低级API

2、发送和接收消息的高级API

3、发送消息模板

4、支持SocketJs,用来解决浏览器端、服务器以及代理不支持WebSocket的问题

STOMP是基于WebSocket的一项技术,为浏览器-服务器之间的通信增加恰当的消息语义

 

Email

使用JavaMailSenderImpl来实现发送Email

 

个人小结:对于一个公司而言,像系统间的同步、异步消息收发,邮件的收发,都是属于基础服务,都会封装成专门的调用接口的,客户端和服务端只要把相关的信息往服务器上发送或监听就行了,由服务器统一处理。

自己有空可以尝试写一个邮件服务器,专门处理邮件的订阅与发送;一个异步消息服务器,内部可以采用MQAMQP

JMX

Spring对DI的支持,是通过应用中配置bean属性,但一旦应用部署运行,单独使用DI就不能改变这些配置了

如果我们希望深入了解正在运行的应用,并要在运行时改变配置,就可以使用Java管理扩展(Java Manage-ment Extensions JMS)

 

 

 

 

成果:

要求:

1、Spring MVC

通过JSON实现浏览器与Controller之间的交互

 

2、消息

MQ、AMQP,编写一个服务端,同时支持以上两种的请求调用

 

3、远程调用

RMI、Hessian和Burlap、HttpInvokey、JAX-RPC和JAX-WS实现远程调用

 

4、Email

邮箱发送与接收模块(可以配合异步消息来使用,但是前提是需要首先Email模块自己要独立出来)

 

5、JMX

尝试使用JMX动态管理Spring中的Bean,在程序运行的时候动态添加、删除、修改Bean的配置,并且查看Spring容器中Bean的情况

 

自定义需求:

参考当前我的工作项目,编写一个预警的查询系统

project:      warningQry

warningQry-web

       warningQry-web-service

       warningQry-web-dao

warningQry-domain

warningQry-commons

       warningQry-commons-mq

       warningQry-commons-amqp

warningQry-server

说明:

       项目分为前端和后端

       前端:与客户直接交互,接收请求,进行查询,先查询本地,本地查询不到,调用(远程调用、异步消息)后端

       后端:接收前端的查询请求,返回查询结果(远程调用、异步消息、邮件)

 

 

ok,需求就先这样子,就看自己啥时候能够完成了。