Spring框架采用的是分层架构,它一系列的功能要素被分为20个模块。
(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
(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
代码提要:
初始化容器通过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) { // 1.初始化容器 ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2.通过getBean()方法获取对象(三种方法获取) // 2.1 第一种 //Person person = (Person)ac.getBean("person"); // 2.2 第二种【使用此方法获取对象时,要求Spring所管理的此类型的对象只能有一个】 //Person person = ac.getBean(Person.class); // 2.3 第三种【一般用这个方式,同时指定id和name】 Person person = ac.getBean("personOne", Person.class); System.out.println(person); } }在第三章Spring完成的简单案例中,applicationContext.xml文件里给Person类的id和name进行赋值的时候,用到了<property>标签,这种就是set方式注入
<bean id="personOne" class="com.hkd.spring.mod.Person"> <!-- 对id和name进行注入(赋值) --> <!-- name的值不是Person类中的属性有没有这个值,而是看有没有与之对应的set方法 --> <!-- 只要是property,就是通过set方法进行注入 --> <property name="id" value="1001"></property> <property name="name" value="小明"></property> </bean> 原理:property的name属性的值不是看Person类中有没有id或者name这个属性,而是看有没有与id和name对应的set方法举例:在Person类中,有setID()这个方法,所以可以set注入(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(); // TODO Auto-generated constructor stub } }(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"> <!-- 用这种方式注入,自动匹配实体类中相对应的的构造方法,如果想让对应,使用index和type属性 --> <constructor-arg value="10012"></constructor-arg> <constructor-arg value="王五"></constructor-arg> <!-- 指定索引为2的(第三个)参数,值的类型为double --> <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对象 Student stu2 = ac.getBean("s2",Student.class); System.out.println(stu2); Student stu3 = ac.getBean("s3",Student.class); System.out.println(stu3); } }(4)最终结果 我们发现score被赋值了,说明构造方法注入成功!
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"> <!-- 使用p命名空间来注入 --> <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); } }得出结果
上面几个例子,我们赋值的时候,都是用value属性进行赋值的,value属性里面都是字符串,像这种的就叫做字面量注入。下面是字面量的定义:
可以使用字符串表示的值,可以通过value属性或value子节点的方式指定基本数据类型及其封装类、String等类型都可以采取字面值注入的方式若字面值中包含特殊字符,可以使用<![CDATA[]]>把字面值包裹起来如果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; //定义一个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(); // TODO Auto-generated constructor stub } } 如果是一个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注入--> <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> <!-- Teacher类型不是字面量,所以要用ref="bean的id"来注入引用类型的变量 --> <property name="teacher" ref="teacher"></property> </bean> <!-- 给Teacher的bean注入值,并给id为teacher --> <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); } } 得到结果,注入成功(一)以字面量为值的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; //增加属性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集合 --> <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"> <!-- 以bean的引用为值的list集合 --> <list> <ref bean="s1"></ref> <ref bean="s2"></ref> <ref bean="s3"></ref> </list> </property> </bean>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>Spring IoC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务
bean的生命周期进行管理的过程:
① 通过构造器或工厂方法创建bean实例 ② 为bean的属性设置值和对其他bean的引用 ③ 调用bean的初始化方法 ④ bean可以使用了 ⑤ 当容器关闭时,调用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.使用"; } //bean创建实例的时候会调用无参构造 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); //关闭bean ac.close(); } } 测试结果将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的销毁方法
缺点:当设置autowire属性的时候,会作用于该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> <!-- 使用byName属性进行自动装配,property的name属性值跟类的变量名一样,就可以不写以下这两步,系统进行自动装配 --> <!-- <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>使用自动装配的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> <!-- 使用byType属性进行自动装配,也可以不写下面两步,但前提是一个类只能有一个bean --> <!-- <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>@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是按照名称装配的,名称找不到了才按照类型装配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的值为一个需要扫描的包上两节我们介绍了<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"> <!-- 1. 扫描全体包用这种即可,不用过滤 --> <!-- <context:component-scan base-package="com.hkd.ioc.UserMod"></context:component-scan> --> <!-- 2. 扫描其中几个包的话需要过滤包含的类,用以下方法,注意use-default-filters的属性值要改成false --> <context:component-scan base-package="com.hkd.ioc.userMod" use-default-filters="false"> <!-- 2.1 只扫描包含类型为annotation注解类型的包为org.springframework.stereotype.Controller的那个包 --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <!-- 2.2 只扫描包含类的类型assignable,expression里写com.hkd.ioc.UserMod.service.UserServiceImpl这个类 --> <context:include-filter type="assignable" expression="com.hkd.ioc.UserMod.service.UserServiceImpl"/> </context:component-scan> <!-- 3. 扫描其中几个类需要过滤排除的类,用以下方法,注意use-default-filters的属性值要改成true(不写默认就是true) --> <context:component-scan base-package="com.hkd.ioc.UserMod"> <!-- 3.1 只扫描排除类型为annotation注解类型的包为org.springframework.stereotype.Repository的那个包 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/> <!-- 3.2 只扫描排除类的类型为assignable,expression里写com.hkd.ioc.UserMod.service.UserServiceImpl这个类 --> <context:exclude-filter type="assignable" expression="com.hkd.ioc.UserMod.service.UserServiceImpl"/> </context:component-scan> <!-- 4. 注意:包含和排除标签可以出现多个,但不能同时出现 --> </beans>面向对象是纵向继承机制 面向切面是横向抽取机制
AOP编程操作的主要对象是切面(aspect),存储公共功能的类就叫做切面切面的作用:模块化横切关注点(公共功能)在用AOP编程时,有些方法需要定义一些公共功能,这些公共功能是针对不同类需要做相同的事情而编写的,这些方法叫做横切关注点,把横切关注点抽取出来,放到一个类里,这样的类我们通常称之为切面
上图便于我们理解AOP:四个方法中都有验证参数、前置日志和后置日志三个功能,这些公共功能叫做横切关注点,把这些横切关注点抽取出来放到一个类里,这个类就是切面,最后通过AOP实现三个功能AOP的好处: 每个事物逻辑位于一个位置,代码不分散,便于维护和升级业务模块更简洁,只包含核心业务代码从每个方法中抽取出来的同一类非核心业务
将横切关注点封装成一个类,这个类就是切面
横切关注点在切面里的叫法,二者实际是一个东西,在不同位置叫法不同
被通知的对象,也就是抽取出的代码所作用到的对象
将通知应用到目标对象之后,被动态创建的对象
功能执行过程中的各个位置,一般只操作四个位置:方法调用前、方法调用后、当抛出异常和finally位置
举例说明:
首先创建一个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() { //1. 获取当前类的类加载器,用来加载代理对象所属类 ClassLoader loader = this.getClass().getClassLoader(); //2. 获取目标对象实现的所有接口的class,代理类会和目标类实现相同的接口,最终通过代理对象实现功能 Class[] interfaces = mathImpl.getClass().getInterfaces(); //3. 使用代理类,进行增强,返回的是代理后的对象。Proxy.newProxyInstance(类加载器,接口,匿名内部类) return Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() { //所有动态代理的方法调用,都会交由invoke()方法去处理 //proxy:被代理后的对象 //method:将要被执行的方法信息 //args:执行方法时需要的参数 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //通过代理对象实现功能,这时候就可以在实现功能前后加入一些其他的操作,比如下面的before和after方法 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(); //执行plus方法 int i = math.plus(1, 1); } }得出结果,成功记录了加数和结果
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
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方法1. 连接点
就一个具体的连接点而言,我们可能会关心这个连接点的一些具体信息例如:当前连接点所在方法的方法名、当前传入的参数值等等这些信息都封装在JoinPoint接口的实例对象中2. 常用的方法 joinpoint.getArgs() —— 获取实际参数数组 joinpoint.getSignature().getName() —— 获取方法名
以上通知的演示在5.4.6节的案例里
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,其值为切入点表达式 * 前置通知,作用于方法执行之前 */ @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:将方法标注为后置通知 * 作用于方法的finally语句块,即不管有没有异常都会执行 */ @After(value = "execution(* com.hkd.AspectJ.*.*(..))") public void afterMethod() { System.out.println("后置通知"); } /** * @AfterReturning:将方法标注为返回通知 * 作用于方法执行之后 * 可通过returning设置接受方法返回值的变量名 * 要想在方法中使用,必须要方法的形参中设置和变量名相同的参数名的参数 */ @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:将方法标注为异常通知(例外通知) * 作用于方法抛出异常时 * 可通过throwing设置接收方法返回的异常信息 * 在参数列表中,通过具体的异常类型,来对指定的异常信息进行操作 */ @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. 得出结果,各种通知实现成功
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); } }在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
如果使用的是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"> <!-- 引入资源文件,写法一: --> <!-- <bean class="org.springframe.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="db.properties"></property> </bean> --> <!-- 引入资源文件,写法二: --> <context:property-placeholder location="db.properties"/> <!-- druid数据库连接池创建数据源 --> <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> <!-- 通过数据源创建JdbcTemplate --> <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"> <!-- 引入资源文件,写法一: --> <!-- <bean class="org.springframe.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="db.properties"></property> </bean> --> <!-- 引入资源文件,写法二: --> <context:property-placeholder location="classpath:db.properties"/> <!-- c3p0数据库连接池创建数据源 --> <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> <!-- 通过数据源创建JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> </beans>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已增加
查询多条数据返回多个对象
@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); } }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. 配置文件需要配置事务管理器
<!-- 配置事务管理器,不管xml还是注解方式,都需要配置事务管理器 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 这个事务是管理数据源的,依赖于数据源产生的连接对象 --> <property name="dataSource" ref="dataSource"></property> </bean>A方法和B方法都有事务,当A在调用B的时候,会将A中的事务传播给B方法,B方法对于事务的处理方式就是事务的传播行为
属性名称属性值属性解释PROPAGATION_REQUIREDREQUIRED如果有事务在运行,当前的方法就在这个事务中运行,否则启动新事务,再执行方法PROPAGATION_REQUIRES_NEWREQUIRES_NEW如果方法已在事务环境中,则停止该事务,使用自己的事务;如果方法不在事务环境中,则自动启动一个新事务后再执行方法PROPAGATION_SUPPORTSSUPPORTS如果当前方法处于事务环境中,则使用该事务,否则不使用事务PROPAGATION_NOT_SUPPORTEDNOT_SUPPORTED当前方法不应该运行在事务中,如果有则将它挂起PROPAGATION_MANDATORYMANDATORY当前方法必须运行在事务内部,如果没有运行的事务,就抛出异常PROPAGATION_NEVERNEVER当前方法不应该运行在事务中,如果有运行的事务,就抛出异常PROPAGATION_NESTEDNESTED即使当前方法处于事务中,依然会启动一个新事务,并且方法在嵌套的事务中执行;即使当前执行的方法不在事务中,也会启动一个新事务,然后执行该方法1. 注解方式
直接在要处理事务的类或者方法前,加上注解,注解中括号里添加属性propagation,如下
@Transactional(propagation = Propagation.REQUIRES_NEW)
2. 配置方式
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表时,就多出了一些行
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别
读未提交:READ UNCOMMITTED 允许A读取B未提交的修改读已提交:READ COMMITTED 要求A只能读取B已提交的修改可重复读:REPEATABLE READ 确保A可以多次从一个字段中读取到相同的值,即A执行期间禁止其它事务对这个字段进行更新串行化:SERIALIZABLE 确保A可以多次从一个表中读取到相同的行,在A执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下 能力脏读不可重复读幻读READ UNCOMMITTED√√√READ COMMITTED×√√REPEATABLE READ××√SERIALIZABLE×××1. 注解方式
直接在@Transactional的isolation属性中设置隔离级别
@Transactional(isolation = Isolation.DEFAULT)
2. 配置方式
timeout:在事务强制回滚前,最多可以执行(等待)的时间
readOnly:指定当前事务中的一系列操作是否为只读;
如果设置为只读,不管事务中有没有写的操作,MySQL都会在请求访问数据的时候,不加锁,提高性能;如果有写的操作,建议一定不能设置只读1. 开启事务注解
<!-- 开启注解驱动,即对事务相关的注解进行扫描,解析含义并执行功能 --> <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>2. 在需要使用事务的bean类或者bean类方法上使用注解@Transactional
如果在方法上,则对这个方法有效;如果在类上,则对类中的所有方法有效其中的一些属性,已经在之前的讲解中展示如何书写