一、Spring简述
1.1 了解Spring
- Spring是一个开源的框架
- Spring为了简化代码
- Spring是一个IoC(DI)和AOP容器框架
1.2 Spring的特性
- 非侵入性:使用了Spring这种技术对原来的技术不造成任何影响,原来的技术可以和Spring一起用;
- 依赖注入:DI ,反转控制(IoC)的一种实现;
- 面向切面编程:Aspect Oriented Programming——AOP,是为了对OOP(面向对象编程)的补充;
- 容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期;
- 组件化:把项目里的类,交给Spring管理。组件就是Spring所管理的对象,在Spring中可使用XML和Java注解组合这些对象。降低耦合度;
- 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的Spring JDBC)。
1.3 Spring的模块
Spring框架采用的是分层架构,它一系列的功能要素被分为20个模块。
二、搭建Spring运行环境
2.1 导入jar包
(1)Spring自身的jar包:
spring-beans-4.3.6.RELEASE.jar
spring-context-4.3.6.RELE2ASE.jar
spring-core-4.3.6.RELEASE.jar
spring-expression-4.3.6.RELEASE.jar
(2)导入记录日志的jar包 commons-logging-1.1.1.jar
2.2 配置xml文件
(1)如果使用eclipse
,则创建xml文件,拷贝如下代码:
注意:beans 里面的内容叫做命名空间
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
</beans>
(2)如果使用STS
,则直接
① File
->New
->Spring Bean Configuration File
② 为文件取名字 例如:applicationContext.xml
三、用Spring完成简单案例
3.1 创建Person类
package com.hkd.spring.mod;
public class Person {
private Integer id;
private String 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;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + "]";
}
}
3.2 配置并完善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">
<bean id="personOne" class="com.hkd.spring.mod.Person">
<property name="id" value="1001"></property>
<property name="name" value="小明"></property>
</bean>
<bean id="personTwo" class="com.hkd.spring.mod.Person">
<property name="id" value="1002"></property>
<property name="name" value="小红"></property>
</bean>
</beans>
3.3 main函数里,创建Person对象并赋值
代码提要:
- 初始化容器
- 通过
getBean()
方法获取对象
package com.hkd.spring.mod;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestBySpring {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = ac.getBean("personOne", Person.class);
System.out.println(person);
}
}
3.4.完成并查看结果
四、IoC容器和Bean的配置
4.1 IoC和DI
4.1.1 IoC——反转控制
- 把原来自己对对象的控制权,交给了程序本身(Spring)来管理
- 我们不关心对象是怎样创建的,我们只关心我们用程序能调用到对象即可
- 对象的创建都交给Spring去做
4.1.2 DI——依赖注入
- DI是IoC的另一种表述方式
- IoC 就是一种反转控制的思想, 而DI是对IoC的一种具体实现
- 依赖注入的过程就是给bean的属性赋值
4.1.3 IOC容器在Spring中的实现
- 在通过IoC容器读取Bean的实例之前,需要先将IOC容器本身实例化。
- Spring提供了IoC容器的两种实现方式【使用后者】:
① BeanFactory:IoC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的。
② ApplicationContext
:BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。
4.2 依赖注入的方式
4.2.1 set注入
在第三章Spring完成的简单案例中,applicationContext.xml文件里给Person类的id和name进行赋值的时候,用到了<property>
标签,这种就是set方式注入
<bean id="personOne" class="com.hkd.spring.mod.Person">
<property name="id" value="1001"></property>
<property name="name" value="小明"></property>
</bean>
- 原理:property的name属性的值不是看Person类中有没有id或者name这个属性,而是看有没有与id和name对应的set方法
- 举例:在Person类中,有setID()这个方法,所以可以set注入
4.2.2 构造方法注入
(1)这里重新写一个类Student,给它几个属性,并完成他的构造方法和toString方法(Java基础内容,如果不会的话要补课咯!)
package com.hkd.spring.di;
public class Student {
private Integer id;
private String name;
private Integer age;
private String sex;
private double score;
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;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", age=" + age + ", sex=" + sex + ", score=" + score + "]";
}
public Student(Integer id, String name, Integer age, String sex) {
super();
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
}
public Student(Integer id, String name, double score, String sex) {
super();
this.id = id;
this.name = name;
this.score = score;
this.sex = sex;
}
public Student() {
super();
}
}
(2)构造方式注入
- 第一种:用
<constructor-arg value=" "></constructor-arg>
标签,提供里面的value值,会根据value值自动去实体类中找对应的构造方法,进行匹配 - 第二种:依然用
<constructor-arg value=" "></constructor-arg>
标签,但除了value属性,我们还给他一个index和type属性,意思是第index个属性的类型是type,这样可以更加精确地匹配
<?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="s2" class="com.hkd.spring.di.Student">
<constructor-arg value="10011"></constructor-arg>
<constructor-arg value="李四"></constructor-arg>
<constructor-arg value="23"></constructor-arg>
<constructor-arg value="男"></constructor-arg>
</bean>
<bean id="s3" class="com.hkd.spring.di.Student">
<constructor-arg value="10012"></constructor-arg>
<constructor-arg value="王五"></constructor-arg>
<constructor-arg value="90" index="2" type="double"></constructor-arg>
<constructor-arg value="男"></constructor-arg>
</bean>
</beans>
(3)编写main函数进行测试
package com.hkd.spring.di;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("beans-di.xml");
Student stu2 = ac.getBean("s2",Student.class);
System.out.println(stu2);
Student stu3 = ac.getBean("s3",Student.class);
System.out.println(stu3);
}
}
(4)最终结果
我们发现score被赋值了,说明构造方法注入成功!
4.3 p命名空间注入
Spring从2.5版本开始引入了一个新的p命名空间,可以通过<bean>元素属性的方式配置Bean的属性
-
首先,我们需要在beans命名空间处加上一句话
xmlns:p=“http://www.springframework.org/schema/p”
-
直接在bean标签的属性中,添加 p:属性
的方式进行赋值
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="s4" class="com.hkd.spring.di.Student"
p:id="10013" p:name="赵六" p:age="10" p:sex="男"
p:score="100.0">
</bean>
</beans>
package com.hkd.spring.di;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("beans-di.xml");
Student stu4 = ac.getBean("s4",Student.class);
System.out.println(stu4);
}
}
-
得出结果
4.4 其他注入方式
4.4.1 字面量
上面几个例子,我们赋值的时候,都是用value属性进行赋值的,value属性里面都是字符串,像这种的就叫做字面量注入。下面是字面量的定义:
- 可以使用字符串表示的值,可以通过value属性或value子节点的方式指定
- 基本数据类型及其封装类、String等类型都可以采取字面值注入的方式
- 若字面值中包含特殊字符,可以使用<![CDATA[]]>把字面值包裹起来
4.4.2 ref
如果value属性的值是一个引用类型,是一个类的话,它就不是字面量,就不能用value赋值了,这时候我们可以采用ref='bean的id'
属性来进行注入
- 还使用上面的例子,假设学生类(Student)需要有一个教师类(Teacher)的属性
package com.hkd.spring.di;
public class Teacher {
private Integer tid;
private String tname;
public Integer getTid() {
return tid;
}
public void setTid(Integer tid) {
this.tid = tid;
}
public String getTname() {
return tname;
}
public void setTname(String tname) {
this.tname = tname;
}
@Override
public String toString() {
return "Teacher [tid=" + tid + ", tname=" + tname + "]";
}
}
- 需要给一个学生傻七配一个老师,编写学生类Student,有一个属性是Teacher类型
package com.hkd.spring.di;
public class Student {
private Integer id;
private String name;
private Integer age;
private String sex;
private double score;
private Teacher teacher;
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
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;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", age=" + age + ", sex=" + sex + ", score=" + score
+ ", teacher=" + teacher + "]";
}
public Student() {
super();
}
}
- 如果是一个Teacher对象的话,就要在xml文件里先把Teacher这个类给注入值,再用
ref="bean的id"
这种方式来注入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 id="s5" class="com.hkd.spring.di.Student">
<property name="id" value="10014"></property>
<property name="name" value="傻七"></property>
<property name="age" value="28"></property>
<property name="sex" value="男"></property>
<property name="teacher" ref="teacher"></property>
</bean>
<bean id="teacher" class="com.hkd.spring.di.Teacher">
<property name="tid" value="001"></property>
<property name="tname" value="A老师"></property>
</bean>
</beans>
- 编写测试类,进行测试Teacher类是否注入进了Student类中
package com.hkd.spring.di;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("beans-di.xml");
Student stu5 = ac.getBean("s5",Student.class);
System.out.println(stu5);
}
}
- 得到结果,注入成功
4.4.3 内部bean
- 还是上面的案例,在xml文件中,Teacher的bean在Student的bean的外部,这叫外部声明的bean,其实也可以声明在内部,叫内部bean
- 当bean实例仅仅给一个特定的属性使用时,可以将其声明为内部bean
- 内部bean声明直接包含在
<property>
或<constructor-arg>
元素里,不需要设置任何id或name属性 - 内部bean不能使用在任何其他地方,只能在自己的bean中使用
<bean id="s6" class="com.hkd.spring.di.Student">
<property name="id" value="10014"></property>
<property name="name" value="崔八"></property>
<property name="age" value="38"></property>
<property name="sex" value="女"></property>
<property name="teacher">
<bean class="com.hkd.spring.di.Teacher">
<property name="tid" value="2222"></property>
<property name="tname" value="admin"></property>
</bean>
</property>
</bean>
4.5 集合属性
4.5.1 List集合和数组
- 还是上面的例子,现在一个教师可以教多个班级,要输出一个教师所教的班级都有哪些,这时候就需要将班级用list集合的方式进行注入了
- 这些标签可以通过
<value>
指定简单的常量值;通过<ref>
指定对其他Bean的引用;通过<bean>
指定内置bean定义;通过<null/>
指定空元素;甚至可以内嵌其他集合 - 配置
java.util.List
类型的属性,需要指定<list>
标签,在标签里包含一些元素 - 配置
java.util.Set
需要使用<set>
标签,定义的方法与List一样 - 数组的定义和List一样,都使用
<list>
元素,但数组还可以使用<Array>
标签
(一)以字面量为值的list集合
- 在Teacher类中增加一个属性cls,并增加其set、get和toString方法
package com.hkd.spring.di;
import java.util.List;
public class Teacher {
private Integer tid;
private String tname;
private List<String> cls;
public List<String> getCls() {
return cls;
}
public void setCls(List<String> cls) {
this.cls = cls;
}
public Integer getTid() {
return tid;
}
public void setTid(Integer tid) {
this.tid = tid;
}
public String getTname() {
return tname;
}
public void setTname(String tname) {
this.tname = tname;
}
@Override
public String toString() {
return "Teacher [tid=" + tid + ", tname=" + tname + "]";
}
}
- 在xml文件中,对Teacher类进行实例化,这时候Teacher中的cls属性就必须赋值成一个list数组
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="t1" class="com.hkd.spring.di.Teacher">
<property name="tid" value="111"></property>
<property name="tname" value="张老师"></property>
<property name="cls">
<list>
<value>A班</value>
<value>B班</value>
<value>C班</value>
</list>
</property>
</bean>
</beans>
- 进行测试,看班级是否注入到了tid为111,tname为张老师的bean下了
package com.hkd.spring.di;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("beans-di.xml");
Teacher t1 = ac.getBean("t1",Teacher.class);
System.out.println(t1);
}
}
- 得到结果,注入成功
(二)以bean的引用为值的list集合
- 现在要给一个tid为222、tname为李老师的老师分配学生,实质上就是给Teacher类注入一个Student类的List集合
- 编写xml里的bean,但是list的值是Student引用类型,如下
<bean id="t2" class="com.hkd.spring.di.Teacher">
<property name="tid" value="222"></property>
<property name="tname" value="李老师"></property>
<property name="students">
<list>
<ref bean="s1"></ref>
<ref bean="s2"></ref>
<ref bean="s3"></ref>
</list>
</property>
</bean>
4.5.2 Map集合
- Java.util.Map通过
<map>
标签定义,<map>
标签里可以使用多个<entry>
作为子标签。每个条目包含一个键和一个值 - 必须在
<key>
标签里定义键 - 因为键和值的类型没有限制,所以可以自由地为它们指定
<value>
、<ref>
、<bean>
或<null/>
元素 - 可以将Map的键和值作为的属性定义:简单常量使用key和value来定义;bean引用通过key-ref和value-ref属性定义
<bean id="t3" class="com.hkd.spring.di.Teacher">
<property name="tid" value="333"></property>
<property name="tname" value="王老师"></property>
<property name="bossMap">
<map>
<entry>
<key>
<value>10001</value>
</key>
<value>校长</value>
</entry>
<entry>
<key>
<value>10002</value>
</key>
<value>副校长</value>
</entry>
</map>
</property>
</bean>
4.5.3 集合类型的bean
- 在4.5.1这一节中,我们发现
<list>
标签在id为t1的bean的内部,这个<list>
里的内容只能在t1这个bean中使用。如果我们想在其他的bean中也能使用,则就需要将这个集合bean的配置拿到外面 - 配置集合类型的bean需要引入
util
名称空间
1. 首先我们需要在beans的命名空间处添加上util的命名空间
xmlns:util=“http://www.springframework.org/schema/util”
xsi:schemaLocation=“http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.3.xsd”
2. 在xml文件里,使用<util:list>
标签,里面的集合可以是字面量,也可以是引用,这个list就写在了bean的外部,在bean中的property标签直接使用ref属性调用即可
<bean id="t4" class="com.hkd.spring.di.Teacher">
<property name="tid" value="444"></property>
<property name="tname" value="忽老师"></property>
<property name="students" ref="students"></property>
</bean>
<util:list id="students">
<ref bean="s2"/>
<ref bean="s3"/>
<ref bean="s4"/>
</util:list>
- 在测试类中进行测试
package com.hkd.spring.di;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("beans-di.xml");
Teacher t4 = ac.getBean("t4",Teacher.class);
System.out.println(t4);
}
}
- 得出结果,外部定义list集合注入成功
- util标签还可以外部定义
<util:map>
、<util:set>
、<util:properties>
标签,以map为例随便写一个
<util:map>
<entry>
<key>
<value>1</value>
</key>
<value>张三</value>
</entry>
</util:map>
4.6 FactoryBean
- Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean
- 工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象
- 工厂bean必须实现
org.springframework.beans.factory.FactoryBean
接口
- 先创建一个Car类,里面有属性车的品牌和价格
package com.hkd.spring.factorybean;
public class Car {
private String brand;
private Double price;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
@Override
public String toString() {
return "Car [brand=" + brand + ", price=" + price + "]";
}
}
- 创建一个MyFactory的类,里面实现FactoryBean接口,并重写三个方法
getObject()
将创建好的bean返回给IOC容器、getObjectType()
返回bean的类型、isSingleton()
创建的bean是否单例
package com.hkd.spring.factorybean;
import org.springframework.beans.factory.FactoryBean;
public class MyFactory implements FactoryBean<Car>{
@Override
public Car getObject() throws Exception {
Car car = new Car();
car.setBrand("奥迪");
car.setPrice(200000.0);
return car;
}
@Override
public Class<?> getObjectType() {
return Car.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
- 创建一个factory-bean.xml文件,注意class属性写的全类名是MyFactory的,因为MyFactory实现了FactoryBean接口,在工厂里进行创建对象
<?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="factory" class="com.hkd.spring.factorybean.MyFactory"></bean>
</beans>
- 创建测试类进行测试,看得到的是不是Car的对象
package com.hkd.spring.factorybean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("factory-bean.xml");
Object object = ac.getBean("factory");
System.out.println(object);
}
}
- 得出结果,测试成功!
4.7 bean的作用域
- 在Spring中,可以在
<bean>
元素的scope
属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的 - 默认情况下,Spring只为每个在IoC容器里声明的bean创建唯一一个实例,整个IoC容器范围内都能共享该实例:所有后续的getBean()调用和bean引用都将返回这个唯一的bean实例。该作用域被称为singleton(单例),它是所有bean的默认作用域
- 当bean的作用域为singleton(单例)时,Spring会在IoC容器对象创建时就创建bean的对象实例
- 当bean的作用域为prototype时,IoC容器在获取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">
<bean id="student" class="com.hkd.ioc.scope.Student" scope="singleton">
<property name="sid" value="1001"></property>
<property name="sname" value="张三"></property>
</bean>
</beans>
类别 | 说明 |
---|
singleton | 在SpringIOC容器里默认只存在一个bean实例,类型是单实例 |
prototype | 每次调用getBean()时,都会返回一个新的实例 |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean,该作用域仅适用于WebApplicationContext环境 |
4.8 bean的生命周期
4.8.1 bean的生命周期
- 首先创建一个类Person,给出几个属性、无参构造方法、toString方法,为了演示初始化和销毁,在这里创建初始化init方法和销毁destroy方法
package com.hkd.ioc.life;
public class Person {
private Integer id;
private String sex;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
System.out.println("2.依赖注入");
this.id = id;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "4.使用";
}
public Person() {
System.out.println("1.创建对象");
}
public void init() {
System.out.println("3.初始化");
}
public void destroy() {
System.out.println("5.销毁");
}
}
- 创建life.xml文件,文件中创建bean实例
注意:如果想要调用到Person类中的init和destroy方法,需要在bean的属性中加入init-method='方法名'
属性和destroy-method='方法名'
属性,具体使用方法如下
<?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="person" class="com.hkd.ioc.life.Person" init-method="init" destroy-method="destroy">
<property name="id" value="1001"></property>
<property name="sex" value="男"></property>
<property name="name" value="张三"></property>
</bean>
</beans>
- 创建测试类,进行测试
package com.hkd.ioc.life;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("life.xml");
Person person = ac.getBean("person",Person.class);
System.out.println(person);
ac.close();
}
}
- 测试结果将bean实例创建的生命周期显示了出来
4.8.2 bean的后置处理器
-
bean后置处理器允许在调用初始化方法前后对bean进行额外的处理
-
bean后置处理器对IoC容器里的所有bean实例逐一处理,而非单一实例
-
bean后置处理器需要实现接口:
org.springframework.beans.factory.config.BeanPostProcessor
-
在初始化方法被调用前后,Spring将把每个bean实例分别传递给上述接口的以下两个方法:
- postProcessBeforeInitialization(Object, String)
- postProcessAfterInitialization(Object, String)
-
添加bean后置处理器后bean的生命周期
① 通过构造器或工厂方法创建bean实例
② 为bean的属性设置值和对其他bean的引用
③ 将bean实例传递给bean后置处理器的postProcessBeforeInitialization()方法【新增】
④ 调用bean的初始化方法
⑤ 将bean实例传递给bean后置处理器的postProcessAfterInitialization()方法【新增】
⑥ bean可以使用了
⑦ 当容器关闭时调用bean的销毁方法
4.9 自动装配
- 手动装配:以value或ref的方式明确指定属性值都是手动装配
- 自动装配:根据指定的装配规则,不需要明确指定,Spring自动将匹配的属性值注入bean中的property中
- autowire属性:根据某种策略自动为非字面量属性赋值
autowire属性值 | 说明 |
---|
default | 由<bean>的上级标签<beans>的default-autowire属性值确定,上级为什么,这里的默认值就是什么 |
byName | 通过属性名和Spring容器中bean的id进行比较,若一致则可直接赋值 |
byType | 通过Spring容器中bean的类型,为兼容性的属性赋值 |
- byType属性中,兼容性的属性是指可以通过子类的对象来为父类对象赋值,也可以通过实现类的对象来为接口赋值
- byType属性中,要求Spring容器中只能有一个能为属性赋值的bean
- 假设现在有一个员工Emp类,在这个员工类中有4个属性,eid员工的id、ename员工的name、car员工的车、dept员工的部门(里面的Car类型、Dept类型的变量这里不再展示)
package com.hkd.ioc.autowire;
public class Emp {
private Integer eid;
private String ename;
private Car car;
private Dept dept;
public Integer getEid() {
return eid;
}
public void setEid(Integer eid) {
this.eid = eid;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Emp [eid=" + eid + ", ename=" + ename + ", car=" + car + ", dept=" + dept + "]";
}
}
- 编写auto.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">
<bean id="emp" class="com.hkd.ioc.autowire.Emp" autowire="byName">
<property name="eid" value="1001"></property>
<property name="ename" value="张三"></property>
<property name="car" ref="car"></property>
<property name="dept" ref="dept"></property>
</bean>
<bean id="car" class="com.hkd.ioc.autowire.Car">
<property name="cid" value="666666"></property>
<property name="cname" value="奔驰"></property>
</bean>
<bean id="dept" class="com.hkd.ioc.autowire.Dept">
<property name="did" value="1"></property>
<property name="dname" value="开发部"></property>
</bean>
</beans>
-
使用自动装配的byName属性进行装配
<?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="emp" class="com.hkd.ioc.autowire.Emp" autowire="byName">
<property name="eid" value="1001"></property>
<property name="ename" value="张三"></property>
</bean>
<bean id="car" class="com.hkd.ioc.autowire.Car">
<property name="cid" value="666666"></property>
<property name="cname" value="奔驰"></property>
</bean>
<bean id="dept" class="com.hkd.ioc.autowire.Dept">
<property name="did" value="1"></property>
<property name="dname" value="开发部"></property>
</bean>
</beans>
-
使用自动装配的byType进行装配
<?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="emp" class="com.hkd.ioc.autowire.Emp" autowire="byType">
<property name="eid" value="1001"></property>
<property name="ename" value="张三"></property>
</bean>
<bean id="car" class="com.hkd.ioc.autowire.Car">
<property name="cid" value="666666"></property>
<property name="cname" value="奔驰"></property>
</bean>
<bean id="dept" class="com.hkd.ioc.autowire.Dept">
<property name="did" value="1"></property>
<property name="dname" value="开发部"></property>
</bean>
</beans>
4.10 通过注解配置bean
4.10.1 常用的注解
-
@Component
—— 标识一个受Spring IoC容器管理的组件(Bean),可以使用在任何层次,使用时只需将该注解标注在相应的类上即可
- @Component 是把最普通的类(不属于三层架构的类)实例化到spring容器中,相当于配置文件中的 <bean id="" class=""/>
- @Component(“userMod”),括号里给的参数相当于配置的 id 属性
-
@Repository
—— 标识一个受Spring IoC容器管理的持久化层组件(DAO层)
-
@Service
—— 标识一个受Spring IoC容器管理的业务逻辑层组件(Service层)
-
@Controller
—— 标识一个受Spring IoC容器管理的表述层控制器组件(控制层,处理请求和响应)
注意:
- 以上三个注解的功能完全相同,只是为了区分各个层而已,使代码更加清晰,在实际开发中,要在不同功能的类上加上相应的注解
- 组件的实质就是加上注解的类,也指Sping中管理的bean
-
@value("属性值")
—— 对一般属性进行注入,直接写在属性的上方,可以不提供set方法
-
@Autowired
—— 自动装配,在需要赋值的非字面量属性上,加上此注解,就可以在Spring容器中通过不同的方式匹配到相应的bean
注意:
- @Autowired 自动装配时默认byType(类型装配)的方式,此时要求Spring容器中只有一个能为其赋值
- 当默认的byType实现不了装配时,会自动切换到byName,此时要求Spring容器中有一个bean的id和属性名一致
- 若自动装配时,匹配到多个能够赋值的bean,可使用
@Qualifier(value="beanId")
指定使用的bean,具体使用方法如下: @Autowired
和@Qualifier(value="beanId")
可以一起作用于一个带形参的方法上,此时@Qualifier(value="beanId")
所指定的bean作用于形参
-
@Resource(name="")
—— 作用相当于@Autowired
和@Qualifier
的结合使用
- @Resource(name="") 默认会根据指定的name属性去Spring容器中寻找与该名称匹配的类型
- 例如:@Resource(name=“userDao”),只有当找不到与名称匹配的Bean时才会按照类型来装配注入
- 与@Autowired刚好相反,@Autowired是按照类型装配的,如果想按照名称进行装配还需要配合@Qualifier使用,而@Resource是按照名称装配的,名称找不到了才按照类型装配
4.10.2 通过注解方式配置bean的步骤
- 在需要被Spring管理的类上加上相应的注解
- 扫描组件 —— 类被注解标识后必须通过Spring进行扫描才能够起作用
4.10.3 扫描组件
- 导入jar包
spring-aop-4.3.6.RELEASE.jar
- 在配置文件中的命名空间添加
xmlns:context=“http://www.springframework.org/schema/context”
xsi:schemaLocation=“http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd”
- 使用
<context:component-scan base-package=""></context:component-scan>
标签,base-package的值为一个需要扫描的包
4.10.4 举例说明
- 我们重新创建一个包,名字是UserMod
- 实现三层架构中的控制层,并加上注解
@controller
package com.hkd.ioc.userMod.controller;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
public UserController() {
System.out.println("UserController");
}
}
- 实现三层架构中的业务逻辑层,包含接口和实现类,并加上注解
@service
package com.hkd.ioc.userMod.service;
public interface UserService {
}
package com.hkd.ioc.userMod.service;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
public UserServiceImpl() {
System.out.println("UserServiceImpl");
}
}
- 实现三层架构中的持久化层,包含接口和实现类,并加上注解
@Repository
package com.hkd.ioc.userMod.service;
public interface UserDao {
}
package com.hkd.ioc.userMod.dao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImpl implements UserDao{
public UserDaoImpl() {
System.out.println("UserDaoImpl");
}
}
- 编写user.xml配置文件,使用
<context:component-scan></context:component-scan>
标签
<?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-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="com.hkd.ioc.userMod"></context:component-scan>
</beans>
- 编写测试类,看程序是否按照三层架构方式执行
package com.hkd.ioc.userMod;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("user.xml");
}
}
- 得出结论,基于Annotation注解的装配实现成功!
4.10.5 扫描组件的包含和排除
-
上两节我们介绍了<context:component-scan>
标签,我们知道这个标签的属性base-package需要写的是一个包的名,意味着扫描这一个包,上一个例子里扫描了UserMod包,意味着UserMod包下的所有包都扫描了
-
但实际开发中,往往不需要扫描所有的包,就得一个一个写UserMod.Service,UserMod.Dao等等,比较麻烦
-
这里介绍了<context:component-scan>
标签下的两个标签,可以在设定的包结构下再次通过注解和类型具体包含到某个或某几个类
<?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-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="com.hkd.ioc.userMod" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="assignable" expression="com.hkd.ioc.UserMod.service.UserServiceImpl"/>
</context:component-scan>
<context:component-scan base-package="com.hkd.ioc.UserMod">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
<context:exclude-filter type="assignable" expression="com.hkd.ioc.UserMod.service.UserServiceImpl"/>
</context:component-scan>
</beans>
五、Spring AOP
5.1 AOP面向切面编程
- AOP(Aspect-Oriented Programming,面向切面编程)是对传统OOP(Object-Oriented Programming,面向对象编程)的一种补充
面向对象是纵向继承机制
面向切面是横向抽取机制
- AOP编程操作的主要对象是切面(aspect),存储公共功能的类就叫做切面
- 切面的作用:模块化横切关注点(公共功能)
在用AOP编程时,有些方法需要定义一些公共功能,这些公共功能是针对不同类需要做相同的事情而编写的,这些方法叫做横切关注点,把横切关注点抽取出来,放到一个类里,这样的类我们通常称之为切面
- 上图便于我们理解AOP:四个方法中都有验证参数、前置日志和后置日志三个功能,这些公共功能叫做横切关注点,把这些横切关注点抽取出来放到一个类里,这个类就是切面,最后通过AOP实现三个功能
- AOP的好处:
- 每个事物逻辑位于一个位置,代码不分散,便于维护和升级
- 业务模块更简洁,只包含核心业务代码
5.2 AOP相关术语
5.2.1 横切关注点
从每个方法中抽取出来的同一类非核心业务
5.2.2 切面(Aspect)
将横切关注点封装成一个类,这个类就是切面
5.2.3 通知(Advice)
横切关注点在切面里的叫法,二者实际是一个东西,在不同位置叫法不同
5.2.4 目标对象(Target Object)
被通知的对象,也就是抽取出的代码所作用到的对象
5.2.5 代理(Proxy)
将通知应用到目标对象之后,被动态创建的对象
5.2.6 连接点(Joinpoint)
功能执行过程中的各个位置,一般只操作四个位置:方法调用前、方法调用后、当抛出异常和finally位置
5.2.7 切入点(Pointcut)
- 定位连接点的方式。每个类的方法中都包含多个连接点,AOP可以通过切入点定位到特定的连接点
- 切点通过
org.springframework.aop.Pointcut
接口进行描述,它使用类和方法作为连接点的查询条件 - 切入点指的是类和方法名
5.3 动态代理
5.3.1 动态代理的原理
- 使用一个代理将原本对象包装起来,然后用该代理对象“取代”原始对象
- 任何对原始对象的调用都要通过代理
- 代理对象决定是否以及何时将方法调用转到原始对象上
5.3.2 动态代理的分类
- 基于接口实现动态代理: JDK动态代理
- 基于继承实现动态代理: CGLIB、Javassist动态代理
5.3.3 JDK动态代理
- JDK动态代理通过
java.lang.reflect.Proxy
来实现 - JDK动态代理必须有一个业务接口类
举例说明:
-
首先创建一个MathI接口,里面提供了加减乘除的四个方法
package com.hkd.proxy;
public interface MathI {
public int plus(int i, int j);
public int sub(int i, int j);
public int mul(int i, int j);
public int div(int i, int j);
}
-
创建接口的实现类MathImpl,并添加方法的具体实现
package com.hkd.proxy;
public class MathImpl implements MathI{
@Override
public int plus(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
-
创建日志类MyLogger,现在想在执行加减乘除方法之前和之后,有一个记录日志的操作,分别记录两个数是多少,和结果是多少
package com.hkd.proxy;
public class MyLogger {
public static void before(String methodName, String args) {
System.out.println("method:"+methodName+",args:"+args);
}
public static void after(String methodName, Object result) {
System.out.println("method:"+methodName+",args:"+result);
}
}
-
创建代理类,注意看步骤
package com.hkd.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class ProxyUtil {
private MathImpl mathImpl;
public ProxyUtil(MathImpl mathImpl) {
super();
this.mathImpl = mathImpl;
}
public Object getProxy() {
ClassLoader loader = this.getClass().getClassLoader();
Class[] interfaces = mathImpl.getClass().getInterfaces();
return Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
MyLogger.before(method.getName(), Arrays.toString(args));
Object result = method.invoke(mathImpl, args);
MyLogger.after(method.getName(), result);
return result;
} catch (Exception e) {
MyLogger.throwing();
e.printStackTrace();
}
return null;
}
});
}
}
-
创建测试类,进行测试
package com.hkd.proxy;
public class Test {
public static void main(String[] args) {
ProxyUtil proxy = new ProxyUtil(new MathImpl());
MathI math = (MathI)proxy.getProxy();
int i = math.plus(1, 1);
}
}
-
得出结果,成功记录了加数和结果
5.3.4 CGLIB动态代理
- JDK动态代理虽然方便,但是代理对象必须实现一个或多个接口
- 如果没有接口,可以使用CGLIB动态代理
5.4 AspectJ开发
5.4.1 什么是AspectJ
- AspectJ是Java社区里最完整最流行的AOP框架,它提供了强大的AOP功能
- 在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP
5.4.2 AspectJ环境搭建
1. 导入jar包
在Spring基础5个包的基础上导入以下jar包
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
spring-aop-4.3.6.RELEASE.jar
spring-aspects-4.3.6.RELEASE.jar
如果不适用接口,则使用cglib动态代理,导入com.springsource.net.sf.cglib-2.2.0.jar
2. 添加命名空间
xmlns:aop=“http://www.springframework.org/schema/aop”
xsi:schemaLocation=http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
5.4.3 切入点表达式
1. 格式:
execution( [权限修饰符] [返回值类型] [简单类名/全类名] [方法名] ([参数列表]) )
2. 作用:
通过表达式的方式定位一个或多个具体的连接点
3. 举例:
- execution(public void com.hkd.study.aop.plus(int, int))
意思是:访问修饰符为public,返回值是void类型,aop接口声明的plus方法,并且方法需要两个int类型的参数 - execution( * com.hkd.study.aop.*.*(..))
a. 第一个*表示任意修饰符及任意返回值;
b. 第二个*表示类名,使用*代表所有的类;
c. 第三个*表示方法名,使用*代表所有方法;最后的(..)表示任意数量、类型的参数
d. 注意第一个*与包名直接有一个空格
4. 注意:
在AspectJ中,切入点表达式可以通过 “&&”、"||"、"!"等操作符结合起来,例如:
- execution (* *.add(int,..)) || execution(* *.sub(int,..))
表示任意类中第一个参数为int类型的add方法或sub方法 - !execution (* *.add(int,..))
表示匹配不是任意类中第一个参数为int类型的add方法
5.4.4 连接点
1. 连接点
- 就一个具体的连接点而言,我们可能会关心这个连接点的一些具体信息例如:当前连接点所在方法的方法名、当前传入的参数值等等
- 这些信息都封装在
JoinPoint
接口的实例对象中
2. 常用的方法
joinpoint.getArgs()
—— 获取实际参数数组
joinpoint.getSignature().getName()
—— 获取方法名
5.4.5 AspectJ注解
注解名称 | 描述 |
---|
@Aspect | 用于定义一个切面 |
@PointCut | 用于定义一个切入点表达式。使用时还需要定义一个包含名字和任意参数的方法签名来表示切入点表达式。实际上这个方法签名就是一个返回值为void且方法体为空的普通方法 |
5.4.6 通知
通知名称 | 通知标识 | 描述 |
---|
前置通知 | @Before | 作用于方法执行之前需要指定value属性,该属性值用于指定一个切入点表达式 |
后置通知 | @After | 作用于方法的finally语句块,后置通知是在连接点完成之后执行的,即不管有没有异常,都会执行 |
返回通知 | @AfterReturning | 作用于方法执行之后,可通过returning设置接受方法返回值的变量名,要想在方法中使用,必须要方法的形参中设置和变量名相同的参数名的参数 |
异常通知 | @AfterThrowing | 作用于方法抛出异常时,可通过throwing设置接收方法返回的异常信息在参数列表中,通过具体的异常类型,来对指定的异常信息进行操作 |
环绕通知 | @Around | 能够全面地控制连接点,甚至可以控制是否执行连接点。连接点的参数类型必须是ProceedingJoinPoint,需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法 |
以上通知的演示在5.4.6节的案例里
5.4.7 基于注解的声明式AspectJ
1. 仍然以加减乘除为例,演示各类通知,首先编写MathI接口
package com.hkd.AspectJ;
public interface MathI {
public int plus(int i, int j);
public int sub(int i, int j);
public int mul(int i, int j);
public int div(int i, int j);
}
2. 编写接口的实现类,注意注解的运用
package com.hkd.AspectJ;
import org.springframework.stereotype.Component;
@Component()
public class MathImpl implements MathI{
@Override
public int plus(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
3. 编写日志类,解析各种通知的写法
package com.hkd.AspectJ;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyloggerAspect {
@Before(value = "execution(* com.hkd.AspectJ.*.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println("method:"+methodName+",arguments:"+Arrays.toString(args));
}
@After(value = "execution(* com.hkd.AspectJ.*.*(..))")
public void afterMethod() {
System.out.println("后置通知");
}
@AfterReturning(value = "execution(* com.hkd.AspectJ.*.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("method:"+methodName+",result:"+result);
}
@AfterThrowing(value = "execution(* com.hkd.AspectJ.*.*(..))",throwing = "ex")
public void afterThrowingMethod(Exception ex) {
System.out.println("有异常了:" + ex);
}
@Around(value="execution(* com.hkd.AspectJ.*.*(..))")
public Object arountMethod(ProceedingJoinPoint joinPoint) {
Object result = null;
try {
System.out.println("前置通知");
result = joinPoint.proceed();
System.out.println("返回通知");
return result;
} catch (Throwable e) {
System.out.println("异常通知");
e.printStackTrace();
} finally {
System.out.println("后置通知");
}
return -1;
}
}
4. 编xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/bean http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="com.hkd.AspectJ"></context:component-scan>
<aop:aspectj-autoproxy />
</beans>
5. 编写测试类,进行测试
package com.hkd.AspectJ;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("aop.xml");
MathI math = ac.getBean("mathImpl", MathI.class);
int i = math.div(2, 1);
System.out.println(i);
}
}
6. 得出结果,各种通知实现成功
5.4.8 重用切入点
- 在编写AspectJ切面时,可以直接在通知注解中书写切入点表达式,但同一个切点表达式可能会在多个通知中重复出现
- 在AspectJ切面中,可以通过
@Pointcut
注解将一个切入点声明成简单的方法 - 切入点的方法体通常是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的
- 切入点方法的访问控制符同时也控制着这个切入点的可见性,如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中。在这种情况下,它们必须被声明为public,在引入这个切入点时,必须将类名也包括在内。如果类没有与这个切面放在同一个包中,还必须包含包名
package com.hkd.AspectJ;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyloggerAspect {
@Pointcut(value = "execution(* com.hkd.AspectJ.*.*(..))")
public void test() {}
@Before(value = "test()")
public void beforeMethod(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println("method:"+methodName+",arguments:"+Arrays.toString(args));
}
}
5.4.9 切面的优先级
- 在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的
- 切面的优先级以通过实现Ordered接口或利用
@Order
注解指定 - 实现Ordered接口,getOrder()方法的返回值越小,优先级越高
- 若使用
@Order
注解,值越小优先级越高,@Order(1)
比@Order(2)
的优先级高,默认值为int的最大值
5.4.10 基于xml的声明式AspectJ
- 正常情况下,基于注解的声明要优先于基于XML的声明
- 通过AspectJ注解,切面可以与AspectJ兼容,而基于XML的配置则是Spring专有的
1. 编写接口和实体类
package com.hkd.AspectJ_xml;
public interface MathI {
public int plus(int i, int j);
public int sub(int i, int j);
public int mul(int i, int j);
public int div(int i, int j);
}
package com.hkd.AspectJ_xml;
import org.springframework.stereotype.Component;
@Component()
public class MathImpl implements MathI{
@Override
public int plus(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
2. 编写切面类
package com.hkd.AspectJ_xml;
import org.springframework.stereotype.Component;
@Component
public class MyLogger {
public void Before() {
System.out.println("前置通知");
}
public void AfterReturning() {
System.out.println("后置通知");
}
public void Around() {
System.out.println("环绕通知");
}
public void AfterThrowing() {
System.out.println("异常通知");
}
public void After() {
System.out.println("最终通知");
}
}
3. 配置xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="com.hkd.AspectJ_xml"></context:component-scan>
<aop:config>
<!-- 1. 配置切面 -->
<aop:aspect ref="myLogger">
<!-- 2. 配置切入点 -->
<aop:pointcut expression="execution(* com.hkd.AspectJ_xml.*.*(..))" id="cut"/>
<!-- 3. 配置通知 -->
<!-- 配置前置通知 -->
<aop:before method="Before" pointcut-ref="cut"/>
<!-- 配置后置通知 -->
<aop:after-returning method="AfterReturning" pointcut-ref="cut" returning="returnVal"/>
<!-- 配置环绕通知 -->
<aop:around method="Around" pointcut-ref="cut"/>
<!-- 配置异常通知 -->
<aop:after-throwing method="AfterThrowing" pointcut-ref="cut" throwing="e"/>
<!-- 配置最终通知 -->
<aop:after method="After" pointcut-ref="cut"/>
</aop:aspect>
</aop:config>
</beans>
4. 编写测试类
package com.hkd.AspectJ_xml;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("AspectJ_xml.xml");
MathI math = ac.getBean("mathImpl",MathI.class);
int i = math.plus(1, 3);
System.out.println(i);
}
}
六、JdbcTemplate
6.1 JdbcTemplate概述
- 为了使JDBC更加易于使用,减少代码量,Spring在JDBC API上定义了一个抽象层,以此建立一个JDBC存取框架
- 作为Spring JDBC框架的核心,JDBC模板的设计目的是为不同类型的JDBC操作提供模板方法。通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取的工作量降到最低
- 可以将Spring的JdbcTemplate看作是一个小型的轻量级持久化层框架
6.2 搭建JdbcTemplate环境
6.2.1 导入jar包
在Spring基础5个包的基础上导入以下jar包:
spring-jdbc-4.3.6.RELEASE.jar
spring-orm-4.3.6.RELEASE.jar
spring-tx-4.3.6.RELEASE.jar
还需要导入数据库连接相关包:
mysql-connector-java-5.1.37-bin.jar
使用druid连接池:druid-1.1.9.jar
使用c3p0连接池:c3p0-0.9.5.5.jar
,mchange-commons-java-0.2.19.jar
6.2.2 创建属性文件db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm
jdbc.username=root
jdbc.password=123
6.2.3 创建连接池
如果使用的是druid连接池,这样创建连接池
<?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-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:property-placeholder location="db.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
如果使用的是c3p0连接池,这样创建连接池
<?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-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
6.3 update()增删改
update()方法可以完成增删改操作,其返回值是int类型,返回受影响的行数
-
创建一个实体类Emp,其中有属性,并且在MySQL数据库中创建相应的表
package com.hkd.jdbctemplate;
public class Emp {
private Integer eid;
private String ename;
private Integer age;
private String sex;
public Integer getEid() {
return eid;
}
public void setEid(Integer eid) {
this.eid = eid;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Emp [eid=" + eid + ", ename=" + ename + ", age=" + age + ", sex=" + sex + "]";
}
}
-
使用c3p0数据库连接池进行数据库连接,详见6.2的步骤
-
使用Junit测试,进行update()的测试
package com.hkd.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
public class TestJdbcTemplate {
ApplicationContext ac = new ClassPathXmlApplicationContext("jdbc_c3p0.xml");
JdbcTemplate jdbcTemplate = ac.getBean("jdbcTemplate",JdbcTemplate.class);
@Test
public void testUpdate() {
String sql = "insert into emp values(null,?,?,?)";
jdbcTemplate.update(sql, "Jack", 24, "Women");
}
}
-
完成测试,得到结果,Jack已增加
6.4 batchUpdate(String, List<Object[]>)批量增删改
batchUpdate(String, List<Object[]>)
执行批量增删改操作- 第一个参数String是提供的sql语句
- 第二个参数的Object[]封装了sql语句每一次执行时所需要的参数,List集合封装了sql语句多次执行时的所有参数
- 仍然使用6.3节的案例,Emp实体类、数据库表和数据库连接池
- 批量增删改的方法
@Test
public void testBatchUpdate() {
String sql = "insert into emp values(null,?,?,?)";
List<Object[]> list = new ArrayList<>();
list.add(new Object[] {"aa",11,"Woman"});
list.add(new Object[] {"bb",22,"Woman"});
list.add(new Object[] {"cc",33,"Woman"});
jdbcTemplate.batchUpdate(sql, list);
}
- 得到结果,已增加成功
6.5 queryForObject()
6.5.1 查询一条数据,返回一个对象
queryForObject(String, RowMapper\<Department>, Object...)
用来获取单条数据,返回一个对象- 第一个参数String是sql语句
- 第二个参数RowMapper是返回一个Object类型的对象
- 第三个参数是sql语句中的参数
@Test
public void testQueryForObjectReturnSingleObject() {
String sql = "select eid,ename,age,sex from emp where eid = ?";
RowMapper<Emp> rowMapper = new BeanPropertyRowMapper<>(Emp.class);
Emp emp = jdbcTemplate.queryForObject(sql, rowMapper, 1);
System.out.println(emp);
}
6.5.2 查询单个值,返回这个值
queryForObject(String, Class, Object...)
- 第一个参数是String类型的sql语句
- 第二个参数是最后返回的是什么类型的这里就写什么类型
- 第三个参数是参数
@Test
public void testQueryForObjectReturnSingleValue() {
String sql = "select count(eid) from emp";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println(count);
}
6.6 query()查询
查询多条数据返回多个对象
@Test
public void testQuery() {
String sql = "select eid,ename,age,sex from emp";
RowMapper<Emp> rowMapper = new BeanPropertyRowMapper<>(Emp.class);
List<Emp> list = jdbcTemplate.query(sql, rowMapper);
for(Emp emp : list) {
System.out.println(emp);
}
}
6.7 JdbcTemplate中的DAO模式
- 创建Emp接口类和EmpImpl接口实现类,注意接口实现类里@Autowire自动装配
package com.hkd.jdbc.dao;
import com.hkd.jdbctemplate.Emp;
public interface EmpDao {
public int insertEmp(Emp emp);
}
package com.hkd.jdbc.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import com.hkd.jdbctemplate.Emp;
@Repository
public class EmpDaoImpl implements EmpDao{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int insertEmp(Emp emp) {
String sql = "insert into emp values(null,?,?,?)";
Object[] obj = new Object[] {
emp.getEname(),emp.getAge(),emp.getSex()
};
return jdbcTemplate.update(sql, obj);
}
}
- 在jdbc_dao.xml文件中配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="com.hkd.jdbc.dao"></context:component-scan>
<context:property-placeholder location="classpath:db.properties" />
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
- 在测试类里进行dao模式的测试
package com.hkd.jdbc.dao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.hkd.jdbctemplate.Emp;
public class test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("jdbc_dao.xml");
EmpDao empDao = ac.getBean("empDaoImpl",EmpDao.class);
Emp emp = new Emp();
emp.setEid(8);
emp.setEname("zhangsan");
emp.setAge(80);
emp.setSex("Man");
int num = empDao.insertEmp(emp);
if(num>0)
System.out.println("插入"+num+"条数据成功");
else
System.out.println("插入数据失败");
}
}
- 得出结果,dao模式连接数据库成功
七、Spring事务管理
7.1 事务概述
- 事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要么都不执行
- 事务的四个关键属性
属性 | 属性解释 |
---|
原子性(atomicity) | “原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行 |
一致性(consistency) | “一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚 |
隔离性(isolation) | 在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰 |
持久性(durability) | 持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中 |
7.2 事务环境搭建
1. 在原有的5个基本包的基础上导入以下jar包
AspectJ和aop相关包:
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
spring-aop-4.3.6.RELEASE.jar
事务相关包:
spring-tx-4.3.6.RELEASE.jar
2. 添加tx命名空间
xmlns:tx=“http://www.springframework.org/schema/tx”
xsi:schemaLocation=http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
3. 配置文件需要配置事务管理器
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
7.3 事务的传播行为propagation
7.3.1 什么是事务的传播行为和属性
A方法和B方法都有事务,当A在调用B的时候,会将A中的事务传播给B方法,B方法对于事务的处理方式就是事务的传播行为
属性名称 | 属性值 | 属性解释 |
---|
PROPAGATION_REQUIRED | REQUIRED | 如果有事务在运行,当前的方法就在这个事务中运行,否则启动新事务,再执行方法 |
PROPAGATION_REQUIRES_NEW | REQUIRES_NEW | 如果方法已在事务环境中,则停止该事务,使用自己的事务; 如果方法不在事务环境中,则自动启动一个新事务后再执行方法 |
PROPAGATION_SUPPORTS | SUPPORTS | 如果当前方法处于事务环境中,则使用该事务,否则不使用事务 |
PROPAGATION_NOT_SUPPORTED | NOT_SUPPORTED | 当前方法不应该运行在事务中,如果有则将它挂起 |
PROPAGATION_MANDATORY | MANDATORY | 当前方法必须运行在事务内部,如果没有运行的事务,就抛出异常 |
PROPAGATION_NEVER | NEVER | 当前方法不应该运行在事务中,如果有运行的事务,就抛出异常 |
PROPAGATION_NESTED | NESTED | 即使当前方法处于事务中,依然会启动一个新事务,并且方法在嵌套的事务中执行; 即使当前执行的方法不在事务中,也会启动一个新事务,然后执行该方法 |
7.3.2 事务的传播行为的用法
1. 注解方式
直接在要处理事务的类或者方法前,加上注解,注解中括号里添加属性propagation,如下
@Transactional(propagation = Propagation.REQUIRES_NEW)
2. 配置方式
7.4 事务的隔离级别
7.4.1 数据库事务并发问题
1. 脏读:
① A将某条记录的age值从20修改为30
② B读取了A更新后的值:30
③ 现在A回滚了,age值恢复到了20
④ B读取到的30就是一个无效的值
2. 不可重复读
① A读取了age值为20
② B将age值修改为30
③ A再次读取age值成了30,和第一次读取不一致了
3. 幻读
① A读取了student表中的一部分数据
② B向student表中插入了新的行
③ A再读取student表时,就多出了一些行
7.4.2 隔离级别
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别
- 读未提交:
READ UNCOMMITTED
允许A读取B未提交的修改 - 读已提交:
READ COMMITTED
要求A只能读取B已提交的修改 - 可重复读:
REPEATABLE READ
确保A可以多次从一个字段中读取到相同的值,即A执行期间禁止其它事务对这个字段进行更新 - 串行化:
SERIALIZABLE
确保A可以多次从一个表中读取到相同的行,在A执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下
能力 | 脏读 | 不可重复读 | 幻读 |
---|
READ UNCOMMITTED | √ | √ | √ |
READ COMMITTED | × | √ | √ |
REPEATABLE READ | × | × | √ |
SERIALIZABLE | × | × | × |
7.4.3 事务隔离级别的写法
1. 注解方式
直接在@Transactional的isolation属性中设置隔离级别
@Transactional(isolation = Isolation.DEFAULT)
2. 配置方式
7.5 事务的超时和只读属性
-
timeout:在事务强制回滚前,最多可以执行(等待)的时间
-
readOnly:指定当前事务中的一系列操作是否为只读;
- 如果设置为只读,不管事务中有没有写的操作,MySQL都会在请求访问数据的时候,不加锁,提高性能;
- 如果有写的操作,建议一定不能设置只读
7.6 声明式事务管理写法
7.6.1 基于注解的声明式事务管理
1. 开启事务注解
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
2. 在需要使用事务的bean类或者bean类方法上使用注解@Transactional
- 如果在方法上,则对这个方法有效;如果在类上,则对类中的所有方法有效
- 其中的一些属性,已经在之前的讲解中展示如何书写
7.6.2 基于xml的声明式事务管理
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<aop:config>
<aop:pointcut
expression="execution(* com.atguigu.tx.component.service.BookShopServiceImpl.purchase(..))"
id="txPointCut"/>
<aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/>
</aop:config>
<tx:advice id="myTx" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="find*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="purchase"
isolation="READ_COMMITTED"
no-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException"
propagation="REQUIRES_NEW"
read-only="false"
timeout="10"/>
</tx:attributes>
</tx:advice>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)