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的类都会自动地配置DispatcherServlet和Spring应用上下文
* Spring应用上下文会位于程序的Servlet上下文之中
* 其效果与配置在web.xml中一致
*
* 注意:这是在Servlet3和Spring3.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的一项技术,为浏览器-服务器之间的通信增加恰当的消息语义
使用JavaMailSenderImpl来实现发送Email
个人小结:对于一个公司而言,像系统间的同步、异步消息收发,邮件的收发,都是属于基础服务,都会封装成专门的调用接口的,客户端和服务端只要把相关的信息往服务器上发送或监听就行了,由服务器统一处理。
自己有空可以尝试写一个邮件服务器,专门处理邮件的订阅与发送;一个异步消息服务器,内部可以采用MQ或AMQP
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,需求就先这样子,就看自己啥时候能够完成了。