框架概述
所谓的框架其实就是程序的架子,在这个程序的架子中,搭建起程序的基本的骨架,针对程序的通用问题给出了便捷的解决方案,可以使开发人员 基于框架快速开发具体的应用程序。
常见的框架
-
SSH: Struts2 (Web层) / Spring (Service层) / Hibernate (DAO层)
-
SSM: SpringMVC (Web层) / Spring (Service层) / MyBatis (DAO层)
Spring框架概述
-
Spring是一个Service层的框架,可以整合许多其它框架进行工作。
-
Spring的主要技术是 IoC (DI) & AoP
- IoC (DI) — 控制反转 (依赖注入)
- AoP — 面向切面编程
Spring IoC
IoC (DI) — 控制反转 (依赖注入)
-
IoC称之为控制反转,简单来说就是将对象的创建的权利及对象的生命周期的管理过程交由Spring框架来处理
-
开发过程中不再需要关注对象的创建和生命周期的管理,而是在需要时由Spring框架提供,这个由spring框架管理对象创建和生命周期的机制称之为控制反转
-
通过IoC + 接口,可以在软件分层实践中,将耦合性提取到Spring容器中进行管理。这样就可以实现软件分层中“高内聚,低耦合”目标中的低耦合了
- 创建对象的过程中Spring可以依据配置对对象的属性进行设置,这个过称之为依赖注入,即DI。
入门案例演示:
-
创建项目:创建基本的java项目
-
导入相关的jar包 (6+1)
-
创建配置文件 — applicationContext.xml 放置到源码目录下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
在Idea中: 在src目录下右键 — >新建 — > XML Configuration File —> Spring Config
-
开发代码
创建一个类:
public class Person01 { public void eat(){ System.out.println("eat ... "); } public void say(){ System.out.println("say ... "); } }
配置bean:
<?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.xsd"> <!--配置bean--> <bean id="person01" class="cn.zss.domain.Person01"></bean> </beans>
在程序中通过Spring容器获取对象并使用:
public class Test01 { /* * 普通方法创建对象 * */ @Test public void test01(){ Person01 person01 = new Person01(); person01.say(); person01.eat(); } /* * Spring框架创建对象 * */ @Test public void test02(){ // 1. 初始化Spring容器 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2. 通过Spring容器获取对象 Person01 p = (Person01) context.getBean("person01"); // 3. 适用对象 p.eat(); p.say(); // 4.关闭容器 ((ClassPathXmlApplicationContext) context).close(); } }
IoC的实现原理
- 初始化Spring容器,加载指定的配置文件,基于配置文件初始化Spring容器
- 当解析到配置文件中的<bean>标签时,Spring容器根据该标签中的class属性指定的类的全路径名,通过反射创建该类的对象,并将该对象存入内置的Map中管理。其中键就是该标签的id值,值就是该对象。
- 当通过
getBean
方法来从容器中获取对象时,其实就是根据传入的条件在内置的Map中寻找是否有匹配的键值,如果有则将该键值对中保存的对象返回,如果没有匹配到则抛出异常。
推论:
-
默认情况下,多次获取同一个id的bean,得到的将是同一个对象。
-
不可以配置多个id相同的bean
-
可以配置多个id不同但class相同的bean
/*
* SpringIOC推论1:
* 默认情况下,从Spring容器中多次获取同一个id的bean得到的同一个对象
* 原因在于:从Spring容器中获取bean的过程本质上是从容器内部map中找到指定键的值返回
* 无论获取多少次,得到的都是map中key对应的对象,自然是同一个对象
* */
/*
* SpringIOC推论2:
* 不可以在spring容器中配置多个id相同的bean
* 原因在于:向spring容器中注册的bean,本质上要以指定的id和反射创建的对象形成键值对存储到map中
* 而map的键是不允许重复的,所以配置多个id相同的bean无法同时存入内部map,因此会抛出异常
* */
@Test
public void test03(){
// 初始化容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取 bean
Person01 p1 = (Person01) context.getBean("person01");
// 第二次获取person01
Person01 p2 = (Person01) context.getBean("person01");
System.out.println(p1 == p2); // true
}
/*
* SpringIOC推论3:
* 可以在容器中配置多个id不同但是class相同的bean
* 原因在于:多个class相同的bean,分别反射创建对象后,使用自己的id存储到spring容器内的map中,因此可以在spring中配置多个class相同但是id不同的bean
* */
@Test
public void test04(){
// 初始化容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取 bean
Person01 p1 = (Person01) context.getBean("person01");
// 获取第二个bean
Person01 p2 = (Person01) context.getBean("person01x");
System.out.println(p1 == p2); // false
}
IoC获取对象的方式
通过context.getBean()方法获取bean时,可以通过如下两种方式获取:
-
传入id值
/* * Spring容器获取对象的方法1:通过id获取 * 如果找不到:抛出异常[NoSuchBeanDefinitionException] * 如果找到唯一的一个:返回找到的对象 * 如果找到多个 (推论2):容器初始化时,抛出异常[BeanDefinitionParsingException] * */ @Test public void test01(){ // 1. 初始化Spring容器 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2. 通过容器获取bean Person01 p1 = (Person01) context.getBean("person01"); // 3. 使用bean p1.eat(); p1.say(); // 4. 关闭spring容器 ((ClassPathXmlApplicationContext) context).close(); }
-
传入class类型
<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.xsd"> <!-- 配置多个id不同但是class相同的bean--> <bean id="person01" class="cn.zss.domain.Person01"></bean> <bean id="person02" class="cn.zss.domain.Person01"></bean> </beans>
/* * Spring容器获取对象的方法2:通过class获取 * 如果找不到:抛出异常[NoSuchBeanDefinitionException] * 如果找到唯一的一个:返回找到的对象 * 如果找到多个:在获取bean时,抛出异常[NoUniqueBeanDefinitionException] * */ @Test public void test02(){ // 1. 初始化Spring容器 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2. 通过容器获取bean Person01 p1 = context.getBean(Person01.class); // 3. 使用bean p1.say(); p1.eat(); // 4. 关闭spring容器 ((ClassPathXmlApplicationContext) context).close(); }
-
通过class方式获取bean时,如果同一个类配置过多个bean,则在获取时因为无法确定到底要获取哪个bean会抛出异常。
-
SpringIOC在通过class获取bean时,如果找不到该类型的bean还会去检查是否存在该类型的子孙类型的bean,如果有则返回,如果找不到或找到多个则抛出异常。这符合java面向对象思想中的多态的特性。
别名标签
在 Spring中提供了别名标签<alias>可以为配置的<bean>起一个别名,要注意的是这仅仅是对指定的<bean>起的一个额外的名字,并不会额外的创建对象存入map
<?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.xsd">
<bean id="person01" class="cn.zss.domain.Person01"></bean>
<!-- 起别名-->
<alias name="person01" alias="per01"></alias>
</beans>
public class Test01 {
@Test
public void test01(){
// 1. 初始化Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 通过容器获取bean
Person01 p1 = (Person01) context.getBean("person01");
// 通过别名获取bean
Person01 p2 = (Person01) context.getBean("per01");
// 3. 使用bean
System.out.println(p1 == p2); // true
// 4. 关闭spring容器
((ClassPathXmlApplicationContext) context).close();
}
}
Spring创建对象的方式
通过类的无参构造方法创建对象
-
当用最普通方式配置一个
<bean>
时,默认就是采用类的无参构造创建对象。 -
在Spring容器初始化时,通过
<bean>
上配置的class属性反射得到字节码对象,通过newInstance()创建对象 -
这种方式下spring创建对象,要求类必须有无参的构造,否则无法通过反射创建对象,会抛出异常。
public class Person01 {
private int age;
Person01(int age){
this.age = age;
}
public void eat(){
System.out.println("eat ... ");
}
}
public class Test01 {
@Test
/*
* 普通方法创建对象
* */
public void test01(){
Person01 person01 = new Person01(5);
person01.eat();
}
/*
* 通过反射创建对象
* newInstance()方法本质上是在调用当前类的无参构造器创建对象
* 如果该类没有无参构造器,则此方法无法正常执行,会抛出异常
* */
@Test
public void test02() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class clz = Class.forName("cn.zss.domain.Person01");
// 前提:类必须有无参构造函数
Person01 p = (Person01) clz.newInstance();
p.eat();
}
/*
* 如果bean没有无参构造,spring无法通过默认的机制管理此bean
* */
@Test
public void test03() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Person01 p = (Person01)context.getBean("person01");
p.eat();
((ClassPathXmlApplicationContext)context).close();
}
}
通过静态工厂创建对象
工厂设计模式 (Factory Pattern)
- 通过工厂隐藏创建对象的细节,使用者不需要关注创建对象的细节,只要找到工厂调用对应的生产方法,就可以直接得到可用的对象
很多的时候,我们面对的类是无法通过无参构造去创建的,例如该类没有无参构造、是一抽象类等等情况,此时无法要求spring通过无参构造创建对象,此时可以使用静态工厂方式创建对象。
-
定义Person类
public class Person01 { private String name; private int age; private char gender; public Person01(String name, int age, char gender) { this.name = name; this.age = age; this.gender = gender; } }
-
定义Person类的静态工厂
/* * 静态工厂 * */ public class Person01StaticFactory { // 私有化构造方法,保证静态工厂类无法实例化 private Person01StaticFactory(){} // 提供公有静态方法用来生产目标对象 public static Person01 getInstance(){ return new Person01("lili",20,'女'); } }
-
编写配置文件
<!-- 静态工厂配置--> <bean id="person01" class="cn.zss.domain.Person01StaticFactory" factory-method="getInstance"></bean>
-
创建对象
public class Test01 { @Test public void test01(){ // 1. 初始化容器 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2. 获取bean Person01 p = (Person01) context.getBean("person01"); // 3. 使用bean System.out.println(p); // 4. 关闭容器 ((ClassPathXmlApplicationContext)context).close(); } }
实例工厂创建对象
实例工厂也可以解决类是无法通过无参构造创建的问题,解决的思路和静态工厂类似,只不过实例工厂提供的方法不是静态的。
spring需要先创建出实例工厂的对象,再调用实例工厂对象上指定的普通方法来创建对象。所以实例工厂也需要配置到Spring中管理。
-
定义Person类的实例工厂
public class Person01InstanceFactory { public Person01 getInstance(){ return new Person01("Tom",20,'男'); } }
-
编写配置文件
<!--实例工厂--> <bean id = "pif" class="cn.zss.domain.Person01InstanceFactory"></bean> <bean id = "person01" factory-bean="pif" factory-method="getInstance"></bean>
-
创建对象
@Test public void test02(){ // 1. 初始化容器 ApplicationContext context = new ClassPathXmlApplicationContext("application2Context.xml"); // 2. 获取bean Person01 p = (Person01) context.getBean("person01"); // 3. 使用bean System.out.println(p); // 4. 关闭容器 ((ClassPathXmlApplicationContext)context).close(); }
Spring工厂创建对象
-
定义Person类的Spring工厂
/*Spring工厂*/ public class Person01SpringFactory implements FactoryBean<Person01> { /* * 生产对象的方法 * */ @Override public Person01 getObject() throws Exception { return new Person01("Potter",20,'男'); } /* * 获取当前工厂生产的对象类型的方法 * */ @Override public Class<?> getObjectType() { return Person01.class; } /* * 当前对象是否是单例对象 * */ @Override public boolean isSingleton() { return true; } }
-
编写配置文件
<!--Spring工厂--> <bean id = "person01" class="cn.zss.domain.Person01SpringFactory"></bean>
-
创建对象
@Test public void test02(){ // 1. 初始化容器 ApplicationContext context = new ClassPathXmlApplicationContext("application3Context.xml"); // 2. 获取bean Person01 p = (Person01) context.getBean("person01"); // 3. 使用bean System.out.println(p); // 4. 关闭容器 ((ClassPathXmlApplicationContext)context).close(); }
单例和多例
-
Spring容器管理的bean在默认情况下是单例的 (常用),即一个bean只会创建一个对象,存在内置 map中,之后无论获取多少次该bean,都返回同一个对象。
-
Spring默认采用单例方式,减少了对象的创建,从而减少了内存的消耗。
-
在实际开发中是存在多例的需求的,Spring也提供了选项可以将bean设置为多例模式。
配置单例模式 / 多例模式:
<?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.xsd">
<!--配置bean-->
<!--
scope属性控制当前bean的创建模式:
singleton:则当前bean处在单例模式中,默认就是此模式
prototype:则当前bean处在多例模式中
-->
<bean id="cart" class="cn.zss.domain.Cart" scope="prototype"></bean>
</beans>
bean在单例模式下的生命周期:
- bean在单例模式下,spring容器启动时解析xml发现该bean标签后,直接创建该bean的对象存入内部map中保存
- 之后无论获取多少次返回的都是同一个bean
- 直到容器销毁,spring关闭,内部的bean跟着被销毁
bean在多例模式下的生命周期:
- bean在多例模式下,spring容器启动时解析xml发现该bean标签后,只是将该bean进行管理,并不会创建对象
- 此后每次使用 getBean()获取该bean时,spring都会重新创建该对象返回,每次都是一个新的对象
- 这个对象spring容器并不会持有,什么销毁取决于使用该对象的用户自己什么时候销毁该对象 (即使关闭容器,对象也可以继续使用)。
懒加载机制
-
Spring默认会在容器初始化的过程中,解析xml,并将单例的bean创建并保存到map中
-
这样的机制在bean比较少时问题不大,但一旦bean非常多时,spring需要在启动的过程中花费大量的时间来创建bean,花费大量的空间存储bean,但这些bean可能很久都用不上,在时间和空间上的浪费显得非常的不值得。
-
所以Spring提供了懒加载机制。所谓的懒加载机制就是可以规定指定的bean不在启动时立即创建,而是在后续第一次用到时才创建,从而减轻在启动过程中对时间和内存的消耗。
-
懒加载机制只对单例bean有作用,对于多例bean设置懒加载没有意义。
懒加载的配置方式:
<?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.xsd"
default-lazy-init="true"><!--配置父标签默认值 (为全局配置懒加载)-->
<!--配置bean-->
<!-- 配置懒加载
lazy-init = "default" 使用父标签中配置的默认值
lazy-init = "true" 开启懒加载
lazy-init = "false" 关闭懒加载
** 多例模式里设置懒加载毫无意义
-->
<!--为指定bean配置懒加载-->
<bean id="person" class="cn.zss.domain.Person" lazy-init="true"></bean>
</beans>
如果同时设定全局和指定bean的懒加载机制,且配置不相同,则对于该bean局部配置覆盖全局配置。
配置初始化和销毁的方法
在Spring中如果某个bean在初始化之后或销毁之前需要做一些额外操作可以为该bean配置初始化和销毁的方法,在这些方法中完成要功能。
配置方式 (示例):
-
定义类
public class JDBCUtils { /* * 初始化方法 * */ public void init(){ System.out.println("连接数据库"); } /* * 销毁方法 * */ public void destroy(){ System.out.println("销毁数据库连接"); } /* * 普通方法 * */ public void getConn(){ System.out.println("获取连接"); } }
-
编写配置文件
<?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.xsd"> <bean id="jdbcUtils" class="cn.zss.domain.JDBCUtils" init-method="init" destroy-method="destroy"> </bean> </beans>
-
测试
public class Test01 { /* * 初始化,销毁方法测试 * */ @Test public void test01(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); JDBCUtils jdbcUtils = (JDBCUtils) context.getBean("jdbcUtils"); jdbcUtils.getConn(); /* * 打印结果: * 连接数据库 获取连接 销毁数据库连接 * */ ((ClassPathXmlApplicationContext)context).close(); } }
Spring中关键方法的执行顺序:
- 在Spring创建bean对象时,先创建对象 (通过无参构造或工厂)
- 接着调用init方法来执行初始化操作
- 之后此bean就可以调用其它普通方法
- 而在对象销毁之前,spring容器调用其destory方法来执行销毁操作。
Spring DI
创建对象的过程中Spring可以依据配置对对象的属性进行设置,这个过称之为依赖注入,即DI
基于Set方法的注入
通常的javabean属性都会私有化,而对外暴露Getter和Setter方法,此时spring可以通过Setter方法将属性的值注入对象。
-
普通属性注入:
a. 定义类
public class Hero { private String name; private int age; private List<String> job; private Set<String> set; private Map<String, String> map; private Properties props; public Hero() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public List<String> getJob() { return job; } public void setJob(List<String> job) { this.job = job; } public Set<String> getSet() { return set; } public void setSet(Set<String> set) { this.set = set; } public Map<String, String> getMap() { return map; } public void setMap(Map<String, String> map) { this.map = map; } public Properties getProps() { return props; } public void setProps(Properties props) { this.props = props; } @Override public String toString() { return "Hero{" + "name='" + name + '\'' + ", age=" + age + ", job=" + job + ", set=" + set + ", map=" + map + ", props=" + props + '}'; } }
b. 编写配置文件
<?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.xsd"> <!--配置bean--> <bean id="hero" class="cn.zss.domain.Hero"> <!--配置的是bean属性的名称(由Set方法决定),而不是类属性--> <!--基本属性配置--> <property name="name" value="狄仁杰"> </property> <property name="age" value="18"></property> <!--集合属性配置--> <!--job属性配置--> <property name="job"> <list> <value>神探</value> <value>官员</value> </list> </property> <!--set属性配置--> <property name="set"> <set> <value>s1</value> <value>s2</value> </set> </property> <!--map属性配置--> <property name="map"> <map> <entry key="k1" value="v1"></entry> <entry key="k2" value="v2"></entry> <!--重复的键,保存最后的键值对--> <entry key="k2" value="v4"></entry> </map> </property> <!--properties属性配置--> <property name="props"> <props> <prop key="p1">v1</prop> <prop key="p2">v2</prop> </props> </property> </bean> </beans>
-
自定义bean的注入:
a. 自定义类
public class Enemy { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Enemy{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
b. 在Hero类中定义属性并提供get和set方法
public class Hero { private Enemy enemy; // ... 其他属性 public Enemy getEnemy() { return enemy; } public void setEnemy(Enemy enemy) { this.enemy = enemy; } // ... 其他get和set方法 @Override public String toString() { return "enemy=" + enemy; } }
c. 编写配置文件
<?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.xsd"> <!--配置bean--> <bean id="hero" class="cn.zss.domain.Hero"> <!--自定义bean注入--> <!--通过ref指定配置的bean的id--> <property name="enemy" ref="enemy"> </property> </bean> <bean id = "enemy" class="cn.zss.domain.Enemy"> <property name="name" value="袁天罡"> </property> <property name="age" value="20"> </property> </bean> </beans>
-
自动装配
为指定<bean>开启自动装配:
?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.xsd"> <!--配置bean--> <!--自动装配: autowire="byName" 根据当前要注入的自定义bean属性名称找到对应id的bean,找到就注入,找不到不注入 autowire="byType": 根据当前要注入的自定义bean的Class类型找到对应Class的bean,找到唯一的就注入,找不到不注入,找到多个抛出异常 --> <bean id="hero" class="cn.zss.domain.Hero" autowire="byName"> <bean id = "enemy" class="cn.zss.domain.Enemy"> <property name="name" value="袁天罡"> </property> <property name="age" value="20"> </property> </bean> </beans>
基于构造方法的注入
对象属性设置的另一种方式是在对象创建的过程中通过构造方法传入并设置对象的属性;Spring也可以通过这样的构造方法实现属性的注入
a. 定义类
public class Hero {
String name;
private int age;
private Enemy enemy;
public Hero() {
}
public Hero(String name, int age, Enemy enemy) {
this.name = name;
this.age = age;
this.enemy = enemy;
}
@Override
public String toString() {
return "Hero{" +
"name='" + name + '\'' +
", age=" + age +
", enemy=" + enemy +
'}';
}
}
public class Enemy {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Enemy{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
b. 编写配置文件
<?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.xsd">
<!--配置bean-->
<bean id="hero" class="cn.zss.domain.Hero" >
<!--
index: 对第几个参数设置
name: 对哪个名称的参数设置
type: 对哪个类型的参数设置
前三个参数必选一个
value: 要设置什么值(直接赋值)
ref: 要设置什么值 (引用赋值)
后两个参数根据类型必选一个
-->
<constructor-arg index="0" value ="狄仁杰" type="java.lang.String"></constructor-arg>
<constructor-arg index="1" value ="45" type="int"></constructor-arg>
<constructor-arg index="2" type="cn.zss.domain.Enemy" ref="enemy"></constructor-arg>
</bean>
<bean id ="enemy" class="cn.zss.domain.Enemy">
<property name="name" value="袁天罡"></property>
<property name="age" value="66"></property>
</bean>
</beans>
c. 编写测试类
ublic class Test01 {
@Test
public void test01(){
// 初始化容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取bean
Hero hero = (Hero)context.getBean("hero");
// 使用bean
System.out.println(hero);
// 销毁bean
((ClassPathXmlApplicationContext)context).close();
}
}
基于注解方式配置Spring IoC
注解
-
注释:java中注释的格式://, /**/, /** */
-
注解:
- sun在jdk5.0开始提供的新特性
- 给程序看的提示信息,程序看后可以根据有无注解及注解上属性的不同配置执行不同的逻辑。
- java中的注解的格式:@AnnoName(key=value,…)
-
注解在开发中,可以作为轻量化配置来使用,比起使用xml作为配置文件,更加的轻便易用,在java开发中大量的使用。
java 内置注解
-
@Override — 声明重写父类方法的注解,要求编译器帮我们检查是否成功的覆盖,如果没有成功覆盖父类方法,编译器将会进行报错提示。
-
@Deprecated — 声明方法被过时,不再建议使用,要求编译器在编译的过程中对于这样的方法的调用提出警告,提示方法过时。
-
@SuppressWarnings — 压制警告,提示编译器,在编译的过程中对指定类型的警告不再提示
注解可以基于有注解 / 没注解或者注解属性的不同来控制程序按照不同方式运行
注解常用作轻量化配置,替代配置文件的部分功能
注解方便开发阶段的配置和更改,配置文件在发布后仍可以方便修改,因此实际开发中注解和配置文件需要结合使用
自定义注解
开发一个注解类
- 开发一个注解类的过程,非常类似于开发一个接口,只不过需要通过@interface关键字来声明
使用元注解修饰注解的声明
- 所谓的元注解是用来修饰注解声明的注解,可以控制被修饰的注解的特性。
-
@Target:用来声明被修饰的注解可以用在什么位置。
可以在@Target的属性中设置ElementType类型的数组来指定可以使用的位置。如果不用此元注解修饰,默认注解可以用在任意位置。
@Target({ElementType.FIELD, ElementType.METHOD}) public @interface FirstAnno { // 自定义注解可以应用于属性和方法上 }
-
@Retention:用来声明被修饰的注解会被保留到什么阶段。
java —> 编译 —> .class —> 类加载器 —> 字节码
可以在该注解的属性中通过RetentionPolicy类型的值来指定注解被保留到何时。
-
RetentionPolicy.SOURCE:此注解将会被保留到源码阶段,.java中;在编译过程中被删除。这种类型的注解通常是给编译器看的。
-
RetentionPolicy.CLASS:此注解将会被保留在源码阶段和编译阶段,.java和.class中;在类加载的过程中被删除。这种类型的注解通常是给类加载器看的。
-
RetentionPolicy.RUNTIME:此注解将会被保留在源码阶段,编译阶段和运行阶段,.java .class和内存中的字节码中都会存在。这种类型的注解通常用来在运行阶段进行反射,控制程序运行过程。
-
只有RUNTIME级别的注解才可以通过反射技术进行反射。
-
-
@Documented :用来声明被修饰注解是否要被文档提取工具提取到文档中;javadoc命令可以用于生成文档
默认不提取。
-
@Inherited:用来声明被修饰的注解是否具有继承性
默认没有继承性
为注解增加属性
- 注解类中还可以声明属性。
- 为注解类声明属性的过程非常类似于为接口定义方法。
- 但要求,注解中的所有的属性必须是public的,可以显式声明,也可以不声明,不声明默认就是public的。
- 注解中的属性只能是八种基本数据类型,String类型,Class类型,枚举类型,其他注解类型,及以上类型的一维数组。
- 一旦声明了属性,在注解使用的过程中就必须为属性赋值,为其赋值的方式就是使用注解时,在注解后通过
(属性名=属性值)
的方式 指定属性的值 - 也可以在声明注解时 在注解的属性后 通过default关键字,声明属性的默认值,声明过默认值的属性默认采用默认值,也可以手动赋值,覆盖默认值
- 如果属性是 一维数组类型而在传入的数组中只有一个值,则包括数组的大括号可以省略
- 如果注解的属性只有一个需要赋值,且该属性的名称叫做value,则在使用注解时,value=“xxx” 中的 ‘value=’ 可以不写
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FirstAnno {
public String name();
public int age() default 0;
String[] addrs();
}
public class Person {
String name;
int age;
@FirstAnno(name="lili",age=18, addrs={"china","beijing"})
public void eat(String food1, String food2){
System.out.println("eat...");
}
}
反射注解
- 只有RUNNTIME级别的注解能够被反射
- Class Method Field Package… 都提供了获取注解信息的方法
返回值 | 方法 |
---|---|
Annotation[] | getAnnotations ():返回此元素上存在的所有注释。 |
A | getAnnotation (Class annotationClass): 如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。 |
boolean | isAnnotationPresent (Class<? extends Annotation> annotationClass) : 如果指定类型的注释存在于此元素上,则返回 true,否则返回 false |
Spring注解方式实现IoC & DI
基本实现流程:
a. 导入开发包
b. 编写配置文件,并导入context约束
<?xml version="1.0" encoding="UTF-8"?>
<!--
约束技术: 通过约束技术可以进行代码提示
xmlns: 用于声明名称空间的缩写,以缩写开头的标签受名称空间的管理
名称空间必须是唯一的
http://www.springframework.org/schema/beans/spring-beans.xsd: 为名称空间真正文件的地址
-->
<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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
</beans>
Idea可以将该配置文件设置为模板,方便以后使用
文件 —> 设置 —> 编辑器 —> 文件和代码模板 —> 点击左侧加号新建模板
c. 开启包扫描
在配置文件中,开启包扫描,指定Spring自动扫描哪些个包下的类;只有在指定的扫描包下的类上的IOC注解才会生效。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- 指定扫描哪些包-->
<!--Spring容器会在启动时自动扫描指定包极其子孙包-->
<context:component-scan base-package="cn.zss.domain"></context:component-scan>
</beans>
d. 使用注解注册bean
-
在配置的包中的类上使用@Component注解,则这个类会自动被注册为bean
-
使用当前类的class为的class,通常情况下使用类名首字母小写为的id
-
如果类名的第二个字母为大写则首字母保留原样,如类名: PErson —> id名: PErson
-
也可以通过在@Component中配置value属性,明确的指定bean的id
package cn.zss.domain;
import org.springframework.stereotype.Component;
@Component // <bean id="person" class="cn.zss.domain.Person"></bean>
/*@Component(value="per") 等价于 @Component("per")
<bean id="per" class="cn.zss.domain.Person"></bean>*/
public class Person {
}
e. 测试类
public class Test01 {
@Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Person p = (Person)context.getBean("person");
System.out.println(p);
((ClassPathXmlApplicationContext)context).close();
}
}
注解方式实现工厂注册bean
- Spring默认通过反射创建bean,如果某些bean没有无参构造器或创建过程非常复杂,则无法通过简单的反射创建bean
- 此时可以通过创建bean的工厂,令SpringIoC通过工厂来创建bean,从而进行注册。
- 可以通过配置文件方式配置bean工厂,同样也可以通过注解配置bean工厂。
配置工厂类
- 工厂类必须放在包扫描目录下,且被@Component注解修饰
配置工厂类中生产bean的方法
-
工厂中生产bean的方法要被@Bean修饰
-
则此方法会被SpringIOC调用,并将返回的对象注册为Spring的bean
-
默认自动推断id (根据方法名,不常用),在老版本中通过@Bean(name=“xxx”)指定id;在新版本中,通过value属性指定id。
案例演示:
a. 定义Dog类
package cn.zss.domain;
public class Dog {
public Dog (String name){
}
}
b. 定义Dog类的工厂类
package cn.zss.domain;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/*
* 实例工厂
* */
@Component
public class DogFactory {
// <bean id = "fogFactory" class="cn.zss.domain.DogFactory"></bean>
@Bean(name = "dog")
// <bean id = "dog" class="cn.zss.domain.Dog"></bean>
public Dog getInstance(){
return new Dog("Tom");
}
}
c. 测试
@Test
public void test02(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Dog dog = (Dog)context.getBean("dog");
System.out.println(dog);
((ClassPathXmlApplicationContext)context).close();
}
Spring注解方式实现DI
- 通过底层的反射机制实现 (可以突破私有的防控权限),既不依靠构造函数,也不依靠Set方法
a. 导入开发包
b. 编写配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!--
约束技术: 通过约束技术可以进行代码提示
xmlns: 用于声明名称空间的缩写,以缩写开头的标签受名称空间的管理
名称空间必须是唯一的
http://www.springframework.org/schema/beans/spring-beans.xsd: 为名称空间真正文件的地址
-->
<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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- 指定扫描哪些包-->
<!--Spring容器会在启动时自动扫描指定包极其子孙包-->
<context:component-scan base-package="cn.zss.domain"/>
<!--开启注解方式的DI-->
<context:annotation-config/>
</beans>
c. 注解方式注入spring内置支持的类型数据 - 非集合类型
- spring中可以通过@Value注解来实现spring内置支持的类型的属性的注入。
package cn.zss.domain;
@Component
public class Hero {
@Value("美国队长")
private String name;
@Value("40")
private int age;
private List<String> list;
private Set<String> set;
private Map<String, String> map;
private Properties props;
@Override
public String toString() {
return "Hero{" +
"name='" + name + '\'' +
", age=" + age +
", list=" + list +
", set=" + set +
", map=" + map +
", props=" + props +
'}';
}
}
- 这种方式可以实现Spring内置支持类型的注入,但是将注入的值写死在了代码中,后续如果希望改变注入的值,必须来修改源代码
- 可以将这些值配置到一个properties配置文件中,再在Spring中进行引入。
d. 编写properties配置文件(my.properties)并加载
myname = 美国队长
myage = 40
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- 指定扫描哪些包-->
<!--Spring容器会在启动时自动扫描指定包极其子孙包-->
<context:component-scan base-package="cn.zss.domain">
</context:component-scan>
<!--开启注解方式的DI-->
<context:annotation-config></context:annotation-config>
<!--加载属性文件-->
<context:property-placeholder location="my.properties" />
</beans>
e. 添加@Value注解
@Component
public class Hero {
@Value("${myname}")
private String name;
@Value("${myage}")
private int age;
private List<String> list;
private Set<String> set;
private Map<String, String> map;
private Properties props;
@Override
public String toString() {
return "Hero{" +
"name='" + name + '\'' +
", age=" + age +
", list=" + list +
", set=" + set +
", map=" + map +
", props=" + props +
'}';
}
}
f. 注解方式注入spring内置支持的类型数据
引入util名称空间,通过适当的util标签注册数据
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
">
<!-- 指定扫描哪些包-->
<!--Spring容器会在启动时自动扫描指定包极其子孙包-->
<context:component-scan base-package="cn.zss.domain">
</context:component-scan>
<!--开启注解方式的DI-->
<context:annotation-config>
</context:annotation-config>
<!--加载属性文件-->
<context:property-placeholder location="my.properties" />
<!--定义集合数据-->
<util:list id="l1">
<value>l1</value>
<value>l2</value>
</util:list>
<util:set id="s1">
<value>s1</value>
<value>s2</value>
</util:set>
<util:map id="m1">
<entry key="k1" value="v1"></entry>
<entry key="k2" value="v2"></entry>
</util:map>
<util:properties id ="p1">
<prop key="p1">v1</prop>
<prop key="p2">v2</prop>
</util:properties>
</beans>
再在类的属性中通过@Value注入赋值
@Component
public class Hero {
@Value("${myname}")
private String name;
@Value("${myage}")
private int age;
@Value("#{@l1}")
private List<String> list;
@Value("#{@s1}")
private Set<String> set;
@Value("#{@m1}")
private Map<String, String> map;
@Value("#{@p1}")
private Properties props;
@Override
public String toString() {
return "Hero{" +
"name='" + name + '\'' +
", age=" + age +
", list=" + list +
", set=" + set +
", map=" + map +
", props=" + props +
'}';
}
}
g. 使用注解注入自定义bean类型数据
在bean中的属性上通过@Autowired实现自定义bean类型的属性注入
-
当Spring容器解析到@Component注解时,创建当前类的bean在Spring容器中进行管理,在创建bean的过程中发现了@Autowired注解,会根据当前bean类型,寻找在Spring中是否存在该类型的bean,找到直接注入,如果找不到还会检查是否有子孙类、实现类存在,如果存在唯一的则自动注入,如果还是没有找到或找到多个无法注入,则还会按照属性名对应id去查找对应的bean,如果存在则注入,如果还是没有找到则抛出异常。
-
也可以额外配置@Qualifier(value=“xxx”)注解强制要求按照id寻找bean,则此时会直接使用给定的id寻找bean,而不会进行基于类型的匹配。
package cn.zss.domain;
@Component
public class Hero {
@Value("美国队长")
private String name;
@Value("100")
private int age;
/*首先按类型注入,
* 如果找到唯一就注入,
* 如果找不到或者找到多个,则开始按照id注入
* 寻找是否存在bean的id和当前属性的属性名相同,如果存在就注入,不存在就抛出异常
*/
@Autowired // 根据类型找到Enemy和newEnemy,然后根据id(enemy) 找到Enemy
// @Qualifier("newEnemy") --- > Hero{name='美国队长', age=100, enemy=Enemy{name='灭霸', age=150}}
// 一旦配置了@Qualifier,@Autowired的工作机制就会发生变化,直接按照指定的id进行注入,
// 如果找到唯一的就注入,找不到,或者找到多个直接抛出异常
private Enemy enemy;
@Override
public String toString() {
return "Hero{" +
"name='" + name + '\'' +
", age=" + age +
", enemy=" + enemy +
'}';
}
}
package cn.zss.domain;
@Component
public class Enemy {
@Value("九头蛇")
private String name;
@Value("100")
private int age;
@Override
public String toString() {
return "Enemy{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package cn.zss.domain;
@Component
public class NewEnemy extends Enemy{
@Value("灭霸")
private String name;
@Value("150")
private int age;
@Override
public String toString() {
return "Enemy{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
其他注解
a. @Scope(value=“prototype”):配置修饰的类的bean是单例还是多例,如果不配置默认为单例
@Component
@Scope("prototype")
public class Person {
}
b. @Lazy: 配置修饰的类的bean采用懒加载机制
@Component
@Lazy
public class Person02 {
public Person02(){
System.out.println("init...");
}
}
c. @PostConstruct: 在bean对应的类中,修饰某个方法,将该方法声明为初始化方法,对象创建之后立即执行
d. @PreDestroy:在bean对应的类中,修饰某个方法,将该方法声明为销毁的方法,对象销毁之前调用的方法
@Component
public class JDBCUtils {
/*
* 初始化方法
* */
@PostConstruct
public void initConn(){
System.out.println("连接数据库");
}
/*
* 销毁方法
* */
@PreDestroy
public void destroyConn(){
System.out.println("销毁数据库连接");
}
/*
* 普通方法
* */
public void insert(){
System.out.println("插入");
}
/*
* 普通方法
* */
public void delete(){
System.out.println("删除");
}
}
e. @Controller @Service @Repository @Component:
- 这四个注解的功能是完全相同的,都是用来修饰类,将类声明为Spring管理的bean的。
- 其中@Component一般认为是通用的注解
- 而@Controller用在软件分层中的控制层,一般用在Web层
- 而@Service用在软件分层中的业务访问层,一般用在Service层
- 而@Repository用在软件分层中的数据访问层,一般用在DAO层
利用Spring IOC&DI 实现软件分层解耦
在层与层之间设计接口 (面向接口编程) 并通过Spring注册对象
Web层:
package cn.zss.web;
@Controller
public class UserServlet {
@Autowired
private UserService userService = null;
public void regist(){
System.out.println("userServlet ... regist ...");
userService.regist();
}
}
Service层:
a. 接口UserService
package cn.zss.service;
public interface UserService {
public void regist();
}
b. 接口的实现类UserServiceImpl
package cn.zss.service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
@Qualifier("userDao")
private UserDao userDao = null;
@Override
public void regist() {
System.out.println("userService ... regist");
userDao.insert();
}
}
DAO层:
a. 接口
package cn.zss.dao;
public interface UserDao {
void insert();
}
b. 接口的第一个实现类
package cn.zss.dao;
import org.springframework.stereotype.Repository;
@Repository("userDao")
public class MySqlUserDao implements UserDao{
@Override
public void insert() {
System.out.println("Mysql ... insert ...");
}
}
c. 接口的第二个实现类
package cn.zss.dao;
import org.springframework.stereotype.Repository;
@Repository
public class OracleUserDao implements UserDao {
@Override
public void insert() {
System.out.println("Oracle ... insert ...");
}
}