IDEA使用

IntelliJ Idea 常用快捷键

Ctrl+N   查找类
Ctrl+Shift+N 查找文件
Ctrl+Alt+L  格式化代码
Ctrl+R 替换文本
Ctrl+F 查找文本
Ctrl+Shift+Space 自动补全代码
Ctrl+空格 代码提示
Ctrl+Alt+Space 类名或接口名提示
Ctrl+P 方法参数提示
Ctrl+Shift+Alt+N 查找类中的方法或变量
Alt+Shift+C 对比最近修改的代码

Shift+F6  重构-重命名
Ctrl+Shift+先上键
Ctrl+X 删除行
Ctrl+D 复制行
Ctrl+/ 或 Ctrl+Shift+/  注释(// 或者/*...*/ )
Ctrl+J  自动代码
Ctrl+E 最近打开的文件
Ctrl+H 显示类结构图
Ctrl+Q 显示注释文档
Alt+F1 查找代码所在位置
Alt+1 快速打开或隐藏工程面板
Ctrl+Alt+ left/right 返回至上次浏览的位置
Alt+ left/right 切换代码视图
Alt+ Up/Down 在方法间快速移动定位
Ctrl+Shift+Up/Down 代码向上/下移动。
F2 或Shift+F2 高亮错误或警告快速定位
 
代码标签输入完成后,按Tab,生成代码。
选中文本,按Ctrl+Shift+F7 ,高亮显示所有该文本,按Esc高亮消失。
Ctrl+W 选中代码,连续按会有其他效果
选中文本,按Alt+F3 ,逐个往下查找相同文本,并高亮显示。
Ctrl+Up/Down 光标跳转到第一行或最后一行下
Ctrl+B 快速打开光标处的类或方法 

idea工具的使用

idea总算折腾好了,从当初的小白,到现在的熟练运用,真是苦不堪言。当时只是听说了idea是公认最好用的编译环境,因为不熟悉,各种不习惯,还有不会用,然后就放下了,就没再用过,直到这几日,又重新用起了idea,

下面说一下我的使用心得,希望对你们有所帮助

idea不同于eclipse,myeclipse,全新的编译方式,工作方式,都和eclipse不大一样,idea如果在mac上用最好了,在windows上也行,感觉有点不搭配的意思。很多快捷键都是针对mac用户的,mac用户也有很好的用户体验。

Spring MVC笔记

struts和spring mvc

Struts创建的action类是多例模式,当刷新页面的时候会重新new Action类
spring mvc 是单例模式,只创建一个

Spring mvc Bean配置文件 name的表示请求路径

Controller类中方法参数,返回值 的处理

  1. 返回string(建议)
    a) 根据返回值找对应的显示页面。路径规则为:prefix前缀+返回值+suffix后缀组成
    b) 代码如下:
@RequestMapping(params="method=reg4")
public String reg4(ModelMap map) {
   System.out.println("HelloController.handleRequest()");
   return"index";
}

前缀为:/WEB-INF/jsp/ 后缀是:.jsp
在转发到:/WEB-INF/jsp/index.jsp

  1. 也可以返回ModelMap、ModelAndView、map、List、Set、Object、无返回值。一般建议返回字符串!

extjs学习

本文引自李林峰的园子

注:本教程的讲解是基于ExtJs3.0版本Ext3.0下载,Ext3.0API下载

简介

ExtJS是一种主要用于创建前端用户界面,是一个与后台技术无关的前端ajax框架。
功能丰富,无人能出其右
无论是界面之美,还是功能之强,ext的表格控件都高居榜首。
单选行,多选行,高亮显示选中的行,推拽改变列宽度,按列排序,这些基本功能咱们就不提了。
自动生成行号,支持checkbox全选,动态选择显示哪些列,支持本地以及远程分页,可以对单元格按照自己的想法进行渲染,这些也算可以想到的功能。
再加上可编辑grid,添加新行,删除一或多行,提示脏数据,推拽改变grid大小,grid之间推拽一或多行,甚至可以在tree和grid之间进行拖拽,啊,这些功能实在太神奇了。更令人惊叹的是,这些功能竟然都在ext表格控件里实现了。

什么是ext

  • 1、ExtJS可以用来开发RIA也即富客户端的AJAX应用,是一个用javascript写的,主要用于创建前端用户界面,是一个与后台技术无关的前端ajax框架。因此,可以把ExtJS用在.Net、Java、Php等各种开发语言开发的应用中。ExtJs最开始基于YUI技术,由开发人员JackSlocum开发,通过参考JavaSwing等机制来组织可视化组件,无论从UI界面上CSS样式的应用,到数据解析上的异常处理,都可算是一款不可多得的JavaScript客户端技术的精品。
  • 2、Ext的UI组件模型和开发理念脱胎、成型于Yahoo组件库YUI和Java平台上Swing两者,并为开发者屏蔽了大量跨浏览器方面的处理。相对来说,EXT要比开发者直接针对DOM、W3C对象模型开发UI组件轻松。

Hello word

目录结构,把 Ext 文件夹放入你的项目中

index.jsp代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!--ExtJs框架开始-->
<script type="text/javascript" src="/Ext/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="/Ext/ext-all.js"></script>
<link rel="stylesheet" type="text/css" href="/Ext/resources/css/ext-all.css"/>
<!--ExtJs框架结束-->
<script type="text/javascript">
    Ext.onReady(function () {
        Ext.MessageBox.alert('标题', 'Hello World!');
    });
</script>
<html>
<head>
    <title></title>
</head>
<body>
<!--
18 说明:
19 (1)Ext.onReady():ExtJS Application的入口...就相当于Java或C#的main函数.
20 (2)Ext.MessageBox.alert():弹出对话框。
21 -->
</body>
</html>

Extjs 窗体 : Window组件

代码如下

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!--ExtJs框架开始-->
<script type="text/javascript" src="/Ext/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="/Ext/ext-all.js"></script>
<link rel="stylesheet" type="text/css" href="/Ext/resources/css/ext-all.css"/>
  <!--ExtJs框架结束-->
  <script type="text/javascript">
    Ext.onReady(function () {
      var win = new Ext.Window({
        title: '窗口',
        width: 476,
        height: 374,
        html: '<div>这里显示内容</div>',
        resizable: true,
        modal: true,
        closable: true,
        maximizable: true,
        minimizable: true
      });
      win.show();
    });
  </script>
</head>
<body>
<!--
说明:
(1)var win = new Ext.Window({}):创建一个新的Window窗体对象。
(2)title: '窗口':窗体的标题。
(3)width: 476,height: 374:宽度及高度。
(4)html: '<div>这里是窗体内容</div>':窗体内部显示的html内容。
(5)resizable: true:是否可以调整窗体的大小,这里设置为 true。
(6)modal: true:是否为模态窗体[什么是模态窗体?当你打开这个窗体以后,如果不能对其他的窗体进行操作,那么这个窗体就是模态窗体,否则为非模态窗体]。
(7)closable:true:是否可以关闭,也可以理解为是否显示关闭按钮。
(8)maximizable: true:是否可以最大化,也可以理解为是否显示最大化按钮。
(9)minimizable: true:是否可以最小化,也可以理解为是否显示最小化按钮。
(10)win.show():窗体展示。
-->
</body>
</html>

window 组件常用的:属性、方法及事件

一、属性

plain:布尔类型,true表示强制与背景色保持协调,默认值为false。
resizable:布尔类型,用户是否可以调整窗体大小,默认值为true表示可以调整大小。
maxinizable:布尔类型,true表示显示最大化按钮,默认值为false。
maximized:布尔类型,true表示显示窗体时将窗体最大化,默认值为false。
closable:布尔类型,true表示显示关闭按钮,默认值为true。
bodyStyle:与边框的间距,如:bodyStyle:”padding:3px”。
buttonAlign:窗体中button的对齐方式(left、center、right),默认值为right。
closeAction:”close”释放窗体所占内存,”hide”隐藏窗体,建议使用”hide”。

二、方法

show:打开窗体。
hide:隐藏窗体。
close:关闭窗体。

三、事件

show:打开窗体时触法。
hide:隐藏窗体时触法。
close:关闭窗体时触法。

Extjs 表单 : FormPanel

代码如下

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <title></title>
<!--ExtJs框架开始-->
<script type="text/javascript" src="/Ext/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="/Ext/ext-all.js"></script>
 <link rel="stylesheet" type="text/css" href="/Ext/resources/css/ext-all.css" />
<!--ExtJs框架结束-->
      <script type="text/javascript">
      Ext.onReady(function () {
            var form = new Ext.form.FormPanel({
              frame: true,
             title: '表单标题',
              style: 'margin:10px',
                html: '<div style="padding:10px">这里表单内容</div>'
           });
          var win = new Ext.Window({
                title: '窗口',
                width: 476,
               height: 374,
                html: '<div>这里是窗体内容</div>',
               resizable: true,
               modal: true,
               closable: true,
               maximizable: true,
               minimizable: true,
                items: form
           });
           win.show();
        });
    </script>
</head>
<body>
<!--
36 说明:
37 (1)var form = new Ext.form.FormPanel({}):创建一个新的form表单对象。
38 (2)title: '表单标题':表单的标题,如果不加的话,不会出现上面的浅色表单标题栏。
39 (3)style: 'margin:10px':表单的样式,加了个外10px的外边距。
40 (4)html: '<div style="padding:10px">这里表单内容</div>':表单内显示html的内容。
41 -->
</body>
</html>

form 组件常用的:属性、方法及事件

一、属性

width:整型,表单宽度。
height:整型,表单高度。
url:字符串,表单提交地址。

二、方法

reset:表单重置。
isValid:表单是否验证全部通过。
submit:表单提交。

Extjs 文本框 : TextField

代码如下

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title></title>
    <!--ExtJs框架开始-->
    <script type="text/javascript" src="/Ext/adapter/ext/ext-base.js"></script>
    <script type="text/javascript" src="/Ext/ext-all.js"></script>
    <link rel="stylesheet" type="text/css" href="/Ext/resources/css/ext-all.css"/>
    <!--ExtJs框架结束-->
    <script type="text/javascript">
        Ext.onReady(function () {
            //初始化标签中的Ext:Qtip属性。
            Ext.QuickTips.init();
            Ext.form.Field.prototype.msgTarget = 'side';
            //用户名input
            var txtusername = new Ext.form.TextField({
                width: 140,
                allowBlank: false,
                maxLength: 20,
                name: 'username',
                fieldLabel: '用户名称',
                blankText: '请输入用户名',
                maxLengthText: '用户名不能超过20个字符'
            });
            //密码input
            var txtpassword = new Ext.form.TextField({
                width: 140,
                allowBlank: false,
                maxLength: 20,
                inputType: 'password',
                name: 'password',
                fieldLabel: '密码',
                blankText: '请输入密码',
                maxLengthText: '密码不能超过20个字符'
            });
            //表单
            var form = new Ext.form.FormPanel({
                frame: true,
                title: '表单标题',
                style: 'margin:10px',
                html: '<div style="padding:10px">这里表单内容</div>',
                items: [txtusername, txtpassword]
            });
            //窗体
            var win = new Ext.Window({
                title: '窗口',
                width: 476,
                height: 374,
                html: '<div>这里是窗体内容</div>',
                resizable: true,
                modal: true,
                closable: true,
                maximizable: true,
                minimizable: true,
                items: form
            });
            win.show();
        });
    </script>
</head>
<body>
<!--
说明:
(1)Ext.QuickTips.init():QuickTips的作用是初始化标签中的Ext:Qtip属性,并为它赋予显示提示的动作。
(2)Ext.form.Field.prototype.msgTarget = 'side':TextField的提示方式为:在右边缘,如上图所示,参数枚举值为"qtip","title","under","side",id(元素id),
   side方式用的较多,右边出现红色感叹号,鼠标上去出现错误提示。
(3)var txtusername = new Ext.form.TextField():创建一个新的TextField文本框对象。
(4)allowBlank: false:不允许文本框为空。
(5)maxLength: 20:文本框的最大长度为20个字符,当超过20个字符时仍然可以继续输入,但是Ext会提示警告信息。
(6)name: 'password':表单名称,这个比较重要,因为我们在与服务器交互的时候,服务端是按name接收post的参数值。
(7)fieldLabel: '用户名':文本框前面显示的文字标题,如“用户名”,“密码”等。
(8)blankText: '请输入用户名':当非空校验没有通过时的提示信息。
(9) maxLengthText: '用户不能超过20个字符':当最大长度校验没有通过时的提示信息。
-->
</body>
</html>

textfield组件常用的:属性、方法及事件

一、属性

allowBlank:是否允许为空,默认为true
blankText:空验证失败后显示的提示信息
emptyText:在一个空字段中默认显示的信息
grow:字段是否自动伸展和收缩,默认为false
growMin:收缩的最小宽度
growMax:伸展的最大宽度
inputType:字段类型:默认为text
maskRe:用于过滤不匹配字符输入的正则表达式
maxLength:字段允许输入的最大长度
maxLengthText:最大长度验证失败后显示的提示信息
minLength:字段允许输入的最小长度
minLengthText:最小长度验证失败后显示的提示信息

Extjs 按钮 : Button

代码如下

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <title></title>
  <!--ExtJs框架开始-->
  <script type="text/javascript" src="/Ext/adapter/ext/ext-base.js"></script>
  <script type="text/javascript" src="/Ext/ext-all.js"></script>
  <link rel="stylesheet" type="text/css" href="/Ext/resources/css/ext-all.css" />
  <!--ExtJs框架结束-->
  <script type="text/javascript">
    Ext.onReady(function () {
      //初始化标签中的Ext:Qtip属性。
      Ext.QuickTips.init();
      Ext.form.Field.prototype.msgTarget = 'side';
      //提交按钮处理方法
      var btnsubmitclick = function () {
        Ext.MessageBox.alert('提示', '你点了确定按钮!');
      }
      //重置按钮"点击时"处理方法
      var btnresetclick = function () {
        Ext.MessageBox.alert('提示', '你点了重置按钮!');
      }
      //重置按钮"鼠标悬停"处理方法
      var btnresetmouseover = function () {
        Ext.MessageBox.alert('提示', '你鼠标悬停在重置按钮之上!');
      }
      //提交按钮
      var btnsubmit = new Ext.Button({
        text: '提交',
        handler: btnsubmitclick
      });
      //重置按钮
      var btnreset = new Ext.Button({
        text: '重置',
        listeners: {
          'mouseover': btnresetmouseover,
          'click': btnresetclick
        }
      });
      //用户名input
      var txtusername = new Ext.form.TextField({
        width: 140,
        allowBlank: false,
        maxLength: 20,
        name: 'username',
        fieldLabel: '用户名称',
        blankText: '请输入用户名',
        maxLengthText: '用户名不能超过20个字符'
      });
      //密码input
      var txtpassword = new Ext.form.TextField({
        width: 140,
        allowBlank: false,
        maxLength: 20,
        inputType: 'password',
        name: 'password',
        fieldLabel: '密码',
        blankText: '请输入密码',
        maxLengthText: '密码不能超过20个字符'
      });
      //表单
      var form = new Ext.form.FormPanel({
        frame: true,
        title: '表单标题',
        style: 'margin:10px',
        html: '<div style="padding:10px">这里表单内容</div>',
        items: [txtusername, txtpassword],
        buttons: [btnsubmit, btnreset]
      });
      //窗体
      var win = new Ext.Window({
        title: '窗口',
        width: 476,
        height: 374,
        html: '<div>这里是窗体内容</div>',
        resizable: true,
        modal: true,
        closable: true,
        maximizable: true,
        minimizable: true,
        buttonAlign: 'center',
        items: form
      });
      win.show();
    });
  </script>
</head>
<body>
<!--
说明:
(1)var btnsubmit = new Ext.Button():创建一个新的Button按钮对象。
(2)handler: btnsubmitclick:当用户点击的时候[即js中的onclick事件]执行方法btnsubmitclick。
(3)listeners: {'mouseover': btnresetmouseover,'click': btnresetclick}:当用户点击的时候[即js中的onclick事件]执行方法btnresetclick,
    鼠标悬停时执行方法btnresetmouseover。
(4)handler与listeners的区别:
    handler:执行的是首发事件,click是button这个组件的首发事件。这就是handler的运行方式:被某个组件的首要event所触发。
            handler是一个特殊的listener。
    listener:是一个事件名 + 处理函数的组合,事件监听,如上例代码所示,我们监听了两个事件"click",与"mouseover"事件,并且会顺序执行。
-->
</body>
</html>

button组件常用的:属性、方法及事件

一、属性

text:字符串,显示在按钮上的文字。
minWidth: 整型,最小宽度。

二、事件

handler:首发方法处理事件。
listeners:事件监听。

Extjs 登录窗体:login

代码如下

<%--
  Created by IntelliJ IDEA.
  User: Dreamer
  Date: 2016/1/30
  Time: 18:11
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <title></title>
  <!--ExtJs框架开始-->
  <script type="text/javascript" src="/Ext/adapter/ext/ext-base.js"></script>
  <script type="text/javascript" src="/Ext/ext-all.js"></script>
  <link rel="stylesheet" type="text/css" href="/Ext/resources/css/ext-all.css" />
  <style type="text/css">
    .loginicon
    {
      background-image: url(image/login.gif) !important;
    }
  </style>
  <!--ExtJs框架结束-->
  <script type="text/javascript">
    Ext.onReady(function () {
      //初始化标签中的Ext:Qtip属性。
      Ext.QuickTips.init();
      Ext.form.Field.prototype.msgTarget = 'side';
      //提交按钮处理方法
      var btnsubmitclick = function () {
        if (form.getForm().isValid()) {
          //通常发送到服务器端获取返回值再进行处理,我们在以后的教程中再讲解表单与服务器的交互问题。
          Ext.Msg.alert("提示", "登陆成功!");
        }
      }
      //重置按钮"点击时"处理方法
      var btnresetclick = function () {
        form.getForm().reset();
      }
      //提交按钮
      var btnsubmit = new Ext.Button({
        text: '提 交',
        handler: btnsubmitclick
      });
      //重置按钮
      var btnreset = new Ext.Button({
        text: '重 置',
        handler: btnresetclick
      });
      //用户名input
      var txtusername = new Ext.form.TextField({
        width: 140,
        allowBlank: false,
        maxLength: 20,
        name: 'username',
        fieldLabel: '用户名',
        blankText: '请输入用户名',
        maxLengthText: '用户名不能超过20个字符'
      });
      //密码input
      var txtpassword = new Ext.form.TextField({
        width: 140,
        allowBlank: false,
        maxLength: 20,
        inputType: 'password',
        name: 'password',
        fieldLabel: '密 码',
        blankText: '请输入密码',
        maxLengthText: '密码不能超过20个字符'
      });
      //验证码input
      var txtcheckcode = new Ext.form.TextField({
        fieldLabel: '验证码',
        id: 'checkcode',
        allowBlank: false,
        width: 76,
        blankText: '请输入验证码!',
        maxLength: 4,
        maxLengthText: '验证码不能超过4个字符!'
      });
      //表单
      var form = new Ext.form.FormPanel({
        url: '******',
        labelAlign: 'right',
        labelWidth: 45,
        frame: true,
        cls: 'loginform',
        buttonAlign: 'center',
        bodyStyle: 'padding:6px 0px 0px 15px',
        items: [txtusername, txtpassword, txtcheckcode],
        buttons: [btnsubmit, btnreset]
      });
      //窗体
      var win = new Ext.Window({
        title: '用户登陆',
        iconCls: 'loginicon',
        plain: true,
        width: 276,
        height: 174,
        resizable: false,
        shadow: true,
        modal: true,
        closable: false,
        animCollapse: true,
        items: form
      });
      win.show();
      //创建验证码
      var checkcode = Ext.getDom('checkcode');
      var checkimage = Ext.get(checkcode.parentNode);
      checkimage.createChild({
        tag: 'img',
        src: 'image/checkcode.gif',
        align: 'absbottom',
        style: 'padding-left:23px;cursor:pointer;'
      });
    });
  </script>
</head>
<body>
<!--
说明:
(1)88行,iconCls: 'loginicon':给窗体加上小图标,样式在第12行定义。
(2)Ext.getDom('checkcode'):根据ID获取Dom。
(3)Ext.get(checkcode.parentNode):根据Dom获取父节点。
(4)checkimage.createChild():创建子节点,标签为<img src='image/checkcode.gif'..../>。
(5)form.getForm().isValid():校验表单的验证项是否全部通过。
(6)form.getForm().reset():重置表单。
-->
</body>
</html>

web.xml详解

web.xml的作用

web.xml,一个Tomcat工程中最重要的配置文件。web.xml没有其实也可以—-只要你确定你的项目里面不需要任何过滤器、监听器、Servlet等等。我试了一下,没有web.xml对那些已经编译成Servlet的jsp页面来说,是不影响正常显示的,但是那些没有编译成Servlet的jsp页面,访问的时候就会报500的错误了。下面逐一看一下web.xml里常用标签的作用。

welcome-file-list

这个标签是用来配置首页用的:

<welcome-file-list>
    <welcome-file>index1.jsp</welcome-file>
    <welcome-file>index2.jsp</welcome-file>
    <welcome-file>index3.jsp</welcome-file>
    <welcome-file>index4.jsp</welcome-file>
    <welcome-file>/target/redirectAndFoward.jsp</welcome-file>
</welcome-file-list>

这么配置的意思,就是当用户访问http://ip:port/工程名的时候,会根据welcome-file-list配置的页面列表,从项目的根目录开始找页面:

  • 第一个配置的index1.jsp能找到,就展示index1.jsp
  • 找不到index1.jsp,则去找第二个index2.jsp,index2.jsp能找到就展示index2.jsp,
  • 找不到index3.jsp,则去找第三个index3.jsp,以此类推,如果所有的页面都找不到则报HTTP Status 404即页面找不到

注意一下,像配置的最后一个welcome-file这种写法也是支持的,我试了一下最前面的那个”/“可加可不加

error-page

error-page表示当HTTP返回指定状态码的时候,容器将此次请求转发到配置的指定页面:

<error-page>
    <error-code>400</error-code>
    <location>/filter/error.jsp</location>
</error-page>
  
<error-page>
    <error-code>404</error-code>
    <location>/filter/error.jsp</location>
</error-page>
  
<error-page>
    <error-code>500</error-code>
    <location>/filter/error.jsp</location>
</error-page>

这表示HTTP状态码为400、404、500的时候,此次请求都会被转发到http://ip:port/工程名/filter/error.jsp这个页面上去。注意一下这里是error-code,所以如果是200的话,是没有效果的

filter

走filter的顺序就是filter定义的顺序

servlet

servlet开发者比较熟悉,先匹配规则,匹配到路径后走相应的Servlet类,就不说了。下面配一个相对不那么常用的,只是相对而已,这种servlet的写法很常见:

<servlet>
    <servlet-name>startUpServlet</servlet-name>
    <servlet-class>com.xrq.servlet.StartUpServlet</servlet-class>
    <init-param>
        <param-name>Name</param-name>
        <param-value>123</param-value>
    </init-param>
    <init-param>
        <param-name>Age</param-name>
        <param-value>456</param-value>
    </init-param>
    <load-on-startup>8</load-on-startup>
</servlet>

这是一个启动servlet,表示容器启动的时候servlet启动,调用其init()方法,所以首先第一个标签load-on-start,分几点说:

  • load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init方法)
  • 它的值必须是一个整数,表示servlet应该被载入的顺序
  • 当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet
  • 当值小于0或者没有指定时,表示这个容器在该servlet被选择时才会去加载
  • 正数值越小,该servlet的优先级就越高,应用启动时就越先加载
  • 当值相同时,容器自己选择顺序来加载

所以,load-on-startup中配置了一个大于等于0的正整数时,该servlet可以当作一个普通的servlet来用,无非是这个servlet启动的时候会加载其init()方法罢了。

另外一个就是init-param了,表示一个键值对,只能在本servlet里面被使用,通过ServletConfig获取,StartUpServlet的写法是:

public class StartUpServlet extends HttpServlet{
    /**
     * 序列化
     */
    private static final long serialVersionUID = 1L;
    
    public void init() throws ServletException
    {
        System.out.println("StartUpServlet.init()");
        System.out.println("Name:" + getServletConfig().getInitParameter("Name"));
        System.out.println("Age:" + getServletConfig().getInitParameter("Age"));
    }
    
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
    {
        
    }
    
    public void destroy()
    {
        System.out.println("StartUpServlet.destory()");
    }
}

servlet只能取到配置在自己的标签内的

listener

listener即监听器,是Servlet的监听器,它可以监听客户端的请求、服务器端的操作等,在事情发生前后做一些必要的处理。通过监听器,可以自动激发一些操作,比如监听在线用户数量,下面就写一个监听用户数量的监听器,首先web.xml配置很简单:

<listener>
    <listener-class>com.xrq.listener.UserCounterListener</listener-class>
</listener>

写一个监听器,监听用户数量一般都是以session创建和session失效为依据的,所以实现HttpSessionListener:

public class UserCounterListener implements HttpSessionListener{
    private AtomicInteger ai = new AtomicInteger(0);
    
    public void sessionCreated(HttpSessionEvent se)
    {
        ai.incrementAndGet();
    }
    
    public void sessionDestroyed(HttpSessionEvent se)
    {
        ai.decrementAndGet();
    }
    
    public int getUserCount()
    {
        return ai.get();
    }
}

除了监听session的监听器外,再介绍一些别的监听器接口:

ServletContextListener

用于监听WEB引用启动和销毁的事件,SevletContextListener是ServletContext的监听者,如果ServletContext发生变化,如服务器启动、服务器关闭,都会被ServletContextListener监听到。监听事件为ServletContextEvent

ServletContextAttributeListener

用于监听WEB应用属性改变的事件,包括添加属性、删除属性、修改属性。监听时间为ServletContextAttributeEvent

HttpSessionBindingListener

HttpSessionBindingListener是唯一一个不需要在web.xml中配置的Listener,当我们的类实现了HttpSessionBindListener接口后,只要对象加入session或者从session中移除,容器会分别自动调用以下两个方法:

  • void valueBound(HttpSesssionBindEvent event)
  • void valueUnbound(HttpSessionBindingEvent event)

注意,这个监听器的触发是针对于实现了该监听器的类的,只有把实现了该监听器的类set进session或从session中remove才会触发这个监听器

HttpSessionAttributeListener

用于监听HttpSession中的属性的操作,当session里面增加一个属性时,触发attributeAdded(HttpSessionBindEvent se)方法;当在session中删除一个属性时,触发attributeRemoved(HttpSessionBindEvent se)方法;当session属性被重新设置时,触发attributeReplaced(HttpSessionBindingEvent se)方法。

注意,这个监听器的触发是针对所有的session的,只要session的属性发生变化,都会触发这个监听器

HttpSessionListener

这个上面已经写过了,监听HttpSession。当创建一个session时,触发sessionCreated(HttpSessionEvent se)方法;当销毁一个session时,会触发sessionDestoryed(HttpSessionEvent se)方法

HttpSessionActivationListener

这个用得不太多,主要监听同一个session转移至不同的JVM的情形

ServletRequestListener和ServletRequestAttributeListener

和ServletContextListener和ServletContextAttributeListener类似,前者监听Request的创建和销毁、后者监听Request中属性的增删改

context-param

context-param里面配置的键值对是全局共享的,整个web项目都能取到这个上下文,比方说我在web.xml里面配置了一个HTTP端口和一个HTTPS端口:

<context-param>
    <param-name>NotSSLPort</param-name>
    <param-value>8080</param-value>
</context-param>
<context-param>
    <param-name>SSLPort</param-name>
    <param-value>8443</param-value>
</context-param>

servlet可以这么取:

protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
{
    System.out.println("NotSSLPort:" + getServletContext().getInitParameter("NotSSLPort"));
    System.out.println("SSLPort:" + getServletContext().getInitParameter("SSLPort"));
}

filter可以这么取:

public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException
{
    HttpServletRequest req = (HttpServletRequest)request;
    ServletContext sc = req.getSession().getServletContext();
    System.out.println("NotSSLPort:" + sc.getInitParameter("NotSSLPort"));
    System.out.println("SSLPort:" + sc.getInitParameter("SSLPort"));
    chain.doFilter(request, response);
}

listener可以这么取,以ServletContextListener为例:

public void contextInitialized(ServletContextEvent sce)
{
    System.out.println("Enter SCListener.contextInitialized");
    System.out.println("NotSSLPort:" + sce.getServletContext().getInitParameter("NotSSLPort"));
    System.out.println("SSLPort:" + sce.getServletContext().getInitParameter("SSLPort"));
}

反正最终的目的就是取到一个ServletContext就对了。是不是感觉ServletContext很熟悉呢?没错,看一下jsp默认的内置对象,随便打开一个转换成Servlet的jsp页面,里面都有内置对象的定义:

PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;

ServletContext也就是我们常说的Application

可能不太常见,这个标签是用来指定对应的格式的浏览器处理方式的,添加mime-type的映射,就可以避免某些类型的文件直接在浏览器中打开了。

举个例子:

<mime-mapping>
    <extension>doc</extension>
    <mime-type>application/msword</mime-type>
</mime-mapping>
<mime-mapping>
    <extension>pdf</extension>
    <mime-type>application/pdf</mime-type>
</mime-mapping>
<mime-mapping>
    <extension>rar</extension>
    <mime-type>application/x-rar-compressed</mime-type>
</mime-mapping>
<mime-mapping>
    <extension>txt</extension>
    <mime-type>text/plain</mime-type>
</mime-mapping>
<mime-mapping>
    <extension>xls</extension>
    <mime-type>application/vnd.ms-excel</mime-type>
</mime-mapping>

session-config

session-config是用来配置session失效时间的,因为session-config里面只有一个子标签:

<session-config>
    <session-timeout>30</session-timeout>
</session-config>

以分钟为单位。当然,代码里面也可以设置:

“request.getSession.setMaxInactiveInterval(30 * 60);”就可以了,单位为秒

元素加载顺序

首先可以肯定,加载顺序与它们在web.xml文件中的先后顺序无关,即不会因为filter写在listener前面就先加载filter。最终得出的结论是listener->filter->servlet。

然后是context-param,用于向ServletContext提供键值对,即应用程序上下文信息,listener、filter、servlet都可能会用到这些上下文中的信息,那么context-param是否应该写在listener、filter、servlet前面呢?未必,context-param可以写在任何位置,因此真正的加载顺序应该是:context-param->listener->filter->servlet。

Listener 监听器

Listener的定义与作用

监听器Listener就是在application,session,request三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。
Listener是Servlet的监听器,可以监听客户端的请求,服务端的操作等。

Listener的分类与使用

主要有以下三类:

ServletContext监听

ServletContextListener:用于对Servlet整个上下文进行监听(创建、销毁)。

public void contextInitialized(ServletContextEvent sce);//上下文初始化
public void contextDestroyed(ServletContextEvent sce);//上下文销毁

public ServletContext getServletContext();//ServletContextEvent事件:取得一个ServletContext(application)对象

ServletContextAttributeListener:对Servlet上下文属性的监听(增删改属性)。

public void attributeAdded(ServletContextAttributeEvent scab);//增加属性
public void attributeRemoved(ServletContextAttributeEvent scab);//属性删除
public void attributeRepalced(ServletContextAttributeEvent scab);//属性替换(第二次设置同一属性)

//ServletContextAttributeEvent事件:能取得设置属性的名称与内容
public String getName();//得到属性名称
public Object getValue();//取得属性的值

Session监听

Session属于http协议下的内容,接口位于javax.servlet.http.*包下。

HttpSessionListener接口:对Session的整体状态的监听。

public void sessionCreated(HttpSessionEvent se);//session创建
public void sessionDestroyed(HttpSessionEvent se);//session销毁

//HttpSessionEvent事件:
public HttpSession getSession();//取得当前操作的session

HttpSessionAttributeListener接口:对session的属性监听。

public void attributeAdded(HttpSessionBindingEvent se);//增加属性
public void attributeRemoved(HttpSessionBindingEvent se);//删除属性
public void attributeReplaced(HttpSessionBindingEvent se);//替换属性

//HttpSessionBindingEvent事件:
public String getName();//取得属性的名称
public Object getValue();//取得属性的值
public HttpSession getSession();//取得当前的session

session的销毁有两种情况:

  • session超时,web.xml配置:

    120
  • 手工使session失效

    public void invalidate();//使session失效方法。session.invalidate();

Request监听

ServletRequestListener:用于对Request请求进行监听(创建、销毁)。

public void requestInitialized(ServletRequestEvent sre);//request初始化
public void requestDestroyed(ServletRequestEvent sre);//request销毁

//ServletRequestEvent事件:
public ServletRequest getServletRequest();//取得一个ServletRequest对象
public ServletContext getServletContext();//取得一个ServletContext(application)对象

ServletRequestAttributeListener:对Request属性的监听(增删改属性)。

public void attributeAdded(ServletRequestAttributeEvent srae);//增加属性
public void attributeRemoved(ServletRequestAttributeEvent srae);//属性删除
public void attributeReplaced(ServletRequestAttributeEvent srae);//属性替换(第二次设置同一属性)

//ServletRequestAttributeEvent事件:能取得设置属性的名称与内容
public String getName();//得到属性名称
public Object getValue();//取得属性的值

在web.xml中配置

Listener配置信息必须在Filter和Servlet配置之前,Listener的初始化(ServletContentListener初始化)比Servlet和Filter都优先,而销毁比Servlet和Filter都慢。

<listener>
    <listener-class>com.listener.class</listener-class>
</listener>

Listener应用实例

利用HttpSessionListener统计最多在线用户人数

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class HttpSessionListenerImpl implements HttpSessionListener {

    public void sessionCreated(HttpSessionEvent event) {
        ServletContext app = event.getSession().getServletContext();
        int count = Integer.parseInt(app.getAttribute("onLineCount").toString());
        count++;
        app.setAttribute("onLineCount", count);
        int maxOnLineCount = Integer.parseInt(app.getAttribute("maxOnLineCount").toString());
        if (count > maxOnLineCount) {
            //记录最多人数是多少
            app.setAttribute("maxOnLineCount", count);
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //记录在那个时刻达到上限
            app.setAttribute("date", df.format(new Date()));
        }
    }
    //session注销、超时时候调用,停止tomcat不会调用
    public void sessionDestroyed(HttpSessionEvent event) {
        ServletContext app = event.getSession().getServletContext();
        int count = Integer.parseInt(app.getAttribute("onLineCount").toString());
        count--;
        app.setAttribute("onLineCount", count);    
        
    }
}

Spring使用ContextLoaderListener加载ApplicationContext配置信息

ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。

ContextLoaderListener如何查找ApplicationContext.xml的配置位置以及配置多个xml:如果在web.xml中不写任何参数配置信息,默认的路径是”/WEB-INF/applicationContext.xml”,在WEB-INF目录下创建的xml文件的名称必须是applicationContext.xml(在MyEclipse中把xml文件放置在src目录下)。如果是要自定义文件名可以在web.xml里加入contextConfigLocation这个context参数。

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring/applicationContext-*.xml</param-value><!-- 采用的是通配符方式,查找WEB-INF/spring目录下xml文件。如有多个xml文件,以“,”分隔。 -->
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

Spring使用Log4jConfigListener配置Log4j日志

Spring使用Log4jConfigListener的好处:

  1. 动态的改变记录级别和策略,不需要重启Web应用。

  2. 把log文件定在 /WEB-INF/logs/ 而不需要写绝对路径。因为系统把web目录的路径压入一个叫webapp.root的系统变量。这样写log文件路径时不用写绝对路径了。

  3. 可以把log4j.properties和其他properties一起放在/WEB-INF/ ,而不是Class-Path。

  4. 设置log4jRefreshInterval时间,开一条watchdog线程每隔段时间扫描一下配置文件的变化。

    webAppRootKey project.root log4jConfigLocation classpath:log4j.properties log4jRefreshInterval 60000 org.springframework.web.util.Log4jConfigListener

Spring使用IntrospectorCleanupListener清理缓存

这个监听器的作用是在web应用关闭时刷新JDK的JavaBeans的Introspector缓存,以确保Web应用程序的类加载器以及其加载的类正确的释放资源。

如果JavaBeans的Introspector已被用来分析应用程序类,系统级的Introspector缓存将持有这些类的一个硬引用。因此,这些类和Web应用程序的类加载器在Web应用程序关闭时将不会被垃圾收集器回收!而IntrospectorCleanupListener则会对其进行适当的清理,已使其能够被垃圾收集器回收。

唯一能够清理Introspector的方法是刷新整个Introspector缓存,没有其他办法来确切指定应用程序所引用的类。这将删除所有其他应用程序在服务器的缓存的Introspector结果。

在使用Spring内部的bean机制时,不需要使用此监听器,因为Spring自己的introspection results cache将会立即刷新被分析过的JavaBeans Introspector cache,而仅仅会在应用程序自己的ClassLoader里面持有一个cache。虽然Spring本身不产生泄漏,注意,即使在Spring框架的类本身驻留在一个“共同”类加载器(如系统的ClassLoader)的情况下,也仍然应该使用使用IntrospectorCleanupListener。在这种情况下,这个IntrospectorCleanupListener将会妥善清理Spring的introspection cache。

  应用程序类,几乎不需要直接使用JavaBeans Introspector,所以,通常都不是Introspector resource造成内存泄露。相反,许多库和框架,不清理Introspector,例如: Struts和Quartz。

  需要注意的是一个简单Introspector泄漏将会导致整个Web应用程序的类加载器不会被回收!这样做的结果,将会是在web应用程序关闭时,该应用程序所有的静态类资源(比如:单实例对象)都没有得到释放。而导致内存泄露的根本原因其实并不是这些未被回收的类!

  注意:IntrospectorCleanupListener应该注册为web.xml中的第一个Listener,在任何其他Listener之前注册,比如在Spring’s ContextLoaderListener注册之前,才能确保IntrospectorCleanupListener在Web应用的生命周期适当时机生效。

<!-- memory clean -->
<listener>
    <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>

Log4j配置

简介

Log4J的配置文件(Configuration File)就是用来设置记录器的级别、存放器和布局的,它可接key=value格式的设置或xml格式的设置信息。通过配置,可以创建出Log4J的运行环境。

配置文件

Log4J配置文件的基本格式如下:

#配置根Logger
log4j.rootLogger  =   [ level ]   ,  appenderName1 ,  appenderName2 ,  …

#配置日志信息输出目的地Appender
log4j.appender.appenderName  =  fully.qualified.name.of.appender.class 
    log4j.appender.appenderName.option1  =  value1
    ...
    log4j.appender.appenderName.optionN  =  valueN 

#配置日志信息的格式(布局)
log4j.appender.appenderName.layout  =  fully.qualified.name.of.layout.class 
    log4j.appender.appenderName.layout.option1  =  value1
    ...
    log4j.appender.appenderName.layout.optionN  =  valueN 

其中 [level] 是日志输出级别,共有5级:

FATAL       0  
ERROR       3  
WARN        4  
INFO        6  
DEBUG       7 

Appender 为日志输出目的地,Log4j提供的appender有以下几种:

org.apache.log4j.ConsoleAppender(控制台),
org.apache.log4j.FileAppender(文件),
org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件),
org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件),
org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)

Layout:日志输出格式,Log4j提供的layout有以下几种:

org.apache.log4j.HTMLLayout(以HTML表格形式布局),
org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)

打印参数: Log4J采用类似C语言中的printf函数的打印格式格式化日志信息,如下:

%m   输出代码中指定的消息
%p   输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL 
%r   输出自应用启动到输出该log信息耗费的毫秒数 
%c   输出所属的类目,通常就是所在类的全名 
%t   输出产生该日志事件的线程名 
%n   输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n” 
%d   输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss , SSS},输出类似:2002年10月18日  22 : 10 : 28 , 921  
%l   输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java: 10 ) 

在代码中初始化Logger:

- 在程序中调用BasicConfigurator.configure()方法:给根记录器增加一个ConsoleAppender,输出格式通过PatternLayout设为"%-4r [%t] %-5p %c %x - %m%n",还有根记录器的默认级别是Level.DEBUG. 
- 配置放在文件里,通过命令行参数传递文件名字,通过PropertyConfigurator.configure(args[x])解析并配置;
- 配置放在文件里,通过环境变量传递文件名等信息,利用log4j默认的初始化过程解析并配置;
- 配置放在文件里,通过应用服务器配置传递文件名等信息,利用一个特殊的servlet来完成配置。

为不同的 Appender 设置日志输出级别:

当调试系统时,我们往往注意的只是异常级别的日志输出,但是通常所有级别的输出都是放在一个文件里的,如果日志输出的级别是BUG!?那就慢慢去找吧。
这时我们也许会想要是能把异常信息单独输出到一个文件里该多好啊。当然可以,Log4j已经提供了这样的功能,我们只需要在配置中修改Appender的Threshold 就能实现,比如下面的例子:

配置文件

### set log levels ###
log4j.rootLogger = debug ,  stdout ,  D ,  E

### 输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern =  %d{ABSOLUTE} %5p %c{ 1 }:%L - %m%n

### 输出到日志文件 ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG ## 输出DEBUG级别以上的日志
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

### 保存异常信息到单独文件 ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = logs/error.log ## 异常日志文件名
log4j.appender.D.Append = true
log4j.appender.D.Threshold = ERROR ## 只输出ERROR级别以上的日志!!!
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

mybatis

介绍

MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装。MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录

添加jar 包

【mybatis】
    mybatis-3.1.1.jar
【MYSQL驱动包】
    mysql-connector-java-5.1.7-bin.jar

添加Mybatis的配置文件conf.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.driver.OracleDriver" />
                <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl" />
                <property name="username" value="test" />
                <property name="password" value="ztt123456" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/dream/bean/userMapper.xml"/>
    </mappers>
</configuration>

定义表所对应的实体类

public class User {
private int id;
private String name;
private int age;
//get,set方法
}

定义操作users表的sql映射文件userMapper.xml

<<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dream.bean.userMapper">
 <insert id="insertUser" parameterType="com.dream.bean.User">
    insert into user11(id,name,age) values(#{id},#{name},#{age})
</insert>

<select id="getUser" parameterType="int"
    resultType="com.dream.bean.User">
    select * from user11 where id=#{id}
</select>

</mapper>

编写测试代码:执行定义的select语句

package com.dream.test;

import java.io.IOException;
import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import com.dream.bean.User;

public class myTest {

@Test
public void mTest() throws IOException {
        String resource = "conf.xml";
        // 加载mybatis的配置文件(它也加载关联的映射文件)
        Reader reader = Resources.getResourceAsReader(resource);
        // 构建sqlSession的工厂
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
        // 创建能执行映射文件中sql的sqlSession
        SqlSession session = sessionFactory.openSession();
        System.out.println(session.getConnection());
        // 映射sql的标识字符串
        String statement = "com.dream.bean.userMapper.insertUser";
        // 执行查询返回一个唯一user对象的sql
        int user = session.insert(statement, new User(1,"zhansan",3));
        // 提交
        
    //		String statement = "com.dream.bean.userMapper.getUser";
    //		User selectOne = session.selectOne(statement, 4);
    //		System.out.println(selectOne.toString());
            
        session.commit();

        session.close();

        //System.out.println(user);
    }

}

至此例子写好了


Spring Acnotation

Spring 基于 Annotation 的依赖注入实现

基于注入的容器配置

注入比配置Spring xml 更好吗?

引入基于注入的配置提出了一个问题,这种方法是否比 XML’更好’。简短的回答取决于。长期的回答是,每一种方法都有其优点和缺点,并且通常是由开发者决定哪些策略更适合他们。由于它们被定义的方式,注入提供了大量的背景下,使他们的声明,导致更短,更简洁的配置。然而,XML在连接部件不接触源代码或重新编译他们擅长。一些开发商宁愿有布线靠近源而另一些人则认为注释类不再是POJO,此外,使配置变得分散,难以控制。

无论选择哪一个,Spring可以容纳两种风格,甚至将它们组合在一起。值得指出的是,通过其javaconfig选项,Spring允许注解中使用一种非侵入性的方式,不接触部件的目标源代码,从模具,所有的配置方式是由弹簧工具套件支持。

Spring 自 2.0 版本开始,陆续引入了一些注解用于简化 Spring 的开发。@Repository 注解便属于最先引入的一批,它用于将数据访问层 (DAO 层 ) 的类标识为 Spring Bean。具体只需将该注解标注在 DAO 类上即可。同时,为了让 Spring 能够扫描类路径中的类并识别出 @Repository 注解,需要在 XML 配置文件中启用 Bean 的自动扫描功能,这可以通过 context:component-scan/ 实现。如下所示:

 // 首先使用 @Repository 将 DAO 类声明为 Bean 
 package bookstore.dao; 
 @Repository 
 public class UserDaoImpl implements UserDao{ …… } 

 // 其次,在 XML 配置文件中启动 Spring 的自动扫描功能
 <beans … > 
    ……
 <context:component-scan base-package=”bookstore.dao” /> 
……
 </beans>

如此,我们就不再需要在 XML 中显式使用 进行 Bean 的配置。Spring 在容器初始化时将自动扫描 base-package 指定的包及其子包下的所有 class 文件,所有标注了 @Repository 的类都将被注册为 Spring Bean。
为什么 @Repository 只能标注在 DAO 类上呢?这是因为该注解的作用不只是将类识别为 Bean,同时它还能将所标注的类中抛出的数据访问异常封装为 Spring 的数据访问异常类型。 Spring 本身提供了一个丰富的并且是与具体的数据访问技术无关的数据访问异常结构,用于封装不同的持久层框架抛出的异常,使得异常独立于底层的框架。

Spring 2.5 在 @Repository 的基础上增加了功能类似的额外三个注解:@Component、@Service、@Constroller,它们分别用于软件系统的不同层次:

  • @Component 是一个泛化的概念,仅仅表示一个组件 (Bean) ,可以作用在任何层次。
  • @Service 通常作用在业务层,但是目前该功能与 @Component 相同。
  • @Constroller 通常作用在控制层,但是目前该功能与 @Component 相同

通过在类上使用 @Repository、@Component、@Service 和 @Constroller 注解,Spring 会自动创建相应的 BeanDefinition 对象,并注册到 ApplicationContext 中。这些类就成了 Spring 受管组件。这三个注解除了作用于不同软件层次的类,其使用方式与 @Repository 是完全相同的。

另外,除了上面的四个注解外,用户可以创建自定义的注解,然后在注解上标注 @Component,那么,该自定义注解便具有了与所 @Component 相同的功能。不过这个功能并不常用。

当一个 Bean 被自动检测到时,会根据那个扫描器的 BeanNameGenerator 策略生成它的 bean 名称。默认情况下,对于包含 name 属性的 @Component、@Repository、 @Service 和 @Controller,会把 name 取值作为 Bean 的名字。如果这个注解不包含 name 值或是其他被自定义过滤器发现的组件,默认 Bean 名称会是小写开头的非限定类名。如果你不想使用默认 bean 命名策略,可以提供一个自定义的命名策略。首先实现 BeanNameGenerator 接口,确认包含了一个默认的无参数构造方法。然后在配置扫描器时提供一个全限定类名,如下所示:

<beans ...> 
 <context:component-scan 
base-package="a.b" name-generator="a.SimpleNameGenerator"/> 
 </beans>

与通过 XML 配置的 Spring Bean 一样,通过上述注解标识的 Bean,其默认作用域是”singleton”,为了配合这四个注解,在标注 Bean 的同时能够指定 Bean 的作用域,Spring 2.5 引入了 @Scope 注解。使用该注解时只需提供作用域的名称就行了,如下所示:

 @Scope("prototype") 
 @Repository 
 public class Demo { … }

如果你想提供一个自定义的作用域解析策略而不使用基于注解的方法,只需实现 ScopeMetadataResolver 接口,确认包含一个默认的没有参数的构造方法。然后在配置扫描器时提供全限定类名:

<context:component-scan base-package="a.b"
 scope-resolver="footmark.SimpleScopeResolver" />

使用 @PostConstruct 和 @PreDestroy 指定生命周期回调方法

Spring Bean 是受 Spring IoC 容器管理,由容器进行初始化和销毁的(prototype 类型由容器初始化之后便不受容器管理),通常我们不需要关注容器对 Bean 的初始化和销毁操作,由 Spring 经过构造函数或者工厂方法创建的 Bean 就是已经初始化完成并立即可用的。然而在某些情况下,可能需要我们手工做一些额外的初始化或者销毁操作,这通常是针对一些资源的获取和释放操作。Spring 1.x 为此提供了两种方式供用户指定执行生命周期回调的方法。

第一种方式是实现 Spring 提供的两个接口:InitializingBean 和 DisposableBean。如果希望在 Bean 初始化完成之后执行一些自定义操作,则可以让 Bean 实现 InitializingBean 接口,该接口包含一个 afterPropertiesSet() 方法,容器在为该 Bean 设置了属性之后,将自动调用该方法;如果 Bean 实现了 DisposableBean 接口,则容器在销毁该 Bean 之前,将调用该接口的 destroy() 方法。这种方式的缺点是,让 Bean 类实现 Spring 提供的接口,增加了代码与 Spring 框架的耦合度,因此不推荐使用。

第二种方式是在 XML 文件中使用 的 init-method 和 destroy-method 属性指定初始化之后和销毁之前的回调方法,代码无需实现任何接口。这两个属性的取值是相应 Bean 类中的初始化和销毁方法,方法名任意,但是方法不能有参数。示例如下:

<bean id=”userService” 
 class=”bookstore.service.UserService” 
 init-method=”init” destroy-method=”destroy”> 
    …
 </bean>

Spring 2.5 在保留以上两种方式的基础上,提供了对 JSR-250 的支持。JSR-250 规范定义了两个用于指定声明周期方法的注解:@PostConstruct 和 @PreDestroy。这两个注解使用非常简单,只需分别将他们标注于初始化之后执行的回调方法或者销毁之前执行的回调方法上。由于使用了注解,因此需要配置相应的 Bean 后处理器,亦即在 XML 中增加如下一行:

<context:annotation-config />

比较上述三种指定生命周期回调方法的方式,第一种是不建议使用的,不但其用法不如后两种方式灵活,而且无形中增加了代码与框架的耦合度。后面两种方式开发者可以根据使用习惯选择其中一种,但是最好不要混合使用,以免增加维护的难度。


使用 @Required 进行 Bean 的依赖检查

依赖检查的作用是,判断给定 Bean 的相应 Setter 方法是否都在实例化的时候被调用了。而不是判断字段是否已经存在值了。Spring 进行依赖检查时,只会判断属性是否使用了 Setter 注入。如果某个属性没有使用 Setter 注入,即使是通过构造函数已经为该属性注入了值,Spring 仍然认为它没有执行注入,从而抛出异常。另外,Spring 只管是否通过 Setter 执行了注入,而对注入的值却没有任何要求,即使注入的 ,Spring 也认为是执行了依赖注入

标签提供了 dependency-check 属性用于进行依赖检查。该属性的取值包括以下几种:

  • none – 默认不执行依赖检查。可以在 标签上使用 default-dependency-check 属性改变默认值。
  • simple – 对原始基本类型和集合类型进行检查。
  • objects – 对复杂类型进行检查(除了 simple 所检查类型之外的其他类型)。
  • all – 对所有类型进行检查。

旧版本使用 dependency-check 在配置文件中设置,缺点是粒度较粗。使用 Spring2.0 提供的 @Required 注解,提供了更细粒度的控制。@Required 注解只能标注在 Setter 方法之上。因为依赖注入的本质是检查 Setter 方法是否被调用了,而不是真的去检查属性是否赋值了以及赋了什么样的值。如果将该注解标注在非 setXxxx() 类型的方法则被忽略。

为了让 Spring 能够处理该注解,需要激活相应的 Bean 后处理器。要激活该后处理器,只需在 XML 中增加如下一行即可。

<context:annotation-config/>

当某个被标注了 @Required 的 Setter 方法没有被调用,则 Spring 在解析的时候会抛出异常,以提醒开发者对相应属性进行设置。


使用 @Resource、@Autowired 和 @Qualifier 指定 Bean 的自动装配策略

自动装配是指,Spring 在装配 Bean 的时候,根据指定的自动装配规则,将某个 Bean 所需要引用类型的 Bean 注入进来。 元素提供了一个指定自动装配类型的 autowire 属性,该属性有如下选项:

  • no – 显式指定不使用自动装配。
  • byName – 如果存在一个和当前属性名字一致的 Bean,则使用该 Bean 进行注入。如果名称匹配但是类型不匹配,则抛出异常。如果没有匹配的类型,则什么也不做。
  • byType – 如果存在一个和当前属性类型一致的 Bean ( 相同类型或者子类型 ),则使用该 Bean 进行注入。byType 能够识别工厂方法,即能够识别 factory-method 的返回类型。如果存在多个类型一致的 Bean,则抛出异常。如果没有匹配的类型,则什么也不做。
  • constructor – 与 byType 类似,只不过它是针对构造函数注入而言的。如果当前没有与构造函数的参数类型匹配的 Bean,则抛出异常。使用该种装配模式时,优先匹配参数最多的构造函数。
  • autodetect – 根据 Bean 的自省机制决定采用 byType 还是 constructor 进行自动装配。如果 Bean 提供了默认的构造函数,则采用 byType;否则采用 constructor 进行自动装配。

当使用 byType 或者 constructor 类型的自动装配的时候,自动装配也支持引用类型的数组或者使用了泛型的集合,这样,Spring 就会检查容器中所有类型匹配的 Bean,组成集合或者数组后执行注入。对于使用了泛型的 Map 类型,如果键是 String 类型,则 Spring 也会自动执行装配,将所有类型匹配的 Bean 作为值,Bean 的名字作为键。

我们可以给 增加 default-autowire 属性,设置默认的自动封装策略。默认值为”no”。如果使用自动装配的同时,也指定了 property 或者 constructor-arg 标签,则显式指定的值将覆盖自动装配的值。目前的自动封装不支持简单类型,比如基本类型、String、Class,以及它们的数组类型。

在按类型匹配的时候 ( 可能是 byType、constructor、autodetect),同一个类型可能存在多个 Bean,如果被注入的属性是数组、集合或者 Map,这可能没有问题,但是如果只是简单的引用类型,则会抛出异常。解决方法有如下几种:

  • 取消该 Bean 的自动装配特性,使用显式的注入。我们可能不希望某个 Bean 被当作其他 Bean 执行自动封装时的候选对象,我们可以给该 增加 autowire-candidate=”false”。(autowire-candidate 属性和 autowire 属性相互独立,互不相干。某个 Bean 可以将 autowire-candidate 设置为 false,同时使用 autowire 特性。) 另外,我们可以设置 的 default-autowire-candidates 属性,可以在该属性中指定可以用于自动装配候选 Bean 的匹配模式,比如 default-autowire-candidates=”*serv,*dao”,这表示所有名字以 serv 或者 dao 结尾的 Bean 被列为候选,其他则忽略,相当于其他 Bean 都指定为 autowire-candidate=”false”,此时可以显式为 指定 autowire-candidate=”true”。在 上指定的设置要覆盖 上指定的设置。
  • 如果在多个类型相同的 Bean 中有首选的 Bean,那么可以将该 的 primary 属性设置为 “true” ,这样自动装配时便优先使用该 Bean 进行装配。此时不能将 autowire-candidate 设为 false。
  • 如果使用的是 Java 5 以上版本,可以使用注解进行更细粒度的控制。

使用 @Autowired 和 @Qualifier 注解执行自动装配

使用 @Autowired 注解进行装配,只能是根据类型进行匹配。@Autowired 注解可以用于 Setter 方法、构造函数、字段,甚至普通方法,前提是方法必须有至少一个参数。@Autowired 可以用于数组和使用泛型的集合类型。然后 Spring 会将容器中所有类型符合的 Bean 注入进来。@Autowired 标注作用于 Map 类型时,如果 Map 的 key 为 String 类型,则 Spring 会将容器中所有类型符合 Map 的 value 对应的类型的 Bean 增加进来,用 Bean 的 id 或 name 作为 Map 的 key。

@Autowired 标注作用于普通方法时,会产生一个副作用,就是在容器初始化该 Bean 实例的时候就会调用该方法。当然,前提是执行了自动装配,对于不满足装配条件的情况,该方法也不会被执行。

当标注了 @Autowired 后,自动注入不能满足,则会抛出异常。我们可以给 @Autowired 标注增加一个 required=false 属性,以改变这个行为。另外,每一个类中只能有一个构造函数的 @Autowired.required() 属性为 true。否则就出问题了。如果用 @Autowired 同时标注了多个构造函数,那么,Spring 将采用贪心算法匹配构造函数 ( 构造函数最长 )。

@Autowired 还有一个作用就是,如果将其标注在 BeanFactory 类型、ApplicationContext 类型、ResourceLoader 类型、ApplicationEventPublisher 类型、MessageSource 类型上,那么 Spring 会自动注入这些实现类的实例,不需要额外的操作。

当容器中存在多个 Bean 的类型与需要注入的相同时,注入将不能执行,我们可以给 @Autowired 增加一个候选值,做法是在 @Autowired 后面增加一个 @Qualifier 标注,提供一个 String 类型的值作为候选的 Bean 的名字。举例如下:

 @Autowired(required=false) 
 @Qualifier("ppp") 
 public void setPerson(person p){}

@Qualifier 甚至可以作用于方法的参数 ( 对于方法只有一个参数的情况,我们可以将 @Qualifer 标注放置在方法声明上面,但是推荐放置在参数前面 ),举例如下:

@Autowired(required=false) 
 public void sayHello(@Qualifier("ppp")Person p,String name){}

我们可以在配置文件中指定某个 Bean 的 qualifier 名字,方法如下

 <bean id="person" class="footmark.spring.Person"> 
    <qualifier value="ppp"/> 
  </bean>

如果没有明确指定 Bean 的 qualifier 名字,那么默认名字就是 Bean 的名字。通常,qualifier 应该是有业务含义的,例如 “domain”,”persistent” 等,而不应该是类似 “person” 方式。

我们还可以将 @Qualifier 标注在集合类型上,那么所有 qualifier 名字与指定值相同的 Bean 都将被注入进来。

最后,配置文件中需要指定每一个自定义注解的属性值。我们可以使用 标签来代替 标签,如果 标签和 标签同时出现,那么优先使用 标签。如果没有 标签,那么会用 提供的键值对来封装 标签。示例如下:

<bean class="footmark.HelloWorld"> 
     <qualifier type="MovieQualifier"> 
     <attribute key="format" value="VHS"/> 
     <attribute key="genre" value="Comedy"/> 
     </qualifier> 
 </bean> 

 <bean class="footmark.HelloWorld"> 
     <meta key="format" value="DVD"/> 
     <meta key="genre" value="Action"/> 
 </bean>

@Autowired 注解对应的后处理注册与前面相似,只需在配置文件中增加如下一行即可:

 <context:annotation-config/>

如果 @Autowired 注入的是 BeanFactory、ApplicationContext、ResourceLoader 等系统类型,那么则不需要 @Qualifier,此时即使提供了 @Qualifier 注解,也将会被忽略;而对于自定义类型的自动装配,如果使用了 @Qualifier 注解并且没有名字与之匹配的 Bean,则自动装配匹配失败。

使用 JSR-250 中的 @Resource 和 @Qualifier 注解

如果希望根据 name 执行自动装配,那么应该使用 JSR-250 提供的 @Resource 注解,而不应该使用 @Autowired 与 @Qualifier 的组合。

@Resource 使用 byName 的方式执行自动封装。@Resource 标注可以作用于带一个参数的 Setter 方法、字段,以及带一个参数的普通方法上。@Resource 注解有一个 name 属性,用于指定 Bean 在配置文件中对应的名字。如果没有指定 name 属性,那么默认值就是字段或者属性的名字。@Resource 和 @Qualifier 的配合虽然仍然成立,但是 @Qualifier 对于 @Resource 而言,几乎与 name 属性等效。

如果 @Resource 没有指定 name 属性,那么使用 byName 匹配失败后,会退而使用 byType 继续匹配,如果再失败,则抛出异常。在没有为 @Resource 注解显式指定 name 属性的前提下,如果将其标注在 BeanFactory 类型、ApplicationContext 类型、ResourceLoader 类型、ApplicationEventPublisher 类型、MessageSource 类型上,那么 Spring 会自动注入这些实现类的实例,不需要额外的操作。此时 name 属性不需要指定 ( 或者指定为””),否则注入失败;如果使用了 @Qualifier,则该注解将被忽略。而对于用户自定义类型的注入,@Qualifier 和 name 等价,并且不被忽略。

的 primary 和 autowire-candidate 属性对 @Resource、@Autowired 仍然有效。


使用 @Configuration 和 @Bean 进行 Bean 的声明

虽然 2.0 版本发布以来,Spring 陆续提供了十多个注解,但是提供的这些注解只是为了在某些情况下简化 XML 的配置,并非要取代 XML 配置方式。这一点可以从 Spring IoC 容器的初始化类可以看出:ApplicationContext 接口的最常用的实现类是 ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext,以及面向 Portlet 的 XmlPortletApplicationContext 和面向 web 的 XmlWebApplicationContext,它们都是面向 XML 的。Spring 3.0 新增了另外两个实现类:AnnotationConfigApplicationContext 和 AnnotationConfigWebApplicationContext。从名字便可以看出,它们是为注解而生,直接依赖于注解作为容器配置信息来源的 IoC 容器初始化类。由于 AnnotationConfigWebApplicationContext 是 AnnotationConfigApplicationContext 的 web 版本,其用法与后者相比几乎没有什么差别,因此本文将以 AnnotationConfigApplicationContext 为例进行讲解。

AnnotationConfigApplicationContext 搭配上 @Configuration 和 @Bean 注解,自此,XML 配置方式不再是 Spring IoC 容器的唯一配置方式。两者在一定范围内存在着竞争的关系,但是它们在大多数情况下还是相互协作的关系,两者的结合使得 Spring IoC 容器的配置更简单,更强大。

前,我们将配置信息集中写在 XML 中,如今使用注解,配置信息的载体由 XML 文件转移到了 Java 类中。我们通常将用于存放配置信息的类的类名以 “Config” 结尾,比如 AppDaoConfig.java、AppServiceConfig.java 等等。我们需要在用于指定配置信息的类上加上 @Configuration 注解,以明确指出该类是 Bean 配置的信息源。并且 Spring 对标注 Configuration 的类有如下要求:

  • 配置类不能是 final 的;
  • 配置类不能是本地化的,亦即不能将配置类定义在其他类的方法内部;
  • 配置类必须有一个无参构造函数。

AnnotationConfigApplicationContext 将配置类中标注了 @Bean 的方法的返回值识别为 Spring Bean,并注册到容器中,受 IoC 容器管理。@Bean 的作用等价于 XML 配置中的 标签。示例如下:

@Configuration 
 public class BookStoreDaoConfig{ 
    @Bean 
    public UserDao userDao(){ return new UserDaoImpl();} 
    @Bean 
    public BookDao bookDao(){return new BookDaoImpl();} 
 }

Spring 在解析到以上文件时,将识别出标注 @Bean 的所有方法,执行之,并将方法的返回值 ( 这里是 UserDaoImpl 和 BookDaoImpl 对象 ) 注册到 IoC 容器中。默认情况下,Bean 的名字即为方法名。因此,与以上配置等价的 XML 配置如下:

<bean id=”userDao” class=”bookstore.dao.UserDaoImpl”/> 
 <bean id=”bookDao” class=”bookstore.dao.BookDaoImpl”/>

@Bean 具有以下四个属性:

  • name – 指定一个或者多个 Bean 的名字。这等价于 XML 配置中 的 name 属性。
  • initMethod – 容器在初始化完 Bean 之后,会调用该属性指定的方法。这等价于 XML 配置中 的 init-method 属性。
  • destroyMethod – 该属性与 initMethod 功能相似,在容器销毁 Bean 之前,会调用该属性指定的方法。这等价于 XML 配置中 的 destroy-method 属性。
  • autowire – 指定 Bean 属性的自动装配策略,取值是 Autowire 类型的三个静态属性。Autowire.BY_NAME,Autowire.BY_TYPE,Autowire.NO。与 XML 配置中的 autowire 属性的取值相比,这里少了 constructor,这是因为 constructor 在这里已经没有意义了。

@Bean 没有直接提供指定作用域的属性,可以通过 @Scope 来实现该功能,关于 @Scope 的用法已在上文列举。
下面讲解基于注解的容器初始化。AnnotationConfigApplicationContext 提供了三个构造函数用于初始化容器。

  • AnnotationConfigApplicationContext():该构造函数初始化一个空容器,容器不包含任何 Bean 信息,需要在稍后通过调用其 register() 方法注册配置类,并调用 refresh() 方法刷新容器。
  • AnnotationConfigApplicationContext(Class<?>… annotatedClasses):这是最常用的构造函数,通过将涉及到的配置类传递给该构造函数,以实现将相应配置类中的 Bean 自动注册到容器中。
  • AnnotationConfigApplicationContext(String… basePackages):该构造函数会自动扫描以给定的包及其子包下的所有类,并自动识别所有的 Spring Bean,将其注册到容器中。它不但识别标注 @Configuration 的配置类并正确解析,而且同样能识别使用 @Repository、@Service、@Controller、@Component 标注的类。

除了使用上面第三种类型的构造函数让容器自动扫描 Bean 的配置信息以外,AnnotationConfigApplicationContext 还提供了 scan() 方法,其功能与上面也类似,该方法主要用在容器初始化之后动态增加 Bean 至容器中。调用了该方法以后,通常需要立即手动调用 refresh() 刷新容器,以让变更立即生效。

需要注意的是,AnnotationConfigApplicationContext 在解析配置类时,会将配置类自身注册为一个 Bean,因为 @Configuration 注解本身定义时被 @Component 标注了。因此可以说,一个 @Configuration 同时也是一个 @Component。大多数情况下,开发者用不到该 Bean,并且在理想情况下,该 Bean 应该是对开发者透明的。@Configuration 的定义如下所示:

 @Target({ElementType.TYPE}) 
 @Retention(RetentionPolicy.RUNTIME) 
 @Documented 
 @Component 
 public @interface Configuration { 
 	String value() default ""; 
 }

在一般的项目中,为了结构清晰,通常会根据软件的模块或者结构定义多个 XML 配置文件,然后再定义一个入口的配置文件,该文件使用 将其他的配置文件组织起来。最后只需将该文件传给 ClassPathXmlApplicationContext 的构造函数即可。针对基于注解的配置,Spring 也提供了类似的功能,只需定义一个入口配置类,并在该类上使用 @Import 注解引入其他的配置类即可,最后只需要将该入口类传递给 AnnotationConfigApplicationContext。具体示例如下:

@Configuration 
 @Import({BookStoreServiceConfig.class,BookStoreDaoConfig.class}) 
 public class BookStoreConfig{ … }

混合使用 XML 与注解进行 Bean 的配置

设计 @Configuration 和 @Bean 的初衷,并不是为了完全取代 XML,而是为了在 XML 之外多一种可行的选择。由于 Spring 自发布以来,Spring 开发小组便不断简化 XML 配置,使得 XML 配置方式已经非常成熟,加上 Spring 2.0 以后出现了一系列命名空间的支持,使得 XML 配置方式成为了使用简单、功能强大的 Bean 定义方式。而且,XML 配置的一些高级功能目前还没有相关注解能够直接支持。因此,在目前的多数项目中,要么使用纯粹的 XML 配置方式进行 Bean 的配置,要么使用以注解为主,XML 为辅的配置方式进行 Bean 的配置。

之所以会出现两者共存的情况,主要归结为三个原因:其一,目前绝大多数采用 Spring 进行开发的项目,几乎都是基于 XML 配置方式的,Spring 在引入注解的同时,必须保证注解能够与 XML 和谐共存,这是前提;其二,由于注解引入较晚,因此功能也没有发展多年的 XML 强大,因此,对于复杂的配置,注解还很难独当一面,在一段时间内仍然需要 XML 的配合才能解决问题。除此之外,Spring 的 Bean 的配置方式与 Spring 核心模块之间是解耦的,因此,改变配置方式对 Spring 的框架自身是透明的。Spring 可以通过使用 Bean 后处理器 (BeanPostProcessor) 非常方便的增加对于注解的支持。这在技术实现上非常容易的事情。

要使用混合配置方式,首先需要判断以哪一种配置方式为主。对这个问题的不同回答将会直接影响到实现的方式。然而大可不必为此伤脑筋,因为不论是以 XML 为主,还是以注解为主,配置方式都是简单而且容易理解的。这里不存在错误的决定,因为仅仅是表现方式不一样。我们首先假设以 XML 配置为主的情况。

对于已经存在的大型项目,可能初期是以 XML 进行 Bean 配置的,后续逐渐加入了注解的支持,这时我们只需在 XML 配置文件中将被 @Configuration 标注的类定义为普通的 ,同时注册处理注解的 Bean 后处理器即可。示例如下:

// 假设存在如下的 @Configuration 类:
 package bookstore.config; 
 import bookstore.dao.*; 
 @Configuration 
 public class MyConfig{ 
 	@Bean 
    public UserDao userDao(){ 
        return new UserDaoImpl(); 
    } 
 } 
此时,只需在 XML 中作如下声明即可:
 <beans … > 
……
<context:annotation-config /> 
<bean class=”demo.config.MyConfig”/> 
 </beans>

于启用了针对注解的 Bean 后处理器,因此在 ApplicationContext 解析到 MyConfig 类时,会发现该类标注了 @Configuration 注解,随后便会处理该类中标注 @Bean 的方法,将这些方法的返回值注册为容器总的 Bean。

对于以上的方式,如果存在多个标注了 @Configuration 的类,则需要在 XML 文件中逐一列出。另一种方式是使用前面提到的自动扫描功能,配置如下:

<context:component-scan base-package=”bookstore.config” />

如此,Spring 将扫描所有 demo.config 包及其子包中的类,识别所有标记了 @Component、@Controller、@Service、@Repository 注解的类,由于 @Configuration 注解本身也用 @Component 标注了,Spring 将能够识别出 @Configuration 标注类并正确解析之。

对于以注解为中心的配置方式,只需使用 @ImportResource 注解引入存在的 XML 即可,如下所示:

@Configuration 
 @ImportResource(“classpath:/bookstore/config/spring-beans.xml”) 
 public class MyConfig{ 
……
 } 
 // 容器的初始化过程和纯粹的以配置为中心的方式一致:
 AnnotationConfigApplicationContext ctx = 
              new AnnotationConfigApplicationContext(MyConfig.class); 
……

结束语

从 2.0 版本开始,Spring 的每一次更新都会提供更多新的注解供开发者使用。这满足了注解爱好者的胃口。但是正如前面所说,Spring 提供更多的注解并不是为了有朝一日取代 XML 配置方式,而是为了给开发者多一种选择。两种声明 Bean 的方式各有特色,XML 方式更加灵活,并且发展的相对成熟,这种配置方式为大多数 Spring 开发者熟悉;注解方式使用起来非常简洁,但是尚处于发展阶段。我们很难说两种配置方式孰优孰劣,但是如果能够灵活搭配两种方式,一定能够进一步提升开发效率。

spring四种注入

平常的java开发中,程序员在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理,spring提出了依赖注入的思想,即依赖类不由程序员实例化,而是通过spring容器帮我们new指定实例并且将实例注入到需要该对象的类中。依赖注入的另一种说法是“控制反转”,通俗的理解是:平常我们new一个实例,这个实例的控制权是我们程序员,而控制反转是指new实例工作不由我们程序员来做而是交给spring容器来做。

spring有多种依赖注入的形式,下面仅介绍spring通过xml进行IOC配置的方式:

Set注入

这是最简单的注入方式,假设有一个SpringAction,类中需要实例化一个SpringDao对象,那么就可以定义一个private的SpringDao成员变量,然后创建SpringDao的set方法(这是ioc的注入入口):

java代码 :

 //注入对象springDao  
private SpringDao springDao;  
    //一定要写被注入对象的set方法  
    public void setSpringDao(SpringDao springDao) {  
    this.springDao = springDao;  
}  

    public void ok(){  
    springDao.ok();  
}  

随后编写spring的xml文件,中的name属性是class属性的一个别名,class属性指类的全名,因为在SpringAction中有一个公共属性Springdao,所以要在标签中创建一个标签指定SpringDao。**标签中的name就是SpringAction类中的SpringDao属性名,ref指下面<bean name=”springDao”…>**,这样其实是spring将SpringDaoImpl对象实例化并且调用SpringAction的setSpringDao方法将SpringDao注入:

xml代码:

<!--配置bean,配置后该类由spring管理-->  
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">  
    <!--(1)依赖注入,配置当前类中相应的属性-->  
    <property name="springDao" ref="springDao"></property>  
</bean>  
<bean name="springDao" class="com.bless.springdemo.dao.impl.SpringDaoImpl"></bean>  

构造器注入

这种方式的注入是指带有参数的构造函数注入,看下面的例子,我创建了两个成员变量SpringDao和User,但是并未设置对象的set方法,所以就不能支持第一种注入方式,这里的注入方式是在SpringAction的构造函数中注入,也就是说在创建SpringAction对象时要将SpringDao和User两个参数值传进来:

java代码 :

public class SpringAction {  
//注入对象springDao  
private SpringDao springDao;  
private User user;  
  
public SpringAction(SpringDao springDao,User user){  
    this.springDao = springDao;  
    this.user = user;  
    System.out.println("构造方法调用springDao和user");  
}  
      
    public void save(){  
    user.setName("卡卡");  
    springDao.save(user);  
}  
}  

在XML文件中同样不用的形式,而是使用标签,ref属性同样指向其它标签的name属性:

xml代码:

<!--配置bean,配置后该类由spring管理-->  
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">  
    <!--(2)创建构造器注入,如果主类有带参的构造方法则需添加此配置-->  
    <constructor-arg ref="springDao"></constructor-arg>  
    <constructor-arg ref="user"></constructor-arg>  
</bean>  
    <bean name="springDao" class="com.bless.springdemo.dao.impl.SpringDaoImpl"></bean>  
    <bean name="user" class="com.bless.springdemo.vo.User"></bean> 

解决构造方法参数的不确定性,你可能会遇到构造方法传入的两参数都是同类型的,为了分清哪个该赋对应值,则需要进行一些小处理:
下面是设置index,就是参数位置:

xml代码:

<bean name="springAction" class="com.bless.springdemo.action.SpringAction">  
    <constructor-arg index="0" ref="springDao"></constructor-arg>  
    <constructor-arg index="1" ref="user"></constructor-arg>  
</bean>  

另一种是设置参数类型:

xml代码 :

<constructor-arg type="java.lang.String" ref=""/>  

静态工厂的方法注入

静态工厂顾名思义,就是通过调用静态工厂的方法来获取自己需要的对象,为了让spring管理所有对象,我们不能直接通过”工程类.静态方法()”来获取对象,而是依然通过spring注入的形式获取:

public class DaoFactory {
    //静态工厂  
    public static final FactoryDao getStaticFactoryDaoImpl(){
        return new StaticFactoryDaoImpl();
    }
}

public class StaticFactoryDaoImpl extends FactoryDao {
}

public class SpringAction {
//注入对象  
private FactoryDao staticFactoryDao;  
//注入对象的set方法  
public void setStaticFactoryDao(FactoryDao staticFactoryDao) {  
    this.staticFactoryDao = staticFactoryDao;  
}  
public void method(){
    staticFactoryDao.saveFactory();
}
}

Spring的IOC配置文件,注意看指向的class并不是FactoryDao的实现类,而是指向静态工厂DaoFactory,并且配置 factory-method=”getStaticFactoryDaoImpl”指定调用哪个工厂方法:

<!--配置bean,配置后该类由spring管理-->  
<bean name="springAction" class="com.dream.springtest.SpringAction">
    <!--(3)使用静态工厂的方法注入对象,对应下面的配置文件(3)-->  
    <property name="staticFactoryDao" ref="staticFactoryDao"></property>
</bean>

<!--(3)此处获取对象的方式是从工厂类中获取静态方法-->  
<bean name="staticFactoryDao" class="com.dream.springtest.DaoFactory" factory-method="getStaticFactoryDaoImpl"></bean>


@Test
public void test() {//测试方法
    ApplicationContext application =new ClassPathXmlApplicationContext("application.xml");
    SpringAction bean = application.getBean(SpringAction.class);
    bean.method();
}

实例工厂的方法注入

实例工厂的意思是获取对象实例的方法不是静态的,所以你需要首先new工厂类,再调用普通的实例方法

public class DaoFactory {
//实例工厂  
       public FactoryDao getFactoryDaoImpl(){  
        return new FactoryDaoImpl();  
    }  
}


public class SpringAction {

    //注入对象  
    private FactoryDao factoryDao;  
    //注入对象的set方法  
    public void setFactoryDao(FactoryDao factoryDao) {  
        this.factoryDao = factoryDao;  
    }  
    public void method(){
    	factoryDao.saveFactory();
    }
}

//最后配置文件
<!--配置bean,配置后该类由spring管理-->  
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">  
    <!--(4)使用实例工厂的方法注入对象,对应下面的配置文件(4)-->  
    <property name="factoryDao" ref="factoryDao"></property>  
</bean>  
  
<!--(4)此处获取对象的方式是从工厂类中获取实例方法-->  
<bean name="daoFactory" class="com.bless.springdemo.factory.DaoFactory"></bean>  
<bean name="factoryDao" factory-bean="daoFactory" factory-method="getFactoryDaoImpl"></bean>  

总结

Spring IOC注入方式用得最多的是(1)(2)种,多谢多练就会非常熟练。
另外注意:通过Spring创建的对象默认是单例的,如果需要创建多实例对象可以在标签后面添加一个属性:

<bean name="..." class="..." scope="prototype">