--------------------------Spring 2.5 理论介绍 --------------------------
Spring 是一个开源的控制反转 (Inversion of Control,IOC) 和面向切面 (AOP) 的容器框架,它的主要目的是简化企业开发
1. IOC 控制反转
先看一段代码:
public class PersonServiceBean{
private PersonDao personDao=new PersonDaoBean();
public void save(Person person){
personDao.save(person);
}
}
在这段代码中, PersonDaoBean 类的对象是在 PersonServiceBean 类中创建的,所以 PersonDaoBean 类对象的创建依赖于 PersonServiceBean
2. 依赖注入 (Dependency Injection)
当我们把依赖对象交给外部容器负责创建,那么 PersonServiceBean 类可以改成:
public class PersonServiceBean{
private PersonDao personDao;
public PersonServiceBean(PersonDao personDao){
this.personDao=personDao;
// 通过构造器参数,让容器把创建好的依赖对象注入进 PersonServiceBean ,当然也可以使用 setter 方法进行注入
}
public void save(Person person){
personDao.save(person);
}
}
所谓依赖注入就是指:在运行期,由外部容器动态地将依赖对象注入到组件中
3. 为什么要使用 Spring 呢
在项目中引入 Spring 立即可以带来下面的一些好处:
(1) 降低组件之间的耦合度,实现软件各层之间的解耦
Controller—-Service—-DAO
(2) 可以使用容器提供的众多服务,例如:事务管理服务,消息服务等等,当我们使用容器管理事务时,开发人员就不再需要手工控制事务,也不需要处理复杂的事务传播
(3) 容器提供单例模式支持,开发人员不再需要自己编写实现代码
(4) 容器提供了 AOP 技术,利用它很容易实现如权限拦截、运行期监控等功能
(5) 容器提供的众多辅佐类,使用这些类能够加快应用的开发,如: JdbcTemplate 、 HibernateTemplate
(6) Spring 对于主流的应用框架提供了集成支持,如:集成 Hibernate , JPA , Struts 等,这样更便于应用的开发
4. 轻量级与重量级概念的划分
划分一个应用是否属于轻量级还是重量级,主要看它使用了多少服务,使用的服务越多,容器要为普通 Java 对象做的工作就越多,必然会影响到应用的发布时间或者是运行性能
对于 Spirng 容器,它提供了很多服务,但这些服务并不是预设为应用打开的,应用需要某种服务,就需要指明使用该服务。如果应用使用的服务很少,比如只使用了 Spring 的核心服务,那么我们可以认为此时应用属于轻量级的。如果应用使用了 Spring 提供的大部分功能,,这时应用就属于重量级。
目前 EJB 容器就因为它默认为应用提供了 EJB 规范中的所有功能,所以它属于重量级
--------------------- 搭建与测试 Spring 2.5 的开发环境 ---------------------
下载 Spring 的 jar 包,从 Spring 的官方网站下载
http://www.springsource.org/download 可以找到 Spring 的不同版本
1. Spring 经常使用到的 jar 檔:
dist/spring.jar
如果使用日志,需要下面这个 jar 包
lib/jakarta-commons/commons-logging.jar
如果使用 AOP ,需要下面 3 个 jar 檔
lib/aspect/aspectjweaver.jar 和 aspectjrt.jar
lib/cglib/cglib-nodep-2.1_3.jar
如果使用 JSR-250 中的注解,例如: @Resource/@PostConstruct/@PreDestroy ,还需要下列 jar 檔
lib/j2ee/common-annotations.jar
如果使用 Spring 内部的 Junit 测试,还需要下面 4 个 jar 檔
dist/modules/spring-test.jar
lib/junit/junit-4.4.jar
lib/easymock/easymock.jar 和 easymockclassextension.jar
2. 搭建环境
(1) 导入 jar 包
(2) 创建 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-2.5.xsd >
</beans>
3. 创建一个需要使用 Spring 容器功能的接口及实现类
先创建一个 Person 类,并含有一个 Hello 方法
public class Person{
public void Notify (){
System. out .println( "Hello World" );
}
}
对该类点击右键,选择 Refactor—Extract Interface 提取该类成一个接口
界面如下:
public interface PersonService {
public abstract void Notify();
}
4. 修改配置文件 Bean.xml 实例化一个 bean
在配置文件中的 <beans> 中添加
< bean id = "personService" class = "com.bean.Person" ></ bean >
5. 测试
创建一个 java 类进行测试
public class Test {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext( "Bean.xml" );
PersonService person=(PersonService) ctx.getBean( "personService" );
person.Notify();
}
}
如果成功会打印出来: Hello Spring
-------------------- 利用 MyEclipse 加载配置文件的说明信息 -----------------
由于 spring 的 schema 文件位于网络上,如果机器不能连接到网络,那么在编写配置信息时候就无法出现提示信息,解决方法有两种:
1. 让机器上网, MyEclipse 会自动从网络上下载 schema 文件并缓存在硬盘上。
2. 手动添加 schema 檔 , 方法如下:
以 ” spring-beans-2.5.xsd” 为例子
windwos->preferences->myeclipse->files and editors->xml->xmlcatalog
点 "add", 在出现的窗口中的 Key Type 中选择 URI, 在 location 中选 "File system", 然后在 spring 解压目录的 dist/resources 目录中选择 spring-beans-2.5.xsd, 回到设置窗口的时候不要急着关闭窗口 , 应把窗口中的 Key Type 改为 Schema location,Key 改为 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
Spring 所包含的 xsd 不止这一个,还有 spring-aop-2.5.xsd 等,所以当遇到非语法的配置文件报错时,就很有可能是没有配置其中所用到的 xsd 檔
-------------------------- 实例化 Spring 容器 ---------------------------
1. 实例化 Spring 容器有两种方式:
方法一:
在类路径下寻找配置文件来实例化容器
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"beans.xml"});
方法二 :
在文件系统路径下寻找配置文件来实例化容器
ApplicationContext ctx = new FileSystemXmlApplicationContext(new String[]{“d://beans.xml“});
该方式不太推荐使用,因为每个操作系统的文件系统都是不同的,所以他的通用性不强,例如这个路径 “d://beans.xml“ 是在 window 下的,而在 linux 就可能不是这个路径了
2. Spring 的配置文件可以指定多个,可以通过 String 数组传入。
3. 当 spring 容器启动后,因为 spring 容器可以管理 bean 对象的创建,销毁等生命周期,所以我们只需从容器直接获取 Bean 对象就行,而不用编写一句代码来创建 bean 对象。从容器获取 bean 对象的代码如下:
OrderService service = (OrderService)ctx.getBean("personService");
这里的 OrderService 表示一个界面
personService 表示获取配置文件中 <bean> 的 id 属性所对应的类的实例
-------------------------Spring 管理 Bean 的原理 -------------------------
利用 Java 读取 XML 文档的技术来模拟实现一个容器,从而利用该容器获取配置文件中 bean 的实例
需要导入 dom4j 及相关一整套的 jar 包
1. 先创建一个 JavaBean ,用来保存配置文件中 <bean> 的 id 和 class
public class BeanDefinition {
private String id ;
private String className ;
public BeanDefinition(String id, String className) {
this . id = id;
this . className = className;
}
public String getId() {
return id ;
}
public void setId(String id) {
this . id = id;
}
public String getClassName () {
return className ;
}
public void setClassName(String className) {
this . className = className;
}
}
2. 通过 spring 提供的 jar 中的某些特殊类的功能,例如: SAXReader 、 Document 和 XPath 等,来模拟实现
public class ItcastClassPathXMLApplicationContext {
private List<BeanDefinition> beanDefines = new ArrayList<BeanDefinition>();
private Map<String, Object> sigletons = new HashMap<String, Object>();
public ItcastClassPathXMLApplicationContext(String filename){
this .readXML(filename);
this .instanceBeans();
}
/**
* 读取 xml 配置文件
* @param filename
*/
private void readXML(String filename) {
SAXReader saxReader = new SAXReader();
Document document= null ;
try {
URL xmlpath = this .getClass().getClassLoader().getResource(filename);
document = saxReader.read(xmlpath);
Map<String,String> nsMap = new HashMap<String,String>();
nsMap.put( "ns" , "http://www.springframework.org/schema/beans" ); // 加入命名空间
XPath xsub = document.createXPath( "//ns:beans/ns:bean" ); // 创建 beans/bean 查询路径
xsub.setNamespaceURIs(nsMap); // 设置命名空间
List<Element> beans = xsub.selectNodes(document); // 获取文档下所有 bean 节点
for (Element element: beans){
String id = element.attributeValue( "id" ); // 获取 id 属性值
String clazz = element.attributeValue( "class" ); // 获取 class 属性值
BeanDefinition beanDefine = new BeanDefinition(id, clazz);
beanDefines .add(beanDefine);
}
} catch (Exception e){
e.printStackTrace();
}
}
/**
* 完成 bean 的实例化
*/
private void instanceBeans() {
for (BeanDefinition beanDefinition : beanDefines ){
try {
if (beanDefinition.getClassName()!= null && ! "" .equals(beanDefinition.getClassName().trim()))
sigletons .put(beanDefinition.getId(), Class.forName (beanDefinition.getClassName()).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 获取 bean 实例
* @param beanName
* @return
*/
public Object getBean(String beanName){
return this . sigletons .get(beanName);
}
}
3. 最后进行测试
public static void main(String[] args) {
ItcastClassPathXMLApplicationContext ctx = new ItcastClassPathXMLApplicationContext ( "Bean.xml" );
PersonService person=(PersonService) ctx.getBean( "personService" );
person.Notify();
}
------------------------- 三种实例化 bean 的方式 --------------------------
1. 使用类构造器实例化
< bean
id = "personService" class = "com.bean.service.impl.Person" >
</ bean >
这个是使用类构造器来实例化,也是我们最初见到的最简单的一种单实例,无论这个 personService 构造多少个,实质上也只有一个实例而已
这种方式实例化的 bean ,是一种
2. 使用静态工厂方法实例化
public class PersonStaticFactory {
public static Person staticCreatePerson(){
return new Person();
}
}
工厂类里边提供一个静态方法,专门用于创建 bean 对象
修改配置文件,实例化该 bean
< bean
id = "personService2" class = "com.bean.service.impl.PersonStaticFactory" factory-method = "staticCreatePerson" />
这种配置方法表示利用 PersonStaticFactory 的 createPerson 方法来实例化
测试:
PersonService person2=(PersonService) ctx.getBean( "personService2" );
System . out .print( " 使用静态工厂方法实例化 :" );
person.Notify();
3. 使用实例工厂方法实例化 :
public class PersonStaticFactory {
public Person createPerson(){
return new Person ();
}
}
修改配置文件,实例化该 bean
< bean
id = "personFactory" class = "com.bean.service.impl.PersonStaticFactory" />
< bean
id = "personService3"
factory-bean = "personFactory"
factory-method = "createPerson" />
先创建工厂类的一个实例 bean--"personFactory"
再使用这个工厂 bean—"personFactory" 来调用 "createPerson" 方法创建 Person 类 bean 的实例
测试:
PersonService person3=(PersonService) ctx.getBean( "personService3" );
System. out .print( " 使用实例工厂方法实例化 :" );
person.Notify();
-----------------------------bean 的作用域 -----------------------------
在每个 spring IoC 容器中默认一个 bean 定义只有一个对象实例。我们可以设置 scope 属性的属性范围让一个 bean 初始化多个对象实例
< bean
id = "personService"
class = "com.bean.service.impl.Person"
scope = "prototype"/ >
这样每次从容器获取 bean 都是新的对象
在 scope 中还有其他参数:
Singleton :唯一值,在 IOC 的容器中,只能出现一个对象,类似类中的静态方法,当 spring 的容器加载的时候,自动对 singleton 类型的的 bean 进行实例化
Prototype :每次产生一个新对象
Request :为每一个 http 的请求创建一个全新的 bean
Session : bean 对象放置在 session 会话中,不同 HTTPSession 使用不同的 Bean 。该作用域仅适用于 WebApplicationContext 环境。
globalSession :同一个全局 Session 共享一个 Bean ,一般用于 Protlet 应用环境。该作用域仅适用于 WebApplicationContext 环境。
------------------------- bean 的生命周期 -------------------------
1. 预设情况下 Spring 会在容器启动时初始化 bean 的实例。
我们可以指定 Bean 节点的 lazy-init=”true” 属性来延迟初始化 bean 。
这时候,只有第一次获取 bean 才会初始化 bean
例如:
< bean
id = "personService"
class = "com.bean.service.impl.Person"
lazy-init = "true"/ >
当 lazy-init="false" 时,表示在容器启动时对 bean 进行实例化
当 lazy-init="true" 时 , 表示进行延迟初始化,在 ctx.getBean 时对 bean 进行实例化
如果想对所有 bean 都应用延迟初始化,可以在根节点 bean 设置
default-lazy-init=”true” ,例如:
< beans default-lazy-init = "true"
</ beans >
2. 在设置 scope="prototype" 属性时,是在 getBean("personService") 时才初始化 bean 的实例,而不设置时,就在容器启动时对 bean 进行实例化
3. 初始化方法
在 bean 添加一个 init 方法
public void init(){
System.out.println("init 方法被调用 ");
}
在 <bean> 中设置 init-method ="init" 属性,再实例化 bean 后,会启动该初始化方法
为了更好地看到效果,添加 Person 类的构造方法,告诉我们 bean 何时被实例化
public Person(){
System.out.println("bean 已被实例化 ");
}
当执行下列这段代码时
AbstractApplicationContext ctx
= new ClassPathXmlApplicationContext("Bean.xml");
读取 xml 配置文件,初始化 Spring 容器
先构造 bean, 再调用 init 方法
4. 销毁方法
在 bean 添加一个 destory 方法
public void destory(){
System. out .println( " 关闭 Spring 容器, destory 方法被调用 " );
}
在 <bean> 中设置 destroy-method = "destory" 属性,在关闭 Spring 容器前会启动该销毁方法
那么又如何来关闭 Spring 容器呢 ?
首先, Spring 容器读取 xml 配置文件不能使用 ApplicationContext ,而要使用抽象类 AbstractApplicationContext
AbstractApplicationContext ctx
= new ClassPathXmlApplicationContext( "Bean.xml" );
ctx.close();
AbstractApplicationContext 也是被 ApplicationContext 所继承的
用 close 方法来正常关闭 Spring 容器
-----------------------Spring 依赖注入之 ref 属性 ------------------------
所谓依赖注入就是指 : 在运行期,由外部容器动态地将依赖对象注入到组件中
这个时候控制权发生了转移,这就是所谓的控制反转
依赖注入一共有两种方法:
一个是使用 property 元素的 ref 属性,这样构造的 bean 可以被任意个其他 bean 使用
另一个是使用内部 bean ,后边将会介绍,但是他只能为一个 bean 所服务
例如:我要在 Person 类中由 Spring 容器在外部创建 PersonDao 类的实例,并注入进来
1. 创建一个 PersonDao 类,添加一个 add 方法
public class PersonDao implements PersonDaoService {
public void add (){
System. out .println( " 这里是 PersonDao 实例调用了 add 方法 " );
}
}
2. 抽取接口 右键单击该类,选择 Refactor—Extract Interface
public interface PersonService {
public abstract void Notify();
}
3. 在 Person 类中添加一个 PersonDao 类的对象引用 ( 属性 ) ,设置该对象的 set 方法,在 notify 方法中调用 PersonDao 对象的 add 方法
public class Person implements PersonService {
public PersonDao personDao ;
public void Notify(){
System. out .println( "Hello Spring" );
personDao .add();
}
public void setPersonDao(PersonDao personDao) {
this . personDao = personDao;
}
}
4. 修改 bean.xml 配置文件
< bean id = "personDao" class = "com.bean.service.impl.PersonDao" ></ bean >
< bean id = "personService" class = "com.bean.service.impl.Person" >
< property name = "personDao" ref = "personDao" ></ property >
</ bean >
创建 PersonDao 类的 bean 实例,该类要被注入进 Person 类中
在 personService 实例中添加 <property> 属性,也就是通过 property 属性隐式执行一个 setter 方法
其中:
name 元素表示为 Person 类中的 personDao 属性注入值,且该元素必须与 setXXX 方法的 XXX 部分的名称相同,且首字母小写,与 struts 中的 formbean 利用反射机制获取值的方式基本一致
ref 元素表示要注入的 bean 的名称,也就是要注入的那个 bean 的 id 名称
5. 测试
依然调用 person.Notify() 方法,就会经过注入 bean ,调用 PersonDao 类的 add 方法
------------------------Spring 依赖注入之内部 bean-----------------------
内部 bean 只能被一个 bean 使用,不能被其他 bean 使用
与上个例子的步骤基本相同,只是 xml 配置文件的设置不一样
修改 xml 配置文件
< bean id = "personService" class = "com.bean.service.impl.Person" >
< property name = "personDao" >
< bean class = "com.bean.service.impl.PersonDao" ></ bean >
</ property >
</ bean >
------------------------Spring 依赖注入之基本属性 -----------------------
Spring 容器不仅能注入依赖对象,还可以注入基本类型的属性
例如:我有一个 id 和 name ,要注入进 Person 类
1. 在 Person 类定义 id 和 name 属性,创建他们的 set 方法
public class Person implements PersonService {
public int id ;
public String name ;
public void Notify(){
System. out .println( "Hello Spring" );
System. out .println( "id=" + id + ",name=" + name );
}
public void setId( int id) {
this . id = id;
}
public void setName(String name) {
this . name = name;
}
}
2. 修改 xml 配置文件
< bean id = "personService" class = "com.bean.service.impl.Person" >
< property name = "id" value = "1" ></ property >
< property name = "name" value = "Shiki" ></ property >
</ bean >
3. 测试
依然调用 person.Notify() 方法,就会注入 id 和 name 属性,通过 Notify 方法打印出 id 和 name 的值
----------------------Spring 依赖注入之集合类型属性 ----------------------
1. 定义 4 种集合类型的属性,生成相应的 get/set 方法,将其 get 方法添加进接口中,因为测试时要由接口对象来调用该方法
public class Person implements PersonService {
private Set<Person> set = new HashSet<Person>();
private List<Person> list = new ArrayList<Person>();
private Properties prop = new Properties();
private Map<String,String> map = new HashMap<String,String>();
public Set<Person> getSet() {
return set ;
}
public void setSet(Set<Person> set) {
this . set = set;
}
public List<Person> getList() {
return list ;
}
public void setList(List<Person> list) {
this . list = list;
}
public Properties getProp() {
return prop ;
}
public void setProp(Properties prop) {
this . prop = prop;
}
public Map<String, String> getMap() {
return map ;
}
public void setMap(Map<String, String> map) {
this . map = map;
}
}
2. 抽取界面
public interface PersonService {
public abstract void Notify();
public abstract Set<Person> getSet();
public abstract List<Person> getList();
public abstract Properties getProp();
public abstract Map<String, String> getMap();
}
3. 修改 xml 配置文件
< bean id = "personService" class = "com.bean.service.impl.Person">
< property name = "set" >
< set >
< value > 第一个 </ value >
< value > 第二个 </ value >
< value > 第三个 </ value >
</ set >
//value 卷标保存值, ref 卷标保存对象实例, <ref bean="person"/>
</ property >
< property name = "list" >
< list >
< value > 第一个 list 元素 </ value >
< value > 第二个 list 元素 </ value >
< value > 第三个 list 元素 </ value >
</ list >
</ property >
< property name = "prop" >
< props >
< prop key = "key1" > value1 </ prop >
< prop key = "key2" > value2 </ prop >
< prop key = "key3" > value3 </ prop >
</ props >
</ property >
< property name = "map" >
< map >
< entry key = "key-1" value = "value-1" />
< entry key = "key-2" value = "value-2" />
< entry key = "key-3" value = "value-3" />
</ map >
</ property >
</ bean >
4. 测试
AbstractApplicationContext ctx
= new ClassPathXmlApplicationContext( "Bean.xml" );
PersonService person=(PersonService) ctx.getBean( "personService" );
System . out .println( "========set===========" );
for (String value : person.getSet()){
System . out .println(value);
}
System . out .println( "========list===========" );
for (String value : person.getList()){
System . out .println(value);
}
System . out .println( "========properties===========" );
for (Object key : person.getProp().keySet()){
System . out .println(key+ "=" + person.getProp().getProperty((String)key));
}
System . out .println( "========map===========" );
for (String key : person.getMap().keySet()){
System . out .println(key+ "=" + person.getMap().get(key));
}
-----------------------Spring 依赖注入之 bean 构造器 ----------------------
使用 bean 构造器可以注入依赖对象或基本类型属性
在 Person 类中定义两个属性,并生成 带有参数的构造器
public class Person implements PersonService {
public PersonDao personDao ;
public String name ;
public Person(PersonDao personDao,String name){
System. out .println( "bean 已被实例化 " );
System. out .println( " 使用构造器注入 :" );
this . personDao =personDao;
this . name =name;
}
}
修改 xml 配置文件
< bean id = "personDao" class = "com.bean.service.impl.PersonDao" />
< bean id = "personService" class = "com.bean.service.impl.Person" >
< constructor-arg index = "0" type = "com.bean.service.impl.PersonDao" ref = "personDao" />
< constructor-arg index = "1" type = "java.lang.String" value = "Shiki" />
</ bean >
index 属性表示构造器中参数的索引, type 是参数的类型, ref 或 value 指定要注入的对象或基本类型的值
---------------------Spring 依赖注入之使用注解注入属性 --------------------
如果我们不希望配置文件所配置的属性非常多非常复杂,我们可以使用注解的方式对依赖对象进行注入
1. 如果想使用注解的方式,则我们需要在 xml 配置文件中添加以下额外的信息:
< 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"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" >
<context:annotation-config/>
</ beans >
这个配置隐式注册了多个处理器,用于对注释进行解析
这些处理器包括:
AutowiredAnnotationBeanPostProcessor :用来解析 @Autowired 注解
CommonAnnotationBeanPostProcessor :用来解析 @Resource 注解
PersistenceAnnotationBeanPostProcessor :用来处理持久化
RequiredAnnotationBeanProcessor
注解本身和 xml 的作用一样,也是用于配置,而不能自己干活,之所以在这里能够干活是因为背后有这些处理器进行处理
<context:annotation-config/> 就针对注解所使用的处理器注入到了 Spring 容器中
注,不配置处理器的另一个方法是:
@Autowired 起作用必须事先在 Spring 容器中声明 AutowiredAnnotationBeanPostProcessor Bean , 该 BeanPostProcessor 将自动对标注 @Autowired 的 Bean 进行注入
< bean class = "org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
2. 导入注解所需要的 jar 包
lib/j2ee/common-annotations.jar
3. 在 java 代码中使用 @Autowired 或 @Resource 注解方式进行装配。
(1) 这两个注解的区别是: @Autowired 默认按类型装配, @Resource 默认按名称装配,当找不到与名称匹配的 bean 才会按类型匹配
(2) @Autowired 默认情况下要求依赖对象必须存在,如果允许 null 值,可以设置 required 属性为 false; 如果我们想使用名称匹配,可以结合 @Qualifier 注解一起使用
(3) @Resource 注解和 @Autowired 注解某些方面一样,也可以标注在字段或属性的 set 方法上,但他默认按名称匹配,名称可以通过 @Resource 的 name 属性指定,如果没有指定 name 属性,当注解标注在字段上,即默认取字段的名称作为 bean 名称寻找依赖对象,当注解标注在属性的 set 方法上,即默认取属性名作为 bean 名称寻找依赖对象
(4) 如果没有指定 name 属性,并且按照默认的名称仍然找不到依赖对象时, @Resource 注解会回退到按类型匹配。一旦指定了 name 属性,就只能按名称匹配了
在 xml 配置文件中配置 Person 和要注入的 bean
< bean id = "personDao" class = "com.bean.service.impl.PersonDao" />
< bean id = "personService" class = "com.bean.service.impl.Person" >
在 Person 类中添加注解
public class Person implements PersonService {
@Autowired
public PersonDao personDao ;
public void Notify(){
System. out .println( "Hello Spring" );
personDao .add();
}
}
@Autowired 是按照类型匹配,结合 @Qualifier 注解一起使用就可以使用名称匹配
@Autowired(required=false) , required=false 属性表示不存在匹配的 bean 的时候,不报错
@Resource @Qualifier ( "personDao" )
@Qualifier(“XXX”) 括号中的内容表示要注入的 bean 的名称
当然,最好是直接使用按名称匹配的注解 @Resource ,如果按照名称找不到相应的 bean ,则会回退到按类型匹配,这样做最保险
-----------------------Spring 依赖注入之自动装配 -----------------------
注入依赖对象可以采用手工装配和自动装配
手工装配包括了:使用构造器注入, set 方法注入, Field 注入 ( 注解方式 )
以上讲解的装配方式都属于手动装配,在实际应用中建议使用手工装配,因为自动装配会产生未知情况,开发人员无法预见最终的装配结果
下面来讲解如何进行自动装配:
在 xml 配置文件中
< bean id = "personDao" class = "com.bean.service.impl.PersonDao" />
< bean
id = "personService"
class = "com.bean.service.impl.Person"
autowire = "xxx"
>
这里的 autowire 属性的取值有多种,可以设定按类型,名称,构造器和自省机制来自动匹配要注入的 bean 。
就这个程序而言,如果要进行名称匹配,在 Person 类中搜索属性的名称与该 xml 配置文件中的其他 bean 的名称相比较,如果存在,就将这个 bean 注入到 Person 类的属性之中
Autowire 属性的取值如下 :
byType: 按类型匹配,可以根据属性的类型,在容器中寻找跟该类型匹配的 bean 。如果发现多个,那么将抛出异常,如果没有找到,即属性值为 null
byName: 按名称匹配,可以根据属性的名称,在容器中寻找跟该属性名匹配的 bean 。如果没有找到,即属性值为 null
constructor 与 byType 的方式类似,不同之处在于它应用于构造器函数。如果在容器中没有找到与构造器参数类型一致的 bean ,那么将会抛出异常
autodetect: 通过 bean 类的自省机制 (introspection) 来决定是使用 constructor 还是 byType 方式进行自动匹配。如果发现默认的构造器,那么将使用 byType 方式
在 Person 类中不需要使用任何注解,但一定要注意!使用 autowire 与那两种注解方式不同,在 person 类中必须含有该依赖对象的 set 方法!否则就无法给依赖对象赋值了
public class Person implements PersonService {
public PersonDao personDao ;
public void Notify(){
System. out .println( "Hello Spring" );
System. out .println( "id=" + id + ",name=" + name );
personDao .add();
}
public void setPersonDao(PersonDao personDao) {
this . personDao = personDao;
}
}
测试:
依然调用 person.Notify() 方法,根据 autowire 所指定的匹配方式来寻找 bean ,如果成功,打印出注入 bean 的 add 方法中的信息
-----------------------Spring 自动扫描和管理 Bean------------------------
前面的例子我们都是使用 XML 的 bean 定义来配置组件。在一个稍大的项目中,通常会有上百个组件,如果这些组件都采用 XML 的 bean 定义来配置,显然会增加配置文件的体积,即使使用注解的方式,也只能会减少 <bean> 中的属性,而 <bean> 元素的数量依旧会很大,查找及维护起来也不太方便。
由此, Spring2.5 版本为我们引入了组件自动扫描机制,他可以在类路径底下寻找标注了 @Component 、 @Service 、 @Controller 、 @Repository 注解的类,并把这些类纳入进 Spring 容器中管理。它的作用和在 xml 檔中使用 <bean> 节点配置组件是一样的
要使用自动扫描机制,我们需要打开一下配置信息: ( 添加自动扫描的处理器 )
< 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"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="cn.itcast"/>
</ beans>
其中 base-package 属性为需要扫描的包 ( 含子包,也就是以 cn.itcast 开头的包都会被扫描到 )
@Service 用于标注业务层组件
@Controller 用于标注控制层组件 ( 如 Struts 中的 Action)
@Repository 用于标注数据访问组件,即 DAO 组件。
@Component 泛指组件,即当组件不好归类的时候,我们可以使用这个注解进行标注
在类中添加这些注解, Spring 容器就会根据指定的包路径自动扫描到这些类,并生成相应的 bean 实例,例如:
@Service
public class Person implements PersonService {}
如果没有指定名称,那么 bean 的名称就是类的简单名称,也就是把类名首字母小写的名称。在使用时,依然使用 getBean(“xXX”) 来获取的 bean 实例,这里边的 xXX 的名称就是那个类的简单名称
当然,也可以通过注解来给 bean 提供一个名称
@Repository ( "abc" )
public class PersonDao implements PersonDaoService {}
getBean(“XXX”) 获取时就使用 abc 这个提供的名称了
测试:
AbstractApplicationContext ctx
= new ClassPathXmlApplicationContext( "Bean.xml" );
PersonService person=(PersonService) ctx.getBean( "person" );
PersonDaoService personDao =(PersonDaoService) ctx.getBean( "abc" );
System. out .println( "person 是否存在? " +person);
System. out .println( "personDao 是否存在? " +personDao );
结合 @Scope(“XXX”) 注解一起使用,可以改变该 bean 的作用域范围
@Service @Scope ( "prototype" )
public class Person implements PersonService {}
prototype 在之前讲解过,每次从容器获取 bean 都是新的对象,而默认获取的 bean 的物件是单例的
利用注解来标注初始化和销毁方法
@PostConstruct
public void init(){
System. out .println( " 完成 Spring 容器初始化, init 方法被调用 " );
}
@PreDestroy
public void destory(){
System. out .println( "Spring 容器即将关闭, destory 方法被调用 " );
}
------------------------------- 静态代理 -------------------------------
例如:要在输出 “HelloWorld” 前打印一个字符串 “Welcome”
1. 定义一个接口类
package ttitfly.proxy;
public interface HelloWorld {
public void print();
// public void say();
}
2. 定义一个该接口的实现类
package ttitfly.proxy;
public class HelloWorldImpl implements HelloWorld{
public void print(){
System.out.println("HelloWorld");
}
// public void say(){
// System.out.println("Say Hello!");
// }
}
3. 定义一个静态代理类
package ttitfly.proxy;
public class StaticProxy implements HelloWorld{
public HelloWorld helloWorld ;
public StaticProxy(HelloWorld helloWorld){
this.helloWorld = helloWorld;
}
public void print(){
System.out.println("Welcome");
// 相当于回调
helloWorld.print();
}
// public void say(){
// // 相当于回调
// helloWorld.say();
// }
}
4. 测试类
package ttitfly.proxy;
public class TestStaticProxy {
public static void main(String[] args){
HelloWorld helloWorld = new HelloWorldImpl();
StaticProxy staticProxy = new StaticProxy(helloWorld);
staticProxy.print();
// staticProxy.say();
}
}
可以看出静态代理类有一个很不好的缺点:如果当接口加一个方法(把上面所有的代码的注释给去掉),所有的实现类和代理类里都需要做个实现。这就增加了代码的复杂度。动态代理就可以避免这个缺点。
-----------------------Proxy 技术实现 AOP( 动态代理 )----------------------
AOP 应用于做权限系统,我们可能需要粗粒度的权限控制,或者细粒度的权限控制,细粒度的权限控制我们一般对方法进行拦截,拦截方法后判断用户是否具有权限,有权限就允许用户执行被拦截的方法
用 Proxy 技术来模拟一下实际的业务需求,在不使用 AOP 框架的时候,实现其功能
但 Proxy 有局限性,必须当目标类实现了接口,才可以使用 jdk 的 Proxy 来生成代理对象。
1. 创建需要代理的 类
PersonServiceBean.java
public class PersonServiceBean{
private String user = null ;
public String getUser() {
return user ;
}
public PersonServiceBean(){}
public PersonServiceBean(String user){
this . user = user;
}
public String getPersonName(Integer personid) {
System. out .println( " 我是 getPersonName() 方法 " );
return "xxx" ;
}
public void save(String name) {
System. out .println( " 我是 save() 方法 " );
}
public void update(String name, Integer personid) {
System. out .println( " 我是 update() 方法 " );
}
}
2. 抽取接口,抽取后,会自动将该类实现该接口
PersonService.java
public interface PersonService {
public void save(String name);
public void update(String name, Integer personid);
public String getPersonName(Integer personid);
}
3. 代理工厂 ( 也就是一个代理的工具类,用来创建代理对象并拦截其方法 )
JDKProxyFactory.java
public class JDKProxyFactory implements InvocationHandler{
private Object targetObject ;
public Object createProxyIntance(Object targetObject){
this . targetObject = targetObject;
return Proxy.newProxyInstance ( this . targetObject .getClass().getClassLoader(),
this . targetObject .getClass().getInterfaces(), this );
// 三个参数为 代理对象的类加载器,实现的接口,以及 InvocationHandler 类的实例
/* InvocationHandler 是代理实例的调用处理程序实现的接口。
每个代码实例都具有一个关联的调用处理程序。
对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。
通过创建代理对象后,由其所调用的方法都将被 InvocationHandler 界面的 invoke 方法所拦截,并根据需求来决定如何执行
*/
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable { // 环绕通知
PersonServiceBean bean = (PersonServiceBean) this . targetObject ;
Object result = null ;
if (bean.getUser()!= null ){
//..... advice()--> 前置通知
try {
result = method.invoke( targetObject , args);
// afteradvice() --> 后置通知
} catch (RuntimeException e) {
//exceptionadvice()--> 例外通知
} finally {
//finallyadvice(); --> 最终通知
}
}
return result;
}
}
4. 测试类
public class AOPTest {
public static void main(String[] args) {
JDKProxyFactory factory = new JDKProxyFactory();
PersonService service = (PersonService) factory.createProxyIntance( new PersonServiceBean( "xxx" ));
// 创建一个代理对象
service.save( "888" );
// 经过拦截,由 invoke 方法来调用
}
}
-----------------------------CGLIB 技术实现 ----------------------------
如果这个类没有提供接口,那么我们就无法来创建 Proxy 代理对象,那么我们用一个第三方的创建代理对象的框架 —cglib
spring 中包含了它的 jar 檔 lib/cglib/cglib-nodep-2.1_3.jar
cglib 可以生成目标类的子类,并重写父类非 final 修饰符的方法。
通过 cglib ,我们可以通过没有实现接口的类就可以创建代理对象
1. 继续使用上边的 PersonServiceBean.java 类作为目标类
2. 创建 cglib 代理工厂
public class CGlibProxyFactory implements MethodInterceptor{
private Object targetObject ;
public Object createProxyIntance(Object targetObject){
this . targetObject = targetObject;
Enhancer enhancer = new Enhancer(); // 创建代理对象
enhancer.setSuperclass( this . targetObject .getClass()); // 非 final
/* 这个参数要填写一个父类,这个父类也就是目标类,就是要代理的那个类
当把目标类设置为代理对象父类的时候,代理技术会产生一个目标类的子类,在这个子类里边,它可以覆盖目标类中所有非 final 修饰的方法
实际上, cglib 创建的代理对象是继承了目标类,对目标类的所有非 final 方法进行覆盖,在这些方法中可以添加一些自身的方法
*/
enhancer.setCallback( this );
// 回调方法,与 Proxy 的原理相同,也必须要实现一个接口,当代理对象的业务方法被调用的时候,会回调这个方法,
return enhancer.create();
}
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
PersonServiceBean bean = (PersonServiceBean) this . targetObject ;
Object result = null ;
if (bean.getUser()!= null ){
result = methodProxy.invoke( targetObject , args);
}
return result;
}
}
3. 测试类
public class AOPTest {
public static void main(String[] args) {
//cglib
CGlibProxyFactory factory = new CGlibProxyFactory();
PersonServiceBean service = (PersonServiceBean) factory.createProxyIntance( new PersonServiceBean( "xxx" ));
service.save( "999" );
}
}
-----------------------------AOP 中的概念 -----------------------------
Aspect( 切面 ) : 指横切性关注点的抽象即为切面 , 它与类相似 , 只是两者的关注点不一样 , 类是对物体特征的抽象 , 而切面横切性关注点的抽象 .
joinpoint( 连接点 ) : 所谓连接点是指那些被拦截到的点。在 spring 中 , 这些点指的是方法 , 因为 spring 只支持方法类型的连接点 , 实际上 joinpoint 还可以是 field 或类构造器 )
Pointcut( 切入点 ): 所谓切入点是指我们要对那些 joinpoint 进行拦截的定义 .
Advice( 通知 ) : 所谓通知是指拦截到 joinpoint 之后所要做的事情就是通知 . 通知分为前置通知 , 后置通知 , 异常通知 , 最终通知 , 环绕通知
Target( 目标对象 ): 代理的目标对象
Weave( 织入 ): 指将 aspects 应用到 target 对象并导致 proxy 对象创建的过程称为织入 .
Introduction( 引入 ): 在不修改类代码的前提下 , Introduction 可以在运行期为类动态地添加一些方法或 Field.
--------------------- 使用 Spring 进行面向切面( AOP )程序设计 --------------------
AOP ,主要是用于拦截方法
要进行 AOP 程序设计,首先我们要在 spring 的配置文件中引入 aop 命名空间:
< beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd " >
</ beans >
Spring 提供了两种切面声明方式,实际工作中我们可以选用其中一种:
基于 XML 配置方式声明切面。
基于注解方式声明切面。
基于注解方式
1. 首先启动对 @AspectJ 注解的支持 ( 蓝色部分为预设的,红色部分是 aop 所用到的 ) :
< beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
< aop:aspectj-autoproxy />
< bean id = "PersonServiceBean" class = "com.aop.PersonServiceBean"/ >
< bean id = "myInterceptor" class = "com.aop.MyInterceptor"/ >
</ beans >
<aop:aspectj-autoproxy/> 表示打开配置项,为我们这个注解提供了解析的功能
注解本身不能自己干活,之所以在这里能够干活是因为背后有这些处理器进行处理
< aop:aspectj-autoproxy proxy-target-class = "true" />
参数 proxy-target-class=true 表示使用 cglib 来生成代理对象,默认 false 是使用 JDKProxy 的方法创建,但是要依据情况而定,如果提供的目标类没有实现接口就使用 cglib ,实现接口的使用 JDKProxy
这里要注意
2. 创建界面及实现类
public interface PersonService {
public void save(String name);
public void update(String name, Integer id);
public String getPersonName(Integer id);
}
public class PersonServiceBean implements PersonService {
public String getPersonName(Integer id) {
System. out .println( " 我是 getPersonName() 方法 " );
return "xxx" ;
}
public void save(String name) {
throw new RuntimeException( " 例外,抛出异常 " );
//System.out.println(" 我是 save() 方法 ");
}
public void update(String name, Integer id) {
System. out .println( " 我是 update() 方法 " );
}
}
3. 创建切面类
利用 aop 技术进行方法拦截,对类和方法用不同的注解进行标记,完成不同的功能
@Aspect // 表示采用注解的方式声明切面
public class MyInterceptor {
/*Pointcut 切入点,用来定义我们需要拦截的方法,后边引用了一些 aop 表达是语言
1 2 3 4 5 6
@Pointcut("execution (* com.source..*.*(..))")
1 号位置 execution 表示执行,执行该方法的时候进行拦截
2 号位置 表示返回值类型, * 号通配符,表示任意类型
3 号位置 表示包名,对哪个包下的类进行拦截
4 号位置 两个点表示对其包下的子包里边的内容也要进行拦截,如果不加点的话 就表示只包含当前包里边的内容
5 号位置 第一个 * 号表示类,第二个 * 号表示方法, *.* 表示对任意个类的所有方法进行拦截
6 号位置 表示方法的参数, .. 表示任意参数,有无都可以, 1 个, 2 个或者多个
*/
/*
通知的种类
@Before 前置通知
@AfterReturning 后置通知
@After 最终通知
@AfterThrowing 例外通知
@Around 环绕通知
*/
/*
通知执行的顺序 ( 环绕通知包括 进入方法和退出方法 )
前置通知 :Shiki
进入方法 ( 环绕通知 进入方法紧跟在方法前执行 )
我是 save() 方法
后置通知 :null( 紧跟在方法后执行 )
最终通知 (finally 中执行 )
退出方法 ( 环绕通知 退出方法当方法执行完毕后执行 )
*/
@Pointcut ( "execution (* com.aop.PersonServiceBean.*(..))" )
private void anyMethod() {} // 利用 @Pointcut 注释声明一个切入点,名称为 anyMethod
//aop 表达是语言 表示对 com.aop.PersonServiceBean 类下的所有方法进行拦截
@Before ( "anyMethod() && args(name)" ) // 括号里边填写的是切入点的名称和方法参数名
// 该参数名必须与该通知定义的方法的参数名同名,否则会报异常
public void doAccessCheck(String name) {
System. out .println( " 前置通知 :" + name);
}
@AfterReturning (pointcut= "anyMethod()" ,returning= "result" )
public void doAfterReturning(String result) {
System. out .println( " 后置通知 :" + result);
}
@After ( "anyMethod()" )
public void doAfter() {
System. out .println( " 最终通知 " );
}
@AfterThrowing (pointcut= "anyMethod()" ,throwing= "e" )
public void doAfterThrowing(Exception e) {
System. out .println( " 例外通知 :" + e);
}
@Around ( "anyMethod()" )
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
//if(){// 判断用户是否在权限 这个环绕通知很适合于做权限验证 如果用户存在,然后执行被拦截的方法
System. out .println( " 进入方法 " );
Object result = pjp.proceed(); // 表示被拦截的方法
System. out .println( " 退出方法 " );
//}
return result;
}
}
4. 修改配置文件,实例化该 bean
< bean id = "myInterceptor" class = "com.aop.MyInterceptor" ></ bean >
< bean id = "PersonServiceBean" class = "com.aop.PersonServiceBean" ></ bean >
5. 测试类
public class Test {
public static void main(String[] args) {
AbstractApplicationContext ctx = new ClassPathXmlApplicationContext( "Bean.xml" );
PersonService personService=(PersonService) ctx.getBean( "PersonServiceBean" );
personService.save( "Shiki" );
System. out .println();
personService.getPersonName (2);
}
}
基于 XML 配置方式
1. 与前一个基于注解的方式所使用的文件基本相同 ( 接口和抽象类保持不变 ) ,只是在切面类中不再写入注解了,是一个只有各种通知方法的类
public class MyInterceptor_XML {
private void anyMethod() {}
public void doAccessCheck(String name) {
System. out .println( " 前置通知 :" + name);
}
public void doAfterReturning(String result) {
System. out .println( " 后置通知 :" + result);
}
public void doAfter() {
System. out .println( " 最终通知 " );
}
public void doAfterThrowing(Exception e) {
System. out .println( " 例外通知 :" + e);
}
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
//if(){// 判断用户是否在权限 这个环绕通知很适合于做权限验证 如果用户存在,然后执行被拦截的方法
System. out .println( " 进入方法 " );
Object result = pjp.proceed(); // 表示被拦截的方法
System. out .println( " 退出方法 " );
//}
return result;
}
}
2. 在 Bean.xml 文件中写入配置信息
< bean id = "personServiceBean" class = "com.aop.PersonServiceBean" ></ bean >
< bean id = "aspectBean" class = "com.aop.MyInterceptor_XML" ></ bean >
< aop:config >
< aop:aspect id = "myaop" ref = "aspectBean" >
< aop:pointcut
id = "mycut" expression = "execution(* com.aop..*.*(..))" />
< aop:before pointcut-ref = "mycut" method = "doAccessCheck" />
< aop:after-returning
pointcut-ref = "mycut" method = "doAfterReturning" />
< aop:after pointcut-ref = "mycut" method = "doAfter" />
< aop:after-throwing
pointcut-ref = "mycut" method = "doAfterThrowing" />
< aop:around
pointcut-ref = "mycut" method = "doBasicProfiling" />
</ aop:aspect >
</ aop:config >
----------------------Spring 与 jdbc 整合 ( 配置数据源 )---------------------
1. 配置数据源共有两种方法
在 Bean.xml 配置文件中配置数据源
< bean id = "dataSource" class = "org.apache.commons.dbcp.BasicDataSource" destroy-method = "close" >
< property name = "driverClassName" value = "org.gjt.mm.mysql.Driver" />
< property name = "url" value = "jdbc:mysql://localhost:3306/hibernate?useUnicode=true&characterEncoding=UTF-8" />
< property name = "username" value = "root" />
< property name = "password" value = "root" />
< property name = "initialSize" value = "1" />
<!-- 连接池启动时的初始值 -->
< property name = "maxActive" value = "500" />
<!-- 连接池的最大值 -->
< property name = "maxIdle" value = "2" />
<!-- 最大空闲值 . 当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到 maxIdle 为止 -->
< property name = "minIdle" value = "1" />
<!-- 最小空闲值 . 当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
</ bean >
使用属性占位符方式配置数据源
使用 <context:property-placeholder location=“jdbc.properties”/> 属性占位符
将属性文件的信息引入配置文件中
< context:property-placeholder location = "jdbc.properties" />
< bean id = "dataSource" class = "org.apache.commons.dbcp.BasicDataSource" destroy-method = "close" >
< property name = "driverClassName" value = "${driverClassName}" />
< property name = "url" value = "${url}" />
< property name = "username" value = "${username}" />
< property name = "password" value = "${password}" />
<!-- 连接池启动时的初始值 -->
< property name = "initialSize" value = "${initialSize}" />
<!-- 连接池的最大值 -->
< property name = "maxActive" value = "${maxActive}" />
<!-- 最大空闲值 . 当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到 maxIdle 为止 -->
< property name = "maxIdle" value = "${maxIdle}" />
<!-- 最小空闲值 . 当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
< property name = "minIdle" value = "${minIdle}" />
</ bean >
2. 创建数据表
CREATE TABLE `person` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk;
3. 创建 javabean
public class Person {
private Integer id ;
private String name ;
public Person(){}
public Person(String name) {
this . name = name;
}
public Integer getId() {
return id ;
}
public void setId(Integer id) {
this . id = id;
}
public String getName() {
return name ;
}
public void setName(String name) {
this . name = name;
}
}
4. 创建界面及实现类
public interface PersonService {
/**
* 保存 person
* @param person
*/
public void save(Person person);
/**
* 更新 person
* @param person
*/
public void update(Person person);
/**
* 获取 person
* @param personid
* @return
*/
public Person getPerson(Integer personid);
/**
* 获取所有 person
* @return
*/
public List<Person> getPersons();
/**
* 删除指定 id 的 person
* @param personid
*/
public void delete(Integer personid);
}
@Transactional
public class PersonServiceBean implements PersonService {
private JdbcTemplate jdbcTemplate ;
public void setDataSource(DataSource dataSource) {
this . jdbcTemplate = new JdbcTemplate(dataSource);
}
public void delete(Integer personid) {
jdbcTemplate .update( "delete from person where id=?" , new Object[]{personid}, new int []{java.sql.Types. INTEGER });
// 所有方法的参数基本类似,第一个为 sql 语句,第二个是给 ? 传递的参数,第三个是 ? 参数的类型码
}
public Person getPerson(Integer personid) {
return (Person) jdbcTemplate .queryForObject( "select * from person where id=?" , new Object[]{personid}, new int []{java.sql.Types. INTEGER }, new PersonRowMapper());
}
@SuppressWarnings ( "unchecked" )
public List<Person> getPersons() {
return (List<Person>) jdbcTemplate .query( "select * from person" , new PersonRowMapper());
// 由于 sql 语句中没有参数,所以也就不存在参数类型码的问题了
}
public void save(Person person) {
jdbcTemplate .update( "insert into person(name) values(?)" , new Object[]{person.getName()},
new int []{java.sql.Types. VARCHAR });
}
public void update(Person person) {
jdbcTemplate .update( "update person set name=? where id=?" , new Object[]{person.getName(), person.getId()},
new int []{java.sql.Types. VARCHAR , java.sql.Types. INTEGER });
}
}
5. 创建查询操作中所使用到的 PersonRowMapper 类
public class PersonRowMapper implements RowMapper {
public Object mapRow(ResultSet rs, int index) throws SQLException {
Person person = new Person(rs.getString( "name" ));
person.setId(rs.getInt( "id" ));
return person;
}
}
6. 创建 Junit 测试类
public class PersonServiceTest {
private static PersonService personService ;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
try {
ApplicationContext cxt = new ClassPathXmlApplicationContext( "jdbcTemplate_properties_Bean.xml" );
personService = (PersonService) cxt.getBean( "personService" );
} catch (RuntimeException e) {
e.printStackTrace();
}
}
@Test public void save(){
for ( int i=0; i<5; i++)
personService .save( new Person( " 传智播客 " + i));
}
@Test public void getPerson(){
Person person = personService .getPerson(1);
System. out .println(person.getName());
}
@Test public void update(){
Person person = personService .getPerson(1);
person.setName( " 张 xx" );
personService .update(person);
}
@Test public void delete(){
personService .delete(1);
}
@Test public void getBeans (){
for (Person person : personService .getPersons()){
System. out .println(person.getName());
}
}
}
7. 对 Spring 的事务管理进行测试,查看当抛出不同异常时是否进行回滚操作 ( 以删除方法为例 )
1) 抛出一个运行期例外,
public void delete(Integer personid) {
jdbcTemplate .update( "delete from person where id=?" , new Object[]{personid},
new int []{java.sql.Types. INTEGER });
throw new RuntimeException( " 运行期例外 " );
}
程序回滚
2) 抛出一个 Check 这种例外 ( 就是普通的 Exception)
public void delete(Integer personid) throws Exception {
jdbcTemplate .update( "delete from person where id=?" , new Object[]{personid},
new int []{java.sql.Types. INTEGER });
throw new Exception ( " 异常 " );
}
程序不回滚,虽然最后抛出异常,但是之前方法会执行
3)Spring 开启的事务默认情况下如果碰到运行期例外,运行期例外一般也叫做 unchecked 例外,这种例外事务会回滚,还有一种叫做 checked 例外,这种例外事务不会回滚
如果想让 checked 例外的方法进行回滚操作,可以使用 @Transactional() 注解来修改它的行为
@Transactional (rollbackFor=Exception. class )
public void delete(Integer personid) throws Exception{
jdbcTemplate .update( "delete from person where id=?" , new Object[]{personid},
new int []{java.sql.Types. INTEGER });
throw new Exception( " 运行期例外 " );
}
rollbackFor 这个属性指定了即使出现了 checked 这种例外,它也会对这种事务进行回滚
相反,如果抛出的是运行期例外,可以使用 noRollbackFor 来 指定它不进行回滚
@Transactional (noRollbackFor=RuntimeException . class )
public void delete(Integer personid) throws Exception{
jdbcTemplate .update( "delete from person where id=?" , new Object[]{personid},
new int []{java.sql.Types. INTEGER });
throw new RuntimeException ( " 运行期例外 " );
}
8. 有的方法不需要事务的支持,例如像查询方法等,可以声明该方法不支持事务
@Transactional (propagation =Propagation. NOT_SUPPORTED )
public Person getPerson(Integer personid) {
return (Person) jdbcTemplate .queryForObject( "select * from person where id=?" , new Object[]{personid},
new int []{java.sql.Types. INTEGER }, new PersonRowMapper());
}
当我们打入了这个事务传播属性时, Spring 容器在方法执行前就不会开启事务了
----------------------Spring 与 jdbc 整合 ( 配置事务 )----------------------
1. 配置事务时,需要在 xml 配置文件中引入用于声明事务的 tx 命名空间
事务的配置方式有两种:注解方式和基于 XML 配置方式。
在 Bean.xml 配置文件中引入用于声明事务的 tx 命名空间
< beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop = "http://www.springframework.org/schema/aop"
xmlns:context = "http://www.springframework.org/schema/context"
xmlns:tx = "http://www.springframework.org/schema/tx"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd " >
采用注解方式配置事务
创建 Spring 容器的事务管理器
< bean id = "txManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" >
< property name = "dataSource" ref = "dataSource" />
</ bean >
添加解析注解的处理器
< tx:annotation-driven transaction-manager = "txManager" />
注解本身不能开启事务,只能起到配置的作用,之所以注解能够起作用,是因为添加了可以解析注解的处理器, transaction-manager 用来指定一个事务管理器
在类中添加事务的注解
@Service @Transactional
public class PersonServiceBean implements PersonService {
}
可以与自动装配一起使用,定义该类使用事务处理,并自动扫描创建该类的 bean
采用 XML 方式配置事务
依然使用 com.jdbcTemplate 包下的类文件
修改 Bean.xml 配置文件
< bean id = "txManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" >
< property name = "dataSource" ref = "dataSource" />
</ bean >
< aop:config >
< aop:pointcut id = "transactionPointcut" expression = "execution(* cn.itcast.service..*.*(..))" />
< aop:advisor advice-ref = "txAdvice" pointcut-ref = "transactionPointcut" />
</ aop:config >
< tx:advice id = "txAdvice" transaction-manager = "txManager" >
< tx:attributes >
< tx:method name = "get*" read-only = "true" propagation = "NOT_SUPPORTED" />
< tx:method name = "*" />
</ tx:attributes >
</ tx:advice >
< bean id = "personService" class = "cn.itcast.service.impl.PersonServiceBean" >
< property name = "dataSource" ref = "dataSource" />
</ bean >
--------------------------- 事务管理与传播属性 ---------------------------
事务的级别
1. REQUIRED :业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务。
2. NOT_SUPPORTED :声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行。
3. REQUIRESNEW :属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行。
4. MANDATORY :该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外。
5. SUPPORTS :这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行。
6. Never :指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行。
7. NESTED : 如果一个活动的事务存在,则运行在一个嵌套的事务中 . 如果没有活动事务 , 则按 REQUIRED 属性执行 . 它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对 DataSourceTransactionManager 事务管理器起效,
增删改查的方法默认的事务级别不同:
Update 方法默认的事务传播属性为 NESTED
------------------------------ 事务隔离级别 -----------------------------
数据库系统提供了四种事务隔离级别供用户选择。不同的隔离级别采用不同的锁类型来实现,在四种隔离级别中, Serializable 的隔离级别最高, Read Uncommited 的隔离级别最低。大多数据库默认的隔离级别为 Read Commited ,如 SqlServer ,当然也有少部分数据库默认的隔离级别为 Repeatable Read ,如 Mysql
Read Uncommited :读未提交数据 ( 会出现脏读 , 不可重复读和幻读 ) 。
Read Commited :读已提交数据 ( 会出现不可重复读和幻读 )
Repeatable Read :可重复读 ( 会出现幻读 )
Serializable :串行化
脏读 :一个事务读取到另一事务未提交的更新新据。
不可重复读: 在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事务已提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取不能读到另一事务已提交的更新数据。
幻读: 一个事务读取到另一事务已提交的 insert 数据。
---------Spring2.x+Hibernate3.x+Struts1.x 整合开发 (Jar 包的选择 )---------
hibernate 核心安装包下的 :
hibernate3.jar
lib/required/*.jar
lib/optional/ehcache-1.2.3.jar
hibernate 注解安装包下的
lib/test/slf4j-log4j12.jar
Spring 安装包下的
dist/spring.jar
dist/modules/spring-webmvc-struts.jar
lib/jakarta-commons/commons-logging.jar
commons-dbcp.jar 、 commons-pool.jar
lib/aspectj/aspectjweaver.jar 、 aspectjrt.jar
lib/cglib/cglib-nodep-2.1_3.jar
lib/j2ee/common-annotations.jar
lib/log4j/log4j-1.2.15.jar
Struts
下载 struts-1.3.8-lib.zip ,需要使用到解压目录下的所有 jar ,建议把 jstl-1.0.2.jar 和 standard-1.0.2.jar 更换为 1.1 版本。 Spring 中已经存在一个 antlr-2.7.6.jar ,所以把 struts 中的 antlr-2.7.2.jar 删除,避免 jar 冲突。
数据库驱动 jar
根据所使用的数据库来选择
------------------------Spring2.x+Hibernate3.x-----------------------
Hibernate 的配置文件可以不写,完全依靠 Spring 的 Bean.xml 文件进行配置,由 Spring 来构造 sessionFactory 和 TransactionManager 的实例
以 Student 程序为例
1. 载入 jar 包
2. 创建数据表
CREATE TABLE `student` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk;
3. 创建 javabean 和 *.hbm.xml
public class Student implements java.io.Serializable {
private Integer id ;
private String name ;
public Integer getId() {
return this . id ;
}
public void setId(Integer id) {
this . id = id;
}
public String getName() {
return this . name ;
}
public void setName(String name) {
this . name = name;
}
}
< hibernate-mapping >
< class name = "com.bean.Student" table = "student" catalog = "hibernate" >
< id name = "id" type = "java.lang.Integer" >
< column name = "id" />
< generator class = "native" />
</ id >
< property name = "name" type = "java.lang.String" >
< column name = "name" length = "20" not-null = "true" />
</ property >
</ class >
</ hibernate-mapping >
4. 配置 Bean.xml 文件
(1) 配置数据源,目地是为了提高性能,减少链接对象的创建数量
< bean id = "dataSource"
class = "org.apache.commons.dbcp.BasicDataSource" >
< property name = "driverClassName"
value = "com.mysql.jdbc.Driver" >
</ property >
< property name = "url"
value = "jdbc:mysql://localhost:3306/itcast?useUnicode=true&characterEncoding=UTF-8" >
</ property >
< property name = "username" value = "root" ></ property >
< property name = "password" value = "root" ></ property >
</ bean >
在 url 中,应该是写为 useUnicode=true&characterEncoding=UTF-8 ,但是 & 号是 xml 文件中的特殊字符,要对它进行转义为 &characterEncoding=UTF-8 指定了客户端使用什么编码与数据库进行通讯
(2) 创建 sessionFactory
其中所使用的 LocalSessionFactoryBean 类除了会创建一个单例的 sessionFactory 对象之外,专门集成 Hibernate 做一些额外的工作,例如:接管 Hibernate 的事务管理
< bean id = "sessionFactory" class = "org.springframework.orm.hibernate3.LocalSessionFactoryBean" >
< property name = "dataSource" ref = "dataSource" ></ property >
< property name = "hibernateProperties" >
< value >
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.hbm2ddl.auto=update
hibernate.show_sql=true
hibernate.format_sql=true
</ value >
</ property >
< property name = "mappingResources" >
< list >
< value > com/bean/Student.hbm.xml </ value >
</ list >
</ property >
</ bean >
hibernateProperties 配置 Hibernate 的属性, mappingResources 指定 Hibernate 的映射档
(3) 创建 DAO 类的实例
< bean id = "studentDAO" class = "com.dao.StudentDAO" >
< property name = "sessionFactory" ref = "sessionFactory" >
</ property >
</ bean >
(4) 事务管理
< bean id = "txManager" class = "org.springframework.orm.hibernate3.HibernateTransactionManager" >
< property name = "sessionFactory" ref = "sessionFactory" />
</ bean >
< tx:annotation-driven transaction-manager = "txManager" />
Spring 为我们开启事务,底层是需要使用 session 来创建,这里使用的 HibernateTransactionManager 是关于 Hibernate 的事务管理器,因为它要得到 session ,通过 session 来开启一个事务
需要通过 sessionFactory 来能得到 session ,所以就会有一个 sessionFactory 属性,用前边创建好的 sessionFactory 实例给该属性注入进去
5. 创建 DAO 类,抽取接口 ( 并添加事务管理的注解 )
类
public class StudentDAO extends HibernateDaoSupport implements StudentDAOService{
public void saveStudent(Student s){
HibernateTemplate template= this .getHibernateTemplate();
template.save(s);
}
}
界面
public interface StudentDAOService {
}
6. 测试类
public static void main(String[] args) {
Student s= new Student();
s.setName( "Shiki" );
ApplicationContext ctx= new ClassPathXmlApplicationContext( "Bean.xml" );
StudentDAO dao=(StudentDAO)ctx.getBean( "studentDAO" );
dao.saveStudent(s);
}
--------------------------Spring 2.x+Struts 1.x-----------------------
Struts 与 Spring 集成方案共有两种方式:
1. Struts 集成 Spring
2. Spring 集成 Struts
------------------------Struts 1.x 集成 Spring 2.x----------------------
1. 载入 jar 包
2. 在 web 容器中实例化 spring 容器 ( 利用 Listener 监听器来实例化 )
在 web.xml 文件中加入
<!-- 指定 spring 的配置文件,默认从 web 根目录寻找配置文件,我们可以通过 spring 提供的 classpath: 前缀指定从类路径下寻找 -->
< context-param >
< param-name > contextConfigLocation </ param-name >
< param-value > classpath:beans.xml </ param-value >
</ context-param >
<!-- 对 Spring 容器进行实例化 -->
< listener >
< listener-class >
org.springframework.web.context.ContextLoaderListener
</ listener-class >
</ listener >
Listener 在启动的时候会向 application 中放入 Spring 容器实例,也就是可以从 application 获取到 Spring 的容器实例,从而调用 getBean 方法
3. 在 web 容器中配置 struts ,
< servlet >
< servlet-name > struts </ servlet-name >
< servlet-class > org.apache.struts.action.ActionServlet </ servlet-class >
< init-param >
< param-name > config </ param-name >
< param-value > /WEB-INF/struts-config.xml </ param-value >
</ init-param >
< load-on-startup > 0 </ load-on-startup >
</ servlet >
< servlet-mapping >
< servlet-name > struts </ servlet-name >
< url-pattern > *.do </ url-pattern >
</ servlet-mapping >
4. 创建 Action ,并为该 Action 在 struts-config.xml 文件中添加配置信息
public class PersonAction extends Action {
@Override
public ActionForward execute(ActionMapping mapping, ActionForm arg1, HttpServletRequest request, HttpServletResponse response) throws Exception {
WebApplicationContext ctx=
WebApplicationContextUtils.getWebApplicationContext ( this .getServlet().getServletContext());
PersonService personService=
(PersonService)ctx.getBean( "personService" );
request.setAttribute( "person" ,personService.getPersons());
return mapping.findForward( "list" );
}
}
<? xml version = "1.0" encoding = "UTF-8" ?>
<! DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd" >
< struts-config >
< action-mappings >
< action path = "/person"
type = "com.Spring2.x_Struts1.x.PersonAction.java"
validate = "false" >
< forward name = "list" path = "/WEB-INF/page/personlist.jsp" ></ forward >
</ action >
</ action-mappings >
</ struts-config >
5. 在 Spring 配置文件中构造 bean 实例
< bean id = "personService" class = "com.jdbcTemplate.PersonServiceBean" >
< property name = "dataSource" ref = "dataSource" />
</ bean >
引用 com.jdbcTemplate 包中的 Person 、 PersonService 及 PersonServiceBean
6. 编写 personlist.jsp 页面,在页面中迭代出 person 中的数
<%@ page language = "java" import = "java.util.*" pageEncoding = "UTF-8" %>
<%@ taglib uri = "http://struts.apache.org/tags-bean" prefix = "bean" %>
<%@ taglib uri = "http://struts.apache.org/tags-logic" prefix = "logic" %>
<%@ taglib uri = "http://struts.apache.org/tags-html" prefix = "html" %>
<%@ taglib uri = "http://struts.apache.org/tags-tiles" prefix = "tiles" %>
<! DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" >
< html >
< head >
</ head >
< body >
< logic:iterate id = "person" name = "person" scope = "request" >
id= < bean:write name = "id" /> ,name= < bean:write name = "name" />
</ logic:iterate >
</ body >
</ html >
-----------------------Spring 2.x 集成 Struts 1.x -----------------------
1. 试想,在 Struts 集成 Spring 的 Action 中,
WebApplicationContext ctx=
WebApplicationContextUtils.getWebApplicationContext ( this .getServlet().getServletContext());
PersonService personService=
(PersonService)ctx.getBean( "personService" );
这段复杂的代码,我们是否可以使用 Spring 的依赖注入功能来实现呢 ?
所以,我们打算将 Action 交给 Spring 管理,然后 Action 就不再由 Struts 框架给我们生成,而是由 Spring 容器帮我们创建
把 Action 交给 Spring 管理后,我们可以使用依赖注入在 action 中注入业务层的 bean 。
但要注意一点,要确保 Action 的 path 属性值与 bean 的名称相同。
Struts 配置:
< action path = "/person/list" ... >
</ action >
Spring 配置:
< bean name = "/person/list" class = "cn.itcast.web.action.PersonAction" />
默认配置 bean 的名称为 id 属性,但是 / 属于特殊字符,所以只能使用 name 属性来配置
2. 在 struts 配置文件中添加进 spring 的请求控制器,该请求控制器会先根据 action 的 path 属性值到 spring 容器中寻找跟该属性值同名的 bean 。如果寻找到即使用该 bean 处理用户请求
<controller>
<set-property property="processorClass"
value="org.springframework.web.struts.DelegatingRequestProcesso"/>
</controller>
请求管理器 org.springframework.web.struts.DelegatingRequestProcessor 的处理过程。
当处理器根据 /person/list.do 这个名称到 Spring 容器中找不到这个 bean 的时候,就会交给 Struts 进行处理了, Struts 就会根据 action 中的 type 属性将 Action 对象 new 出来,放在缓存里边去
----------------------Spring 3.0 Security 安全框架 ----------------------
1. 加入 Security 的 jar 包
2. 修改 web.xml 配置文件
<? xml version = "1.0" encoding = "UTF-8" ?>
< web-app version = "2.5" xmlns = "http://java.sun.com/xml/ns/javaee"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" >
<!-- 安全框架的配置 -->
< filter >
< filter-name > springSecurityFilterChain </ filter-name > <!-- 名子不能更换 -->
< filter-class > org.springframework.web.filter.DelegatingFilterProxy </ filter-class >
</ filter >
< filter-mapping >
< filter-name > springSecurityFilterChain </ filter-name >
< url-pattern > /* </ url-pattern > <!-- 过滤所有的请求操作 -->
</ filter-mapping >
<!-- Spring 的配置 , 加载 Spring 的配置文件 -->
< context-param >
< param-name > contextConfigLocation </ param-name >
< param-value > /WEB-INF/applicationContext.xml </ param-value >
</ context-param >
< listener >
< listener-class > org.springframework.web.context.ContextLoaderListener </ listener-class >
</ listener >
< welcome-file-list >
< welcome-file > index.jsp </ welcome-file >
</ welcome-file-list >
</ web-app >
3. 在 Spring 的配置文件中加入 Security 的命名空间
<? 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:security="http://www.springframework.org/schema/security"
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.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd " >
</ beans >
4. 配置 Spring 配置文件
<!-- 安全级别的设定 -->
< security:http auto-config = "true" >
< security:intercept-url pattern = "/**" access = "ROLE_UESR" />
<!-- 要求登录的用户必须拥有某个角色 -->
</ security:http >
security:http 表示所有与 web 命名空间相关的上级,
intercept-url 表示要拦截的 url 形式,比如
<intercept-url pattern=”/secure/**” access=”isAuthenticated()” />
表示根目录下的 secure 目录需要经过验证后才能访问的。
Pattern 用来匹配请求当中的内容, access 表示模式哪一种权限的使用者
form-login 是 Spring Security 自动为你生成的一个简陋的登录页面,即使你没有创建任何登录页面,当然你也可以修改,但不建议你修改,因为你可以不使用默认的,可以采用 如下方式: < security:form-login login-page = "/login.jsp" /> 自定义一个登录页面。
容易出错的地方
先看一个小例子:
< http use-expressions = "true" >
< intercept-url pattern = "/secure/extreme/**"
access = "hasRole('ROLE_SUPERVISOR')" />
< intercept-url pattern = "/secure/**" access = "isAuthenticated()" />
<!—
Disable web URI authorization, as we're using <global-method-security> and have @Secured the services layer instead
<intercept-url pattern="/listAccounts.html" access="isRememberMe()" />
<intercept-url pattern="/post.html" access="hasRole('ROLE_TELLER')" />
-->
< http use-expressions = "true" >
表示这里的配置可以使用一种表达式,这种表达式就是类似于 isAuthenticated() 这样的写法,在后面会看到与这种写法不一样但同样可以达到相同效果的写法。
在 Spring 翻译文档(也是翻译自官方文档)或英文官方文档中,给出的与上面例子功能相似的说明大概是这样的:
<http auto-config='true'>
<intercept-url pattern="/**" access="ROLE_USER" />
</http>
注意到与上面例子不同的地方了吗?
还是注意第一行,这回不是 <http use-expressions=”true”> 而是 <http auto-config=’true’> 了,而下面的配置
<intercept-url pattern=”/**” access=”ROLE_USER” /> 也与上面的写法不同,事实上如果是 <http use-expressions=”true”> 的话,这句 access=”ROLE_USER” 就是错的,正确的写法就应该为: hasRole(‘ROLE_USER’) 。
这不得不说这是 Spring Security 的文档与例子不搭配的一个低级错误,因为当一个使用者在打开例子又看到文档说明时,他往往不知道这两者有何区别,就如同我刚使用的时候一样,我在使用 <http use-expressions=”true”> 的同时,将 access=”ROLE_USER” 这种写法也写了进来,结果可想而知,报了错!报错信息诸如:
org.apache.jasper.JasperException: java.lang.IllegalArgumentException: Failed to evaluate
expression 'ROLE_SUPERVISOR'
就是说 use-expressions 这种表示法是不认识 access=”ROLE_USER” 这种写法的,另外,当使用了 <http use-expressions=”true”> 时,一定要在配置中至少有一个符合 use-expressions 的表示法,否则就会报类似如下错误:
org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 0): Field or property
'ROLE_SUPERVISOR' cannot be found on object of type 'org.springframework.security.web.access.
expression.WebSecurityExpressionRoot'
<!-- 认证管理 -->
< security:authentication-manager alias = "auther_manager" >
< security:authentication-provider >
<!-- 进行密码的加密操作 -->
< security:password-encoder hash = "md5" />
< security:user-service >
< security:user name = "admin" password = "21232f297a57a5a743894a0e4a801fc3" authorities = "ROLE_UESR" /> <!-- 通过用户名 , 设定角色信息 -->
</ security:user-service >
</ security:authentication-provider >
</ security:authentication-manager >
<!-- 添加提示信息的显示操作 -->
< bean id = "messageSource" class = "org.springframework.context.support.ReloadableResourceBundleMessageSource" >
< property
name = "basename" value = "classpath:message_zh_CN" >
</ property >
</ bean >
<!-- 登录的错误的信息 , 会作为异常存放在 sesssion 中 ,key : SPRING_SECURITY_LAST_EXCEPTION --> 、
5. 在这个例子中
< security:user name = "admin" password = "21232f297a57a5a743894a0e4a801fc3" authorities = "ROLE_UESR" /> security:user 卷标中的 password 属性,对密码的信息进行了加密 --MD5 加密
在登录页面中输入密码后,安全框架自动对密码进行 MD5 加密,然后在与配置文件中的密码进行比较
那么如何生成一个字符串的 MD5 码呢
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import sun.security.provider.MD5;
public class Test {
public static void main(String[] args) {
Md5PasswordEncoder encoder= new Md5PasswordEncoder();
String s = encoder.encodePassword( "xxx" , null );
System. out .println(s);
}
}
6. 创建 index.jsp 檔,用来测试一下 Spring 的安全框架是否配置成功
<%@ page language = "java" import = "java.util.*" pageEncoding = "gbk" %>
<! DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
< html >
< head >
</ head >
< body >
这是首页 , 欢迎你 !!!!
< br >
经历 Spring Security 框架后 ,Session 的属性
< br >
${sessionScope.SPRING_SECURITY_LAST_USERNAME}
${sessionScope.SPRING_SECURITY_CONTEXT}
</ body >
</ html >
当有请求到来的时候, Spring Security 框架开始查找要访问的资源是否存在访问的权利
要访问 index.jsp 页面时,自动产生一个登录的页面,如果登录成功,也就是表示具有访问的权利,就可以连结到 index.jsp 页面了
7. 由自己来编写 login.jsp 登录页面,不使用安全框架所生成的登录页面
在浏览器中进入登录页面后,右键选择 ” 查看源文件 ” ,可以看到由安全框架所生成的登录页面的一些信息,例如 form 中 action 的配置, input 中 name 的写法等
< html >
< head >
</ head >
< body >
< h3 > 用户的登录 </ h3 >
< font color = red >
</ font >< br >
< form action = "/SS/j_spring_security_check" method = "post" >
用户名 : < input type = "text" name = "j_username" >< br >
密码 : < input type = "password" name = "j_password" >< br >
< input type = "submit" value = " 登录 " >
</ form >
</ body >
</ html >
action="/SS/j_spring_security_check"
name="j_username"
这些信息都有由安全框架规定的格式,所以我们就可以依靠此配置信息来创建基于安全框架的自己的登录页面
在 Spring 的配置文件中配置有关 login.jsp 登录页面的内容
<!-- 安全级别的设定 -->
< security:http auto-config = "true" >
< security:form-login login-page = "/login.jsp" />
<!-- 配置如果没有权限 , 定位登录页面 -->
< security:intercept-url pattern = "/login.jsp" filters = "none" />
<!-- 将登录的页面 , 设置成为非过滤状态 -->
< security:intercept-url pattern = "/**" access = "ROLE_UESR" />
<!-- 要求登录的用户必须拥有某个角色 -->
</ security:http >
创建 login.jsp
<%@ page language = "java" import = "java.util.*" pageEncoding = "gbk" %>
< html >
< head >
</ head >
< body >
< h3 > 用户的登录 </ h3 >
< font color = red >
${sessionScope.SPRING_SECURITY_LAST_EXCEPTION.message} </ font >< br >
< form action = "${pageContext.request.contextPath } /j_spring_security_check " method = "post" >
用户名 : < input type = "text" name = "j_username" >< br >
密码 : < input type = "password" name = "j_password" >< br >
< input type = "submit" value = " 登录 " >
</ form >
</ body >
</ html >
再次进行测试,但是会发现一个新的问题
如果输入的内容不正确,不会产生任何提示,那么如何显示错误信息呢 ?
这样就需要使用安全框架所提供的 ” 国际化输出 ” 功能来实现
安全框架中把所有的错误都绑定为了一个异常,而且提供了一个国际化文件,该文件的位置在
spring-security-core-3.0.2.RELEASE/org/springframework/security/ 路径下
会有很多不同语言版本的资源文件
其中 messages_zh_CN.properties 就是中文的资源文件
找到该档后,继续对 Spring 的配置文件添加国际化输出的内容
<!-- 添加提示信息的显示操作 -->
< bean id = "messageSource" class = "org.springframework.context.support.ReloadableResourceBundleMessageSource" >
< property name = "basename" value = "classpath:message_zh_CN" >
</ property >
< property name = "basename" value = "classpath:org.springframework.security/message_zh_CN" >
</ property >
</ bean >
<!-- 登录的错误的信息 , 会作为异常存放在 sesssion 中 ,key : SPRING_SECURITY_LAST_EXCEPTION -->
在登录页面中来获取该国际化输出信息,用一个表达式来封装,如果登录不成功就提示出来
< font color = red >
${sessionScope.SPRING_SECURITY_LAST_EXCEPTION.message}
</ font >< br >
但是又会有一个问题出现,如果用户名或密码错误,提示的信息是 ” 坏的凭证 ” ,那么用户有如何能看得懂呢 ?
可以自己重写编写一个配置文件,但是 key 必须与原先的资源文件中使用的 key 对应起来,且必须明白其 key 的含义,最后将 key 与自己编写的中文的 value 对应起来,放入新的档中
再 Spring 的配置文件中,修改配置文件的路径,例如:
message_zh_CN.properties 文件
AbstractUserDetailsAuthenticationProvider.badCredentials= /u60A8/u65E0/u6743/u8BBF/u95EE/u8BE5/u8D44/u6E90,/u8BF7/u767B/u5F55
将该文件放置在 src 目录下,配置其路径为:
< property name = "basename" value = "classpath:message_zh_CN" ></ property >
8. 将配置文件中配置的用户名和密码等信息保存在数据库中,以便实现对数据库的操作
创建数据表 , 添加资料
创建数据库表时要注意, users 表和 authorities 表 是安全框架中自带的用户表和权限表,表中的字段名一定不能自定义,要与安全框架中指定的名称相同,否则会出现不匹配的问题
/* 用户表 */
create table users(
username varchar(50) primary key,
password varchar(50) not null ,
enable` tinyint(1) not null ,
);
/* 权限表 */
create table authorities(
username varchar(50) not null ,
authority varchar(50) not null ,
constraint fk_author_users foreign key (username) references users(username)
);
/* 创建索引 */
create unique index ix_auth_username on authorities(username,authority)
/* 插入相关的资料 */
insert into users(username,password,enable) values( 'admin' , '21232f297a57a5a743894a0e4a801fc3' , 1 );
insert into users(username,password,enable) values( 'user' , 'ee11cbb19052e40b07aac0ca060c23ee' , 1 );
insert into authorities values( 'admin' , 'ROLE_ADMIN' );
insert into authorities values( 'user' , 'ROLE_USER' );
在 Spring 的配置文件中 , 创建数据源 ( 注意建表时字段的名子 )
<!-- 创建数据源 -->
< bean id = "dataSource" class = "org.springframework.jdbc.datasource.DriverManagerDataSource >
<!-- 更换成为 Spring 的数据源管理 -->
< property name = "driverClassName" value = "com.mysql.jdbc.Driver" />
< property name = "url" value = "jdbc:mysql://localhost:3306/test" />
< property name = "username" value = "root" ></ property >
< property name = "password" value = "1234" ></ property >
</ bean >
修改 Spring 的配置文件,去掉 ” 认证管理 ” 部分,添加 ” 新的认证管理 ”
存储数据是 , 注意是否使用 MD5 的加密操作 , 配合 Spring 的认证权限的设置
<!-- 新的认证管理 ( 新的数据库的验证 ) -->
< security:authentication-manager alias = "manager" >
< security:authentication-provider >
< security:password-encoder hash = "md5" />
< security:jdbc-user-service data-source-ref = "dataSource" />
</ security:authentication-provider >
</ security:authentication-manager >
修改 index.jsp 页面的内容
<%@ page language = "java" import = "java.util.*" pageEncoding = "gbk" %>
<! DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" >
< html >
< head >
</ head >
< body >
这是首页 , 欢迎你 !!!!
< br >
< a href = "admin.jsp" > 进入 admin.jsp 页面 </ a >
</ body >
</ html >
添加一个 admin.jsp 页面
<%@ page language = "java" import = "java.util.*" pageEncoding = "gbk" %>
< html >
< head >
</ head >
< body >
this is Admin page
</ body >
</ html >
修改 Spring 的配置文件,重新设定安全级别,当用户通过登录进入 index.jsp 后,只有 ROLE_ADMIN 权限的用户可以进入 admin.jsp, 其他权限均不可以
<!-- 安全级别的设定 -->
< security:http auto-config = "true" >
<!-- 配置如果没有权限 , 定位登录页面 -->
< security:form-login login-page = "/login.jsp" />
<!-- 将登录的页面 , 设置成为非过滤状态 -->
< security:intercept-url pattern = "/login.jsp" filters = "none" />
<!-- 该页面不进行权限的验证 -->
< security:intercept-url pattern = "/admin.jsp" access = "ROLE_ADMIN" />
<!-- 必须是 admin 用户才能访问 -->
< security:intercept-url pattern = "/**" access = "ROLE_USER" />
<!-- 过滤所有的请求 -->
</ security:http >
所以按照上述的过程配置后,运行会出错
这是由于没有配置首页,也就是 index.jsp 对访问者的权限操作,重新修改配置文件中的安全级别的设定
<!-- 安全级别的设定 -->
< security:http auto-config = "true" >
<!-- 配置如果没有权限 , 定位登录页面 -->
< security:form-login login-page = "/login.jsp" />
<!-- 将登录的页面 , 设置成为非过滤状态 -->
< security:intercept-url pattern = "/login.jsp" filters = "none" />
<!-- 该页面不进行权限的验证 -->
< security:intercept-url pattern = "/admin.jsp" access = "ROLE_ADMIN" />
<!-- 必须是 admin 用户才能访问 -->
< security:intercept-url pattern = "/index.jsp" access = "ROLE_ADMIN,ROLE_USER" />
<!-- 首页访问的权限 -->
< security:intercept-url pattern = "/**" access = "ROLE_USER" />
<!-- 过滤所有的请求 -->
</ security:http >
运行程序后,如果该用户没有权限访问 admin.jsp 页面,就会报一个异常:
HTTP Status 403 – Access is denied
那么我们可以自己定义一个 accessAdmin.jsp 错误页面来提示使用者没有权限登录
<%@ page language = "java" import = "java.util.*" pageEncoding = "gbk" %>
< html >
< head >
</ head >
< body >
您的访问权限不足,请更换身份登录
</ body >
</ html >
在 Spring 的配置文件中,要配置该错误页面
修改安全级别的设定
< security:http
auto-config = "true" access-denied-page = "/accessAdmin.jsp" >
……
</ security:http >
access-denied-page="/accessAdmin.jsp" 表示如果用户访问该接口不具备相应的权限,按么就会跳转到 access-denied-page 属性所 指定的页面中
9. 在页面中获取登录用户的信息
有两种方式:使用 Shared 组件和使用卷标的方式
使用 Shared 组件
<%@ page language = "java" import = "java.util.*" pageEncoding = "gbk" %>
<%@ page import = "org.springframework.security.core.context.SecurityContext" %>
<%@ page import = "org.springframework.security.core.context.SecurityContextHolder" %>
<%@ page import = "org.springframework.security.core.Authentication" %>
<%@ page import = "org.springframework.security.core.userdetails.UserDetails" %>
< html >
< head >
</ head >
< body >
This is admin page. < br >
<%
SecurityContext secContext=SecurityContextHolder.getContext(); // 获取 SpringSecurity 的上下文
Authentication auth=secContext.getAuthentication();
// 获取认证的对象
Object p=auth.getPrincipal();
// 获取主体物件
String username= "" ;
if (p instanceof UserDetails){ //UserDetails 这个类是属于权限认证内容的,是 Spring 安全框架给提供的
username=((UserDetails)p).getUsername();
} else {
username=p.toString();
}
out.print(username);
%>
</ body >
</ html >
使用标签的方式
<%@ page language = "java" import = "java.util.*" pageEncoding = "gbk" %>
<%@ taglib uri = "http://www.springframework.org/security/tags" prefix = "sec" %>
< html >
< head >
</ head >
< body >
This is admin page. < br >
欢迎 : < sec:authentication property = "name" ></ sec:authentication >
</ body >
</ html >
10. 根据使用者权限的不同,让不同权限的使用者所看到的页面信息也不同
例如在这个例子中, ROLE_ADMIN 权限的用户登录后,可以点击 ” 进入 admin.jsp 页面 ” 这个连接进入到 admin.jsp 页面中,而 ROLE_USER 权限的用户就不具备此功能,所以就不能让此权限的用户登录后看到 ” 进入 admin.jsp 页面 ” 这个超链接
所以要使用 AOP 面向切面的技术,实现对方法的拦截控制
先使用 Spring 安全框架所提供的卷标来实现此功能,主要用于不同的权限建造不同组件的时候,大多数使用这个,可以直接在页面中配置,我们来修改 admin.jsp 文件
<%@ page language = "java" import = "java.util.*" pageEncoding = "gbk" %>
<%@ taglib uri = "http://www.springframework.org/security/tags" prefix = "sec" %>
<! DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" >
< html >
< head >
</ head >
< body >
这是首页 , 欢迎你 !!!!
< br >
< sec:authorize ifAllGranted = "ROLE_ADMIN" >
< a href = "admin.jsp" > 进入 admin.jsp 页面 </ a >
</ sec:authorize >
<!-- 表示只有该权限的用户登录后,才能在页面中显示卷标对中所包含的内容 -->
</ body >
</ html >
11. Spring 安全框架的 web 架构是完全基于 Servlet 的过滤器,其中安全的过滤器链可以管理 request 和 response ,还可以制作一批次的安全组件
<!-- 配置 Spring 过滤器链 -->
< security:filter-chain-map path-type = "ant" >
< security:filter-chain pattern = "/webServices/**" filters = "
securityContextPersistenceFilterWithASFalse,
basicAuthenticationFilter,
" />
</ security:filter-chain-map >
Pattern 属性表示要对什么作为请求, filters 来配置一些标准型过滤器
12. AOP 技术的使用,方法权限的控制
抽象类的使用
在实际的类中, pig 继承类 animal
Timer 任务调度器
每月前 10 天每隔 15 分钟触发一次
Spring 的事务配置
Spring 配置文件中关于事务配置总是由三个组成部分,分别是 DataSource 、 TransactionManager 和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分。 DataSource 、TransactionManager 这两部分只是会根据数据访问方式有所变化,比如使用Hibernate 进行数据访问 时,DataSource 实际为SessionFactory ,TransactionManager 的实现为 HibernateTransactionManager 。 具体如下图: 根据代理机制的不同,总结了五种Spring 事务的配置方式,配置文件如下: 第一种方式:每个Bean 都有一个代理 <? xml version="1.0" encoding="UTF-8" ?> 第二种方式:所有Bean 共享一个代理基类 <? xml version="1.0" encoding="UTF-8" ?> 第三种方式:使用拦截器 <? xml version="1.0" encoding="UTF-8" ?> 第四种方式:使用tx 卷标配置的拦截器 <? xml version="1.0" encoding="UTF-8" ?> 第五种方式:全注解 <? xml version="1.0" encoding="UTF-8" ?> 此时在DAO 上需加上@Transactional 注解,如下: package com.bluesky.spring .dao;
隔离级别 : 数据库提供了四种事务隔离级别, 不同的隔离级别采用不同的锁类开来实现. 在四种隔离级别中, Serializable 的级别最高, Read Uncommited 级别最低. 大多数数据库的默认隔离级别为: Read Commited, 如Sql Server , Oracle. 少数数据库默认的隔离级别为Repeatable Read, 如MySQL InnoDB 存储引擎 即使是最低的级别, 也不会出现 第一类 丢失 更新问题 .
丢失 更新 : 脏读: 一个事务读到另一个事务未提交的更新数据 例: 1.Mary 的原工资为 1000, 财务人员将 Mary 的工资改为了 8000( 但未提交事务 ) 不可重复读: 在同一个事务中, 多次读取同一数据, 返回的结果有所不同. 换句话说就是, 后续读取可以读到另一个事务已提交的更新数据. 相反" 可重复读" 在同一事务多次读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据. 例: 1. 在事务1 中,Mary 读取了自己的工资为1000, 操作并没有完成 幻读: 一个事务读取到另一个事务已提交的insert 数据. 例 : 第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部资料行。同时 ( 此时第一事务还未提交 ) ,第二个事务向表中插入一行新资料。这时第一个事务再去读取表时 , 发现表中还有没有修改的资料行,就好象发生了幻觉一样。 |