声明:
本文全文手写,代码全部手写,也希望大家,可以做一遍,最起码调试一遍,这样比看的效果好的多,本文的spring使用的是5.0.4版本,ide使用的是IntelliJ IDEA,不足和错误之处还请大家指出,谢谢!!
一、spring是什么
Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson创建。简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架
- spring百度百科
- spring-framework
二、spring快速入门
什么是spring?
- 首先我们了解到struts是web框架(jsp/action/actionform)
- hibernate是 orm框架处于持久层
spring是容器框架,用于配置bean,并维护bean之间的关系的框架
- spring中的bean:是一个很重要的概念,这里的bean可以是Java中的任何一种对象:JavaBean/service/action/数据源/dao等等
- spring中的ioc(inverse of control 控制反转)
spring中的di(dependency injection 依赖注入)
接下来看一个层次框架图:
说明:- web层: struts充当web层,接管jsp,action,表单,主要体现出mvc的数据输入,数据的处理,数据的显示分离
- model层: model层在概念上可以理解为包含了业务层,dao层,持久层,需要注意的是,一个项目中,不一定每一个层次都有
- 持久层:体现oop,主要解决关系模型和对象模型之间的阻抗
入门项目:
- 创建java项目(web中也可以使用)
- 创建lib文件夹引入spring的开发最小包(最小配置,spring.jar(该包把最常用的包都包括),commons-logging.jar(日志包))
- 创建配置文件,一般在src目录下
- 配置bean
说明:<bean></bean>
这对标签元素的作用:当我们加载spring框架时,spring就会自动创建一个bean对象,并放入内存相当于我们常规的new一个对象,而<property></property>
中的value则是实现了“对象.set方法”,这里也体现了注入了概念 - 然后在java文件(测试文件)中调用
接下来看具体的项目:
说明:这是我的目录结构,其中我使用了ide整合了jar包,如果是手动创建时只需将jar包导入到项目里即可User.java
package com.nuc.Bean;
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String sayHello(){
System.out.println("hello"+ name);
return "true";
}
}
抛开spring框架,使用传统方式实现在测试类中调用sayHello方法:
这样,没有异议吧。
接下来使用spring调用该方法
结果为小强,是因为上面的配置文件中配置value为小强
这样一个基本的项目就完成了~
接下来是细节:
创建User2这个类
package com.nuc.Bean; public class User2 { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void sayBye(){ System.out.println("bye"+name); } }
- 在User中新增
private User2 user2;
并在sayHello中调用sayBye方法
- 执行test类报出错误,这是由于user2未注入
- 在配置文件中配置注入
注意点细节都已经在图中注释表明 - 再次运行测试类
spring运行原理图
入门项目小结:
spring实际上是容器框架,可以配置各种bean,并可以维护bean与bean的关系,当我们需要使用某个bean的时候,我们可以直接getBean(id),使用即可
现在我们来回答什么是spring这个问题
- spring是一个容器框架,它可以接管web层,业务层,dao层,持久层的各个组件,并且可以配置各种bean, 并可以维护bean与bean的关系,当我们需要使用某个bean的时候,我们可以直接getBean(id),使用即可
接下来对几个重要的概念做说明:
- ioc是什么?
- ioc(inverse of control)控制反转:所谓反转就是把创建对象(bean)和维护对象(bean)的关系的权利从程序转移到spring的容器(spring-config.xml)
- di是什么?
- di(dependency injection)依赖注入:实际上di和ioc是同一个概念,spring的设计者,认为di更准确的表示spring的核心
实质上学习框架就是,最重要的就是学习各个配置
三、接口编程
- spring就提倡接口编程,在配合di技术就可以达到层与层解耦的目的
举案例说明:
这个项目实现的是大小写转换
基本思路:- 创建一个接口
- 创建两个类实现接口
- 配置bean
- 使用
下面是我的项目目录
ChangeLetter.java
package com.nuc;
public interface ChangeLetter {
public String change();
}
LowerLetter.java
package com.nuc;
public class LowerLetter implements ChangeLetter {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public String change(){
//大小字母转小写
return str.toLowerCase();
}
}
UpperLetter.java
package com.nuc;
public class UpperLetter implements ChangeLetter {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public String change(){
//把小写字母转成大写
return str.toUpperCase();
}
}
spring-config.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="changeLetter" class="com.nuc.UpperLetter">-->
<!--<property name="str">-->
<!--<value>sjt</value>-->
<!--</property>-->
<!--</bean>-->
<bean id="changeLetter" class="com.nuc.LowerLetter">
<property name="str" value="SJT"/>
</bean>
</beans>
说明:其中的两个bean id名相同是为了调试方便,可通过注释来调试
Test.java
package com.nuc;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
//调用change不使用接口
// UpperLetter changeLetter = (UpperLetter) ac.getBean("changeLetter");
// System.out.println(changeLetter.change());
//使用接口
ChangeLetter changeLetter = (ChangeLetter)ac.getBean("changeLetter");
System.out.println(changeLetter.change());
}
}
以上这个案例,我们可以初步体会到,di和接口编程,的确可以减少层(web层)和层(业务层)之间的耦合度,尽管看起来似乎没什么改变,而且好像麻烦了一些,但是当项目大了以后,这种耦合度的降低就显得尤为重要
四、获取Bean
ApplicationContext 应用上下文容器取
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
当这句代码被执行,spring-config.xml文件中配置的bean就会被实例化。(但要注意bean的生命周期要为singleton),也就是说,不管没有getBean(),使用上下文容器获取bean,就会实例化该bean
Bean工厂容器取
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
这句代码被执行,spring-config.xml文件中配置的bean不会被实例化,即光实例化容器,并不会实例化bean
而是在执行以下代码时才会被实例化,即使用bean的时候:
factory.getBean("beanId");
如何验证上述说法呢?每一个java类都有一个默认的构造方法。给这个构造方法输出一句话。具体如下:
- 创建一个类,类有一个属性,装配,该属性
- 重写该类的构造方法,输出bean被创建
- 创建测试类,测试
使用ApplicationContext应用上下文容器
使用bean工厂
可以看到,这一行代码,并不能时bean实例化,接下来加factory.getBean(“student”);试试
这样就是bean实例化了那么在实际开发中选择哪种方式?
在移动开发中,即项目运行在移动设备中使用BeanFactory(节约内存,所以,你想节约内存开发就是使用bean工厂,但速度会受影响),但大部分的项目都是使用ApplicationContext(可以提前加载,缺点是消耗一点内存)
贴一张bean的生命周期图:
接下来我们验证前两种作用域:
第一种
结果
可以看到stu1和stu2拥有相同的地址,接下来测试第二种
测试结束!
至于后三种是在web开发中才有实际意义!
五、三种获取ApplicationContext对象引用的方法
- ClassPathXmlApplicationContext (从类路径中加载)
- 这个不在赘述,上面所有例子都是利用这种方式加载的
- FileSystemXmlApplicationContext (从文件系统中加载)
可以看到是没有问题的,需要注意的是,文件路径为绝对路径,且注意使用转义符,直接使用“C:\sjt\idea\code\spring\spring-interface\src”,会报错,需要将“\”转义,但实际开发中应用不多,了解即可 - XmlWebApplicationContext (从web系统中加载)
- 这种方式,注意,在tomcat启动时就会加载,此处不做说明,在web应用中说明
六、再谈Bean的生命周期
生命周期是一个重点吗?答案是肯定的!!
- 不了解生命周期难道不能开发了吗?那自然是可以的,但如果你想实现更加高级的功能,你不了解那可能是会出问题的!而在面试过程中也是经常提及的。
接下里我们举例子说明
生命周期分为以下几步:
- 1、实例化
- 当我们加载sping-config.xml文件时,bean就会被实例化到内存(前提是scope=singleton)
- 2、设置属性值
- 调用set方法设置属性,前提是有对应的set方法
- 3、如果你调用BeanNameAware的set’Bean’Name()方法
- 这是个接口,该方法可以给出正在被调用的bean的id
- 4、如果你调用BeanFactoryAware的setBeanFactory()方法
- 这也是个接口,该方法可以传递beanFactory
- 5、如果你调用了ApplicationContextAeare的setApplicationContext()方法
- 同样为接口,该方法传递一个ApplicationContext
- 6、BeanPostProcessor的预初始化方法Before
- 这个东西很厉害了,可以叫做后置处理器,它不是接口,具体细节,代码体现
- 7、如果你调用了InitializingBean的afterPropertiesSet()方法
- 8、调用自己的init方法,具体为在bean中有一个属性inin-method=”init”
- 9、BeanPostProcessor的方法After
- 10、使用bean,体现为调用了sayHi()方法
- 11、容器关闭
- 12、可以实现DisposableBean接口的destory方法
- 13、可以在调用自己的销毁方法,类似于8
实际开发过程中,并没有这么复杂,常见过程为,1,2,6,9,10,11
接下来看代码
MyBeanPostProcessor.java- 1、实例化
package com.nuc.BeanLife;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第九步,postProcessAfterInitialization方法被调用");
return null;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第六步,postProcessBeforeInitialization方法被调用");
System.out.println("第六步,"+bean+"被创建的时间为"+new java.util.Date());
/*
在这里,能做的事情可就不止上面的这么简单的一句输出了,它还可以过滤每个对象的ip
还可以给所有对象添加属性或者函数,总之就是所有对象!
其实,这里体现了AOP编程的思想,AOP呢就是面向切成编程(针对所有对象编程)
*/
return null;
}
}
PersonService.java
package com.nuc.BeanLife;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class PersonService implements BeanNameAware,BeanFactoryAware,ApplicationContextAware,InitializingBean,DisposableBean{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("第二步调用set方法");
}
public void sayHi(){
System.out.println("第十步,hi"+ name);
}
public PersonService(){
System.out.println("第一步,实例化bean");
}
@Override
public void setBeanName(String arg0){
//该方法可以给出正在被调用的bean的id
System.out.println("第三步,setBeanName被调用,调用的id名为:"+arg0);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
//该方法可以传递beanFactory
System.out.println("第四步,setBeanFactory被调用,beanFactory为:"+beanFactory);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//该方法传递一个ApplicationContext
System.out.println("第五步,调用setApplicationContext方法:"+applicationContext);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("第七步,调用afterPropertiesSet()方法");
}
public void init(){
System.out.println("第八步、调用我自己的init()方法");
}
@Override
public void destroy() throws Exception {
//关闭数据连接,socket,文件流,释放资源
//这个函数的打印你看不到,应为
System.out.println("第十步,销毁方法(但不建议使用这种方式释放资源)");
}
public void destory(){
// 也看到不
System.out.println("销毁");
}
}
Test.java
package com.nuc.BeanLife;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
PersonService person1= (PersonService) ac.getBean("personService");
person1.sayHi();
}
}
spring-config.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="personService" init-method="init" destroy-method="destroy" scope="singleton" class="com.nuc.BeanLife.PersonService">
<property name="name" value="sjt"></property>
</bean>
<bean id="personService2" class="com.nuc.BeanLife.PersonService">
<property name="name" value="sjt2"></property>
</bean>
<!--配置自己的后置处理器,优点类似filter-->
<bean id="myBeanPostProcessor" class="com.nuc.BeanLife.MyBeanPostProcessor">
</bean>
</beans>
测试结果
4月 17, 2018 4:57:26 下午
信息: Loading XML bean definitions from class path resource [spring-config.xml]
第一步,实例化bean
第二步调用set方法
第三步,setBeanName被调用,调用的id名为:personService
第四步,setBeanFactory被调用,beanFactory为:org.springframework.beans.factory.support.DefaultListableBeanFactory@ae13544: defining beans [personService,personService2,myBeanPostProcessor]; root of factory hierarchy
第五步,调用setApplicationContext方法:org.springframework.context.support.ClassPathXmlApplicationContext@646d64ab: startup date [Tue Apr 17 16:57:26 CST 2018]; root of context hierarchy
第六步,postProcessBeforeInitialization方法被调用
第六步,com.nuc.BeanLife.PersonService@2e6a8155被创建的时间为Tue Apr 17 16:57:27 CST 2018
第七步,调用afterPropertiesSet()方法
第八步、调用我自己的init()方法
第九步,postProcessAfterInitialization方法被调用
第一步,实例化bean
第二步调用set方法
第三步,setBeanName被调用,调用的id名为:personService2
第四步,setBeanFactory被调用,beanFactory为:org.springframework.beans.factory.support.DefaultListableBeanFactory@ae13544: defining beans [personService,personService2,myBeanPostProcessor]; root of factory hierarchy
第五步,调用setApplicationContext方法:org.springframework.context.support.ClassPathXmlApplicationContext@646d64ab: startup date [Tue Apr 17 16:57:26 CST 2018]; root of context hierarchy
第六步,postProcessBeforeInitialization方法被调用
第六步,com.nuc.BeanLife.PersonService@6221a451被创建的时间为Tue Apr 17 16:57:27 CST 2018
第七步,调用afterPropertiesSet()方法
第九步,postProcessAfterInitialization方法被调用
第十步,hisjt
Process finished with exit code 0
动手做一遍是最好的选择!!
使用bean工厂获取bean对象,生命周期是和上下文获取的不一样的,如下图
其中我只装配了一个bean,可见执行步骤的短缺
七、装配Bean
使用xml装配
- 上下文定义文件的根元素是
<beans></beans>
,有多个子元素<bean></bean>
,每个<bean>
元素定义了bean如何被装配到spring容器中 - 对子元素bean最基本的配置包括bean的ID和它的全称类名
- 对bean的scope装配,默认情况下为单例模式,具体情况上面已经说过,建议查看文档,更加具体,尽量不要使用原型bean,即scope设置为propotype,这样子会对性能有较大的影响
bean的init-metho和destory-method的书写,在生命周期那一块儿已经很清楚了,此处不再赘述,需要说明的是,可以通过注解的方式来配置,而不是在bean中使用init-metho和destory-method属性
注入集合类型的数据,例如,map,set,list,数组,Properties….
接下来举例子
目录结构:
Department.java
package com.nuc; import java.util.List; import java.util.Map; import java.util.Set; public class Department { private String name; private String []empName;//这里int的数组也可以注入成功 private List<Employee> empList; private Map<String,Employee> empMap; private Properties pp; public Properties getPp() { return pp; } public void setPp(Properties pp) { this.pp = pp; } public Set<Employee> getEmpSet() { return empSet; } public void setEmpSet(Set<Employee> empSet) { this.empSet = empSet; } private Set<Employee> empSet; public List<Employee> getEmpList() { return empList; } public void setEmpList(List<Employee> empList) { this.empList = empList; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String[] getEmpName() { return empName; } public void setEmpName(String[] empName) { this.empName = empName; } public Map<String, Employee> getEmpMap() { return empMap; } public void setEmpMap(Map<String, Employee> empMap) { this.empMap = empMap; } }
Employee.java
package com.nuc; public class Employee { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
Test.java
package com.nuc; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; public class Test { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml"); Department department = (Department)ac.getBean("department"); System.out.println(department.getName()); // 取集合 for(String empName:department.getEmpName()){ System.out.println(empName); } System.out.println("取list..."); for (Employee e:department.getEmpList()){ System.out.println("name="+e.getName()); } System.out.println("取set..."); for (Employee e:department.getEmpSet()){ System.out.println("name="+e.getName()); } System.out.println("迭代器取map..."); //1.迭代器 Map<String,Employee> employeeMap = department.getEmpMap(); Iterator iterator = employeeMap.keySet().iterator(); while (iterator.hasNext()){ String key = (String)iterator.next(); Employee employee=employeeMap.get(key); System.out.println("key="+key+" "+ employee.getName()); } System.out.println("entry取map..."); //2.简洁(建议使用这种方式) for (Entry<String,Employee> entry:department.getEmpMap().entrySet()){ System.out.println(entry.getKey()+" "+entry.getValue().getName()); } } System.out.println("通过properties取数据"); Properties properties = department.getPp(); for (Entry<Object,Object> entry:properties.entrySet()){ System.out.println(entry.getKey().toString()+" "+entry.getValue()); } }
spring-config.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="department" class="com.nuc.Department"> <property name="name" value="财务部"></property> <!--给数组注入--> <property name="empName"> <list> <value>小明</value> <value>小花</value> </list> </property> <!--给list注入--> <!--list可以存放相同的对象,并当作不同对象输出--> <property name="empList"> <list> <ref bean="employee1"></ref> <ref bean="employee2"></ref> </list> </property> <!--给set注入--> <!--set集合不可以存放相同对象--> <property name="empSet"> <set> <ref bean="employee1"></ref> <ref bean="employee2"></ref> </set> </property> <!--给map注入--> <!--输出的对象取决于key值,key值不同,对象相同也可以打出--> <!--当key值相同时,对象相同或者不同都打出最后一个key所对应的对象--> <property name="empMap"> <map> <entry key="1" value-ref="employee1"></entry> <entry key="2" value-ref="employee2"></entry> <entry key="3" value-ref="employee2"></entry> </map> </property> <!--给属性集合注入--> <property name="pp"> <props> <prop key="1">hello</prop> <prop key="2">world</prop> </props> </property> </bean> <bean id="employee1" class="com.nuc.Employee"> <property name="name" value="北京"></property> </bean> <bean id="employee2" class="com.nuc.Employee"> <property name="name" value="太原"></property> </bean> </beans>
测试结果:
注意点,细节都已在代码中注释!
- 内部bean
- 具体自行了解,实际中应用不多,不符合重用度高的原则
- 继承配置bean
- 举例说明:
结构图:
Student.java
- 举例说明:
- 上下文定义文件的根元素是
package com.nuc.inherit;
public class Student {
protected String name;
protected int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Gradate.java
package com.nuc.inherit;
public class Gradate extends Student {
private String degree;
public String getDegree() {
return degree;
}
public void setDegree(String degree) {
this.degree = degree;
}
}
spring-config.java
<?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.nuc.inherit.Student">
<property name="name" value="sjt"></property>
<property name="age" value="22"></property>
</bean>
<!--配置gradate对象-->
<bean id="gradate" parent="student" class="com.nuc.inherit.Gradate">
<!--如果子类重新赋值,则覆盖父类的-->
<property name="name" value="小明"></property>
<property name="degree" value="博士"></property>
</bean>
</beans>
Test2.java
package com.nuc.inherit;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test2 {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("com/nuc/inherit/spring-config.xml");
Gradate gradate = (Gradate) ac.getBean("gradate");
System.out.println(gradate.getName()+" "+gradate.getAge()+" "+gradate.getDegree());
}
}
- 以上我们都是用set注入依赖的,下面介绍构造函数注入依赖
<bean name="user" class="com.nuc.Bean.User">
<!--通过constructor-arg标签完成了对构造方法的传参-->
<!--如果是属性是类类型,则使用ref=""-->
<constructor-arg index="0" type="java.lang.String" value="小强"></constructor-arg>
<constructor-arg index="1" type="java.lang.String" value="男"></constructor-arg>
<constructor-arg index="2" type="int" value="20"></constructor-arg>
</bean>
当然对应的User要有相应的构造方法。
set注入的缺点是无法清晰的表达哪个属性是必须的,哪些是可选的,构造器注入的优势,是可以通过构造强制依赖关系,不可能实例化不完全或者不能使用的bean
但其实实际开发中还是set注入较多,即property注入
- bean的自动装配:
接下来是实例:
目录图
Dog.java
package com.nuc.autowire;
public class Dog {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Master.java
package com.nuc.autowire;
public class Master {
private String name;
private Dog dog;
private Master(Dog dog){
//为了自动装配的constructor
this.dog= dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
}
Test.java
package com.nuc.autowire;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("com\\nuc\\autowire\\beans.xml");
Master master = (Master)ac.getBean("master");
System.out.println(master.getName()+"养了只狗,它的名字叫"+ master.getDog().getName()+",他今年"+master.getDog().getAge()+"岁了");
}
}
beans.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">
<!--配置master对象-->
<bean id="master" class="com.nuc.autowire.Master" autowire="constructor">
<property name="name" value="sjt"></property>
<!--传统方式-->
<!--<property name="dog" ref="dog"></property>-->
</bean>
<!--配置dog对象,byName时使用-->
<!--<bean id="dog" class="com.nuc.autowire.Dog">-->
<!--<property name="name" value="小黄"></property>-->
<!--<property name="age" value="2"></property>-->
<!--</bean>-->
<!--配置dog对象,byType时使用-->
<!--<bean id="dog11" class="com.nuc.autowire.Dog">-->
<!--<property name="name" value="小黄"></property>-->
<!--<property name="age" value="2"></property>-->
<!--</bean>-->
<!--配置dog对象,constructor时使用-->
<bean id="dog22" class="com.nuc.autowire.Dog">
<property name="name" value="小黄"></property>
<property name="age" value="2"></property>
</bean>
</beans>
- autodetect:是在constructor和byType之间选一种
default:这种方式在文档中没有提及,需要在beans中指定,当你在beans中指定以后,所有的bean都是你所指定的装配方式,如果没有指定,则默认为no,所以,no之所以为默认指定装配方式,其实是从beans那里来的
其实在实际开发中,很少用到自动装配, 一般都是手动set装配的(property),而且自动装配也是在bean中没有配置才取执行自动装配的- spring本身提供的bean
- 分散配置
- spring本身提供的bean
八、AOP编程(难点)
aop:aspect oriented programming(面向切面编程),它是对一类对象或所有对象编程。
- 核心:在不增加代码的基础上,还增加新功能
- 提醒:aop编程,实际上是开发框架本身用的多,开发中不是很多,将来会很多
- 初步理解:面向切面:其实是,把一些公共的“东西”拿出来,比如说,事务,安全,日志,这些方面,如果你用的到,你就引入。
接下来通过例子来理解这个抽象的概念,概念稍后再说
步骤:
拿前置通知打比方,后来还会有,后置通知,环绕通知,异常通知,引入通知- 定义接口
- 编写对象(被代理对象=目标对象)
- 编写通知(前置通知目标方法调用前调用)
- 在beans.xml中配置
- 配置被代理对象
- 配置通知
- 配置代理对象(是proxyFactoryBean的对象实例)
- 配置代理接口集
- 织入通知
- 配置被代理对象
接下来看代码:
目录结构:
TestServiceInter.java(interface)
package com.nuc.Aop;
public interface TestServiceInter {
public void sayHello();
}
TestServiceInter2.java(interface)
package com.nuc.Aop;
public interface TestServiceInter2 {
public void sayBye();
}
TestService.java
package com.nuc.Aop;
public class TestService implements TestServiceInter,TestServiceInter2{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void sayHello(){
System.out.println("hi "+name);
}
@Override
public void sayBye() {
System.out.println("bye "+name);
}
}
MyMethodBeforeAdvice.java
package com.nuc.Aop;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
//前置通知
@Override
public void before(Method method, Object[] objects, Object o)
throws Throwable {
//method:被调用方法的名字
//objects:给method传递的参数
//o:目标对象
System.out.println("记录日志。。。"+method.getName());
}
}
beans.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="testService" class="com.nuc.Aop.TestService">
<property name="name" value="sjt"/>
</bean>
<!--配置前置通知-->
<bean id="myMethodBeforeAdvice" class="com.nuc.Aop.MyMethodBeforeAdvice"></bean>
<!--配置代理对象-->
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--配置代理接口-->
<property name="proxyInterfaces">
<list>
<value>com.nuc.Aop.TestServiceInter</value>
<value>com.nuc.Aop.TestServiceInter2</value>
</list>
</property>
<!--把通知织入到代理对象-->
<property name="interceptorNames">
<!--相当于把myMethodBeforeAdvice前置通知和代理对象关联起来-->
<value>myMethodBeforeAdvice</value>
</property>
<!--配置被代理对象,可以指定-->
<property name="target" ref="testService"></property>
</bean>
</beans>
Test.java
package com.nuc.Aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("com/nuc/Aop/beans.xml");
TestServiceInter testService = (TestServiceInter)ac.getBean("proxyFactoryBean");
testService.sayHello();
//当一个类继承多个接口,那么他们之间可以互转
((TestServiceInter2)testService).sayBye();
}
}
测试结果
AOP的术语
- 切面:要实现交叉功能,是系统模块化的一个切面领域,如记录日志
- 连接点:应用程序执行过程中插入切面的地点,可以是方法调用,异常抛出,或者要修改的字段
- 连接点是一个静态的概念
- 通知: 切面的实际实现,它通知系统的新行为,如日志通知包含了实现日志功能的代码,如向日志文件写日志,通知在连接点插入应用系统中。
- 切入点:定义了通知应该应用在哪些连接点,通知可以应用到AOP框架支持的任何连接点
- 切入点是动态概念,当通知应用了连接点,连接点就变成了切入点
- 引入:为类添加新方法和属性
- 目标对象:被通知的对象,既可以是你编写的类,也可以是第三方类
- 代理:将通知应用到目标对象后创建后的对象,应用系统的其他部分不用为了支持代理对象而改变
- spring的两种代理:
- 若目标对象实现了若干个接口,spring使用JDK的java.lang.reflect.Proxy类代理
- 若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类
- spring的两种代理:
- 织入:将切面应用到目标对象从而创建一个新代理对象的过程,织入发生在目标对象生命周期的多个点上
- 编译期:切面在目标对象编译时织入,这需要一个特使的编译器
- 类装载期:切面在目标对象被载入jvm时织入,这需要一个特殊的类加载器
- 运行期:切面在应用系统运行时切入
接下来引入后置通知,环绕通知,异常通知,引用通知
类似于前置通知,前三者需要继承一种接口,引用通知直接配置
MyAfterReturningAdvice.java
package com.nuc.Aop;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class MyAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects,
Object o1)
throws Throwable {
//后置通知
//o:前面函数的返回值
//method:哪个方法被调用
//objects:调用方法的参数
//o1:目标对象
System.out.println("后置通知:调用结束,关闭资源。");
}
}
MyMethodInterceptor.java
package com.nuc.Aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//环绕通知
System.out.println("环绕通知:进入函数体,调用方法前");
Object obj = methodInvocation.proceed();
System.out.println("环绕通知:完成调用");
return obj;
}
}
MyThrowsAdvice.java
package com.nuc.Aop;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
public class MyThrowsAdvice implements ThrowsAdvice {
//异常通知
//ThrowsAdvice这个接口是标识性接口,没有任何方法
public void afterThrowing(Method m,Object[] os,Object target,Exception e){
System.out.println("异常通知:出问题了:"+e.getMessage());
}
}
beans.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="testService" class="com.nuc.Aop.TestService">
<property name="name" value="sjt"/>
</bean>
<!--配置前置通知-->
<bean id="myMethodBeforeAdvice" class="com.nuc.Aop.MyMethodBeforeAdvice"></bean>
<!--配置后置通知-->
<bean id="myAfterReturningAdvice" class="com.nuc.Aop.MyAfterReturningAdvice"></bean>
<!--配置环绕通知-->
<bean id="myMethodInterceptor" class="com.nuc.Aop.MyMethodInterceptor"></bean>
<!--配置异常通知-->
<bean id="myThrowsAdvice" class="com.nuc.Aop.MyThrowsAdvice"></bean>
<!--定义前置通知的切入点(引用通知)-->
<bean id="myMethodBeforeAdviceFilter" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="myMethodBeforeAdvice"></property>
<property name="mappedNames">
<list>
<!--这里支持使用正则表达式匹配-->
<!--配置了sayHello使用前置通知过滤-->
<value>sayHello</value>
</list>
</property>
</bean>
<!--配置代理对象-->
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--配置代理接口-->
<property name="proxyInterfaces">
<list>
<value>com.nuc.Aop.TestServiceInter</value>
<value>com.nuc.Aop.TestServiceInter2</value>
</list>
</property>
<!--把通知织入到代理对象-->
<property name="interceptorNames">
<list>
<!--相当于把myMethodBeforeAdvice前置通知和代理对象关联起来-->
<!--使用自定义切入点-->
<value>myMethodBeforeAdviceFilter</value>
<!--织入后置通知-->
<value>myAfterReturningAdvice</value>
<!--织入环绕通知-->
<value>myMethodInterceptor</value>
<!--织入异常通知-->
<value>myThrowsAdvice</value>
</list>
</property>
<!--配置被代理对象,可以指定-->
<property name="target" ref="testService"></property>
</bean>
</beans>
TestService.java
如图这一处变动
总之呢就是一个配置 -> 织入的过程
运行结果:
可以看到前置通知和后置通知,似乎能够识别方法,事实上也是这样的(spring框架内置)。而且sayBay()也得到了应用。这正是,我们前面所提到了,AOP是对一类或所有对象编程的体现,又由于异常通知的配置,有了异常,由于引用通知的配置,致使sayBay的前置通知及后续无法通知。
正常结果(配置引用通知):
总结
spring框架呢,其实就是学习了一大堆的配置,还有几个spring比较新颖的“思想”,IOC,AOP,这些技术。主要还是spring-config.xml文件的配置,之所以后来改成beans,是因为,这个文件就是对bean的配置!这套教程,是根据韩老师的视频总结的,全文基本都是要点,没有水货。。。(自我认为),因为韩老师将的很到位,很深刻,所以教程自然差不了
为时5天的总结就结束了。笔者后期还会推出springMVC,Hibernate,望支持~