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,需求就先这样子,就看自己啥时候能够完成了。

Spring 4 bak的更多相关文章

  1. Spring中属性文件properties的读取与使用

    实际项目中,通常将一些可配置的定制信息放到属性文件中(如数据库连接信息,邮件发送配置信息等),便于统一配置管理.例中将需配置的属性信息放在属性文件/WEB-INF/configInfo.propert ...

  2. spring获取properties

    实际项目中,通常将一些可配置的定制信息放到属性文件中(如数据库连接信息,邮件发送配置信息等),便于统一配置管理.例中将需配置的属性信息放在属性文件/WEB-INF/configInfo.propert ...

  3. spring + jdbc + extjs configuration

    所有源代码能够訪问我的GitHub 有空没空的稻谷了几天,最终前后台跑通了,提供一套可用的配置文件. (因为与extjs整合,spring security的登录须要重写原handler.会在后面补上 ...

  4. Spring 中使用Properties文件

    Spring提供了加载Properties文件的工具类:org.springframework.beans.factory.config.PropertyPlaceholderConfigurer. ...

  5. Spring源码系列 — Resource抽象

    前言 前面两篇介绍了上下文的启动流程和Environemnt的初始化,这两部分都是属于上下文自身属性的初始化.这篇开始进入Spring如何加载实例化Bean的部分 - 资源抽象与加载. 本文主要从以下 ...

  6. Spring(一)概述

    Spring 的前世今生 相信经历过不使用框架开发 Web 项目的 70 后.80 后都会有如此感触,如今的程序员开发项目太轻松 了,基本只需要关心业务如何实现,通用技术问题只需要集成框架便可.早在 ...

  7. 一篇文章带你掌握主流基础框架——Spring

    一篇文章带你掌握主流基础框架--Spring 这篇文章中我们将会介绍Spring的框架以及本体内容,包括核心容器,注解开发,AOP以及事务等内容 那么简单说明一下Spring的必要性: Spring技 ...

  8. 基于spring注解AOP的异常处理

    一.前言 项目刚刚开发的时候,并没有做好充足的准备.开发到一定程度的时候才会想到还有一些问题没有解决.就比如今天我要说的一个问题:异常的处理.写程序的时候一般都会通过try...catch...fin ...

  9. 玩转spring boot——快速开始

    开发环境: IED环境:Eclipse JDK版本:1.8 maven版本:3.3.9 一.创建一个spring boot的mcv web应用程序 打开Eclipse,新建Maven项目 选择quic ...

随机推荐

  1. javascript数组浅谈2

    上次说了数组元素的增删,的这次说说数组的一些操作方法 join()方法: ,,] arr.join("_") //1_2_3 join方法会返回一个由数组中每个值的字符串形式拼接而 ...

  2. jQuery知识大杂汇

    1.jQuery 语法是通过选取 HTML 元素,并对选取的元素执行某些操作. 基础语法: $(selector).action() 举几枚栗子吧: $(this).hide() - 隐藏当前元素 $ ...

  3. virtualbox虚拟机迁移出现"connot find device eth0"错误

    我在自己的机器上面配置virtualbox虚拟机完毕以后,移植到另外一台机器上面,登陆页面总是在检查network,并且最后网络加载失败,不论我是用桥接还是NAT方式连接.登陆系统以后,我尝试连接网络 ...

  4. 我遇到的CocoaPods的问题(也许后期会解决,持续更新)

    在此博客中写下两类关于CocoaPods的问题: 未解决的问题:可以留着以后解决 已经解决的问题:可以备份以后回头再参考解决同样的问题 <已解决的问题> 解决方法是:pod install ...

  5. [转]个人源码管理:如何在本机配置自己的SVN Repository (图解)

    本文转自:http://blog.csdn.net/wikijava/article/details/6245588 Repository 即源码的集中存放处,所有修改后提交的源码就是保存在这里,并在 ...

  6. Effective Java 16 Favor composition over inheritance

    Inheritance disadvantage Unlike method invocation, inheritance violates encapsulation. Since you don ...

  7. LightSpeed 相关问题处理

    1. 关于KeyTable 配置文件中有一个节点 lightSpeedContexts  该节点下存放的是一些使用LightSpeed的配置,如 <add name="myDB&quo ...

  8. 问题解决——MFC SDI程序 CFormView中控件随窗口缩放

    从来都是做对话框程序,这次想做个SDI的程序,想着用一下带Robbin界面的office2007风格,就不用使用那些花钱的商业控件/UI库了. 如果你不想看我打的文字,可以直接拷走代码,自己声明上定义 ...

  9. 6天的巴厘岛旅行 I love Bali

    6天的巴厘岛旅游今天结束了,从第一天的踏进异国之域的新奇,到最后一天的将且回程的恋恋不舍,要记下的.不愿忘记的东西太多太多. 1.下午5点半抵达巴厘岛登巴萨国际机场,7点半才出机场,让Ricky导游和 ...

  10. Hadoop,Vertica环境搭建

    本打算使用mapr的虚拟机在里面进行开发,使用eclipse进行调试,它的问题是,有时候服务不能完全起来, 如jobtracker和tasktracker,cldb没有起来,重启服务有可能解决. 但另 ...