Spring教程检视阅读
地址
可供参考的教程
《菜鸟学 SSH》
《Spring Boot 那些事》
《初识 Spring Security》
《Spring MVC 4.2.4.RELEASE 中文文档》
Spring 框架的参考手册
Spring IoC 容器
BeanFactory 和相关的接口,比如BeanFactoryAware、DisposableBean、InitializingBean,仍旧保留在 Spring 中,主要目的是向后兼容已经存在的和那些 Spring 整合在一起的第三方框架。
在 Spring 中,有大量对 BeanFactory 接口的实现。其中,最常被使用的是 XmlBeanFactory 类。这个容器从一个 XML 文件中读取配置元数据,由这些元数据来生成一个被配置化的系统或者应用。
在资源宝贵的移动设备或者基于 applet 的应用当中, BeanFactory 会被优先选择。否则,一般使用的是 ApplicationContext,除非你有更好的理由选择 BeanFactory。
ApplicationContext 包含 BeanFactory 所有的功能,一般情况下,相对于 BeanFactory,ApplicationContext 会更加优秀。当然,BeanFactory 仍可以在轻量级应用中使用,比如移动设备或者基于 applet 的应用程序。
最常被使用的 ApplicationContext 接口实现:
Spring Bean
下图表达了Bean 与 Spring 容器之间的关系:
Spring 配置元数据
Spring IoC 容器完全由实际编写的配置元数据的格式解耦。有下面三个重要的方法把配置元数据提供给 Spring 容器:
Bean 的作用域
当在 Spring 中定义一个 bean 时,你必须声明该 bean 的作用域的选项。例如,为了强制 Spring 在每次需要时都产生一个新的 bean 实例,你应该声明 bean 的作用域的属性为 prototype。同理,如果你想让 Spring 在每次需要时都返回同一个bean实例,你应该声明 bean 的作用域的属性为 singleton。
Spring 框架支持以下五个作用域,分别为singleton、prototype、request、session和global session,5种作用域说明如下所示,
注意,如果你使用 web-aware ApplicationContext 时,其中三个是可用的。
作用域 描述
singleton 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值
prototype 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()
request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境
global-session 一般用于Portlet应用环境,该运用域仅适用于WebApplicationContext环境
singleton 作用域:
默认是singleton,在单例情况下,bean对象在某处的改变会影响到后面或者正在使用该bean的结果。因为一变全变。而prototype则每次获取的都是一个新的实例,所有对某个bean实例改变只对该bean有效,对其他值不变,其他bean创建时都是带默认的创建值状态。
prototype 作用域
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。
Spring Bean 生命周期
Bean的生命周期可以表达为:Bean的定义——Bean的初始化——Bean的使用——Bean的销毁
为了定义安装和拆卸一个 bean,我们只要声明带有 init-method 和/或 destroy-method 参数的 。
建议你不要使用 InitializingBean 或者 DisposableBean 的回调方法,因为 XML 配置在命名方法上提供了极大的灵活性。
如果你有太多具有相同名称的初始化或者销毁方法的 Bean,那么你不需要在每一个 bean 上声明初始化方法和销毁方法。框架使用 元素中的 default-init-method 和 default-destroy-method 属性提供了灵活地配置这种情况 。
例子:
注意:当bean声明作用域为scope="prototype" 原型时,是不会执行destroy-method="destroy"方法,调用
context.registerShutdownHook();是不会执行的,因为对于容器来说原型的bean没有被销毁的概念,要用的时候就创建一个,只有当scope="singleton" 单例时才会执行这个destroy()钩子方法。
public class Message { //在bean对象中声明初始化和结束工作后的方法 public void init(){ System.out.println("----------------init-----------------"); } public void destroy(){ System.out.println("----------------destroy-----------------"); } } 在配置bean实例时加入init-method 和 destroy-method <bean name="message" class="com.self.Message" scope="singleton" init-method="init" destroy-method="destroy"> <property name="msg" value="hello lairf"></property> </bean> <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-3.0.xsd" default-init-method="init" default-destroy-method="destroy">
Spring Bean 后置处理器
Bean 后置处理器允许在调用初始化方法前后对 Bean 进行额外的处理。
BeanPostProcessor 接口定义回调方法,你可以实现该方法来提供自己的实例化逻辑,依赖解析逻辑等。你也可以在 Spring 容器通过插入一个或多个 BeanPostProcessor 的实现来完成实例化,配置和初始化一个bean之后实现一些自定义逻辑回调方法。
你可以配置多个 BeanPostProcessor 接口,通过设置 BeanPostProcessor 实现的 Ordered 接口提供的 order 属性来控制这些 BeanPostProcessor 接口的执行顺序。
BeanPostProcessor 可以对 bean(或对象)实例进行操作,这意味着 Spring IoC 容器实例化一个 bean 实例,然后 BeanPostProcessor 接口进行它们的工作。
ApplicationContext 会自动检测由 BeanPostProcessor 接口的实现定义的 bean,注册这些 bean 为后置处理器,然后通过在容器中创建 bean,在适当的时候调用它。
举例:
实现 BeanPostProcessor 的类获得到的是可以对所有的类都作用的后置处理器(注意需要在beans实例化容器配置文件中配置实例化,才能生效),有点面向切面的感觉,是统一的对所有实例的操作,该接口有两个方法实现:postProcessBeforeInitialization() 和 postProcessAfterInitialization()分别对实例初始化流程的前后进行逻辑处理。且在init-method方法之前,毕竟init-method是在bean实例化后执行的。
public class BeforeInitMessage implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object o, String s) throws BeansException { System.out.println("----------------BeforeInitMessage begin--------------------"+s); return o; } @Override public Object postProcessAfterInitialization(Object o, String s) throws BeansException { System.out.println("----------------BeforeInitMessage after--------------------"+s); return o; } } default-init-method="init" default-destroy-method="destroy" <bean name="message" class="com.self.Message" scope="singleton" > <property name="msg" value="hello lairf"></property> </bean> <bean name="beforeInitMessage" class="com.self.BeforeInitMessage"/> <bean class="com.self.PhoneCall" name="phoneCall" scope="singleton" > <property name="phoneNumber" value="18959967751"/> </bean> public class MainApp { public static void main(String[] args) { AbstractApplicationContext context = new ClassPathXmlApplicationContext("spring/appcontext-core.xml"); Message message = (Message)context.getBean("message"); System.out.println("=============================message==="+message.getMsg()); PhoneCall phoneCall = (PhoneCall) context.getBean("phoneCall"); System.out.println("=============================message==="+phoneCall.getPhoneNumber()); context.registerShutdownHook(); } }
输出:
----------------BeforeInitMessage begin--------------------message ----------------init----------------- ----------------BeforeInitMessage after--------------------message ----------------BeforeInitMessage begin--------------------phoneCall ---------------- PhoneCall init----------------- ----------------BeforeInitMessage after--------------------phoneCall =============================message===hello lairf =============================message===18959967751 ----------------PhoneCall destroy----------------- ----------------destroy-----------------
Spring Bean 定义继承
子 bean 的定义继承父定义的配置数据。子定义可以根据需要重写一些值,或者添加其他值。
Spring Bean 定义的继承与 Java 类的继承无关,但是继承的概念是一样的。你可以定义一个父 bean 的定义作为模板和其他子 bean 就可以从父 bean 中继承所需的配置。
当你使用基于 XML 的配置元数据时,通过使用父属性,指定父 bean 作为该属性的值来表明子 bean 的定义。
Bean 定义模板
你可以创建一个 Bean 定义模板,不需要花太多功夫它就可以被其他子 bean 定义使用。在定义一个 Bean 定义模板时,你不应该指定类的属性,而应该指定带 true 值的抽象属性,即abstract="true"
父 bean 自身不能被实例化,因为它是不完整的,而且它也被明确地标记为抽象的。当一个定义是抽象的,它仅仅作为一个纯粹的模板 bean 定义来使用的,充当子定义的父定义使用。
实例:
<bean id="templateBean" abstract="true"> <property name="msg" value="hello world"></property> </bean> <bean name="message" class="com.self.Message" scope="singleton" > <property name="msg" value="hello lairf"></property> </bean> <bean name="textMessage" class="com.self.TextMessage" parent="templateBean">
输出
=============================message===hello lairf =============================message1===hello world
Spring 依赖注入
Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系。
基于构造函数和基于 setter 方法的 两种DI方法。
序号 依赖注入类型 & 描述
1 Constructor-based dependency injection当容器调用带有多个参数的构造函数类时,实现基于构造函数的 DI,每个代表在其他类中的一个依赖关系。
2 Setter-based dependency injection基于 setter 方法的 DI 是通过在调用无参数的构造函数或无参数的静态工厂方法实例化 bean 之后容器调用 beans 的 setter 方法来实现的。
Spring 基于构造函数的依赖注入实例
<bean id="tire" class="com.self.Tire" /> <bean id="vehicle" class="com.self.Vehicle"> <constructor-arg ref="tire"/> </bean> ---------------------------------------------- <bean id="student" class="com.self.Student"> <constructor-arg index="0" value="29"/> <constructor-arg index="1" value="lairf"/> </bean> Vehicle vehicle = (Vehicle)context.getBean("vehicle"); vehicle.turnOn(); Student student = (Student)context.getBean("student"); student.basicInfo(); public class Student { private Integer age; private String name; public Student(Integer age, String name) { this.age = age; this.name = name; } public void basicInfo(){ System.out.println("age:"+age+"------"+"name :"+ name); } } public class Vehicle { private Tire tire; public Vehicle(Tire tire) { System.out.println("---------------Vehicle constructor"); this.tire =tire; } public void turnOn(){ tire.span(); } } public class Tire { public Tire() { System.out.println("---------------tire constructor"); } public void span(){ System.out.println("---------------Tire span"); } }
Spring 基于设值函数的依赖注入
当容器调用一个无参的构造函数或一个无参的静态 factory 方法来初始化你的 bean 后,通过容器在你的 bean 上调用设值函数,基于设值函数的 DI 就完成了。
注意定义在基于构造函数注入和基于设值函数注入中的 Beans.xml 文件的区别。唯一的区别就是在基于构造函数注入中,我们使用的是〈bean〉标签中的〈constructor-arg〉元素,而在基于设值函数的注入中,我们使用的是〈bean〉标签中的〈property〉元素。
第二个你需要注意的点是,如果你要把一个引用传递给一个对象,那么你需要使用 标签的 ref 属性,而如果你要直接传递一个值,那么你应该使用 value 属性。
配置文件可以使用 p-namespace 以一种更简洁的方式重写 。另一种property标签。
-ref 部分表明这不是一个直接的值,而是对另一个 bean 的引用。
实例:
<bean id="softEngineer" class="com.self.SoftEngineer"> <property name="computer" ref="computer"/> </bean> <bean id="computer" class="com.self.Computer"/> <bean id="wife" class="com.self.Wife" p:name="maomao" p:location="China" p:postCode="362016"></bean> <bean id="husband" class="com.self.Husband" p:name="Jack" p:spouse-ref="wife"></bean> public class SoftEngineer { public SoftEngineer() { System.out.println("----------SoftEngineer constructor"); } private Computer computer; public Computer getComputer() { return computer; } public void setComputer(Computer computer) { this.computer = computer; } public void useComputer(){ computer.coding(); } } public class Computer { public Computer() { System.out.println("-------------Computer constructor"); } public void coding(){ System.out.println("--------------product codes"); } } SoftEngineer softEngineer = (SoftEngineer)context.getBean("softEngineer"); softEngineer.useComputer();
Spring 注入内部 Beans
正如你所知道的 Java 内部类是在其他类的范围内被定义的,同理,inner beans 是在其他 bean 的范围内定义的 bean。因此在 或 元素内 元素被称为内部bean 。
其实内部bean并不是对应java的内部类,就是一个要被依赖注入的bean,只是配置bean写法有点不同而已。
<bean id="textEditor" class="com.tutorialspoint.TextEditor"> <property name="spellChecker"> <bean id="spellChecker" class="com.tutorialspoint.SpellChecker"/> </property> </bean>
Spring 注入集合
你已经看到了如何使用 value 属性来配置基本数据类型和在你的 bean 配置文件中使用
现在如果你想传递多个值,如 Java Collection 类型 List、Set、Map 和 Properties,应该怎么做呢。为了处理这种情况,Spring 提供了四种类型的集合的配置元素,如下所示:
元素 描述
它有助于连线,如注入一列值,允许重复。
它有助于连线一组值,但不能重复。
你可以使用
或
你会遇到两种情况(a)传递集合中直接的值(b)传递一个 bean 的引用作为集合的元素。
注入 null 和空字符串的值
如果你需要传递一个空字符串作为值,那么你可以传递它,如下所示:
<bean id="..." class="exampleBean"> <property name="email" value=""/> </bean>
前面的例子相当于 Java 代码:exampleBean.setEmail("")。
如果你需要传递一个 NULL 值,那么你可以传递它,如下所示:
<bean id="..." class="exampleBean"> <property name="email"><null/></property> </bean>
前面的例子相当于 Java 代码:exampleBean.setEmail(null)。
例子:
public class Company { private List hrList; private Set engineerSet; private Map subCompanyMap; private Properties LeaderProps; private String[] luckinArr; public String[] getLuckinArr() { return luckinArr; } public void setLuckinArr(String[] luckinArr) { this.luckinArr = luckinArr; } public List getHrList() { return hrList; } public void setHrList(List hrList) { this.hrList = hrList; } public Set getEngineerSet() { return engineerSet; } public void setEngineerSet(Set engineerSet) { this.engineerSet = engineerSet; } public Map getSubCompanyMap() { return subCompanyMap; } public void setSubCompanyMap(Map subCompanyMap) { this.subCompanyMap = subCompanyMap; } public Properties getLeaderProps() { return LeaderProps; } public void setLeaderProps(Properties leaderProps) { LeaderProps = leaderProps; } } <bean id="hrPeople" class="com.self.HrPeople"/> <bean id="company" class="com.self.Company"> <property name="hrList"> <list> <value>yingjie</value> <value>zhangsan</value> <value>yanjihong</value> <value>yingjie</value> <ref bean="hrPeople"/> </list> </property> <property name="engineerSet"> <set> <value>lairf</value> <value>liudd</value> <value>zhoujh</value> <ref bean="hrPeople"/> </set> </property> <property name="leaderProps"> <props> <prop key="mmc">chensc</prop> <prop key="kefu">laowang</prop> <prop key="fcar">xiaoqi</prop> </props> </property> <property name="subCompanyMap"> <map> <entry key="xiamen" value="mmc"></entry> <entry key="tianjin" value="uc"></entry> <entry key="beijing" value="zc"></entry> <entry key="shanghai" value="fcar"></entry> <entry key="quanzhou" value-ref="hrPeople"/> </map> </property> <property name="luckinArr"> <array> <value>chenxf</value> <value>zhutt</value> <value>maoyh</value> </array> </property> Company company = (Company)context.getBean("company"); System.out.println(company.getHrList()); System.out.println(company.getEngineerSet()); System.out.println(company.getLeaderProps()); System.out.println(company.getSubCompanyMap()); System.out.println(Arrays.toString(company.getLuckinArr()));
Spring Beans 自动装配
你已经学会如何使用
Spring 容器可以在不使用
自动装配模式
下列自动装配模式,它们可用于指示 Spring 容器为来使用自动装配进行依赖注入。你可以使用
模式 描述
no 这是默认的设置,它意味着没有自动装配,你应该使用显式的bean引用来连线。你不用为了连线做特殊的事。在依赖注入章节你已经看到这个了。
byName 由属性名自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byName。然后尝试匹配,并且将它的属性与在配置文件中被定义为相同名称的 beans 的属性进行连接。
byType 由属性数据类型自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byType。然后如果它的类型匹配配置文件中的一个确切的 bean 名称,它将尝试匹配和连接属性的类型。如果存在不止一个这样的 bean,则一个致命的异常将会被抛出。
constructor 类似于 byType,但该类型适用于构造函数参数类型。如果在容器中没有一个构造函数参数类型的 bean,则一个致命错误将会发生。
autodetect Spring首先尝试通过 constructor 使用自动装配来连接,如果它不执行,Spring 尝试通过 byType 来自动装配。
可以使用 byType 或者 constructor 自动装配模式来连接数组和其他类型的集合。
自动装配的局限性
当自动装配始终在同一个项目中使用时,它的效果最好。如果通常不使用自动装配,它可能会使开发人员混淆的使用它来连接只有一个或两个 bean 定义。不过,自动装配可以显著减少需要指定的属性或构造器参数,但你应该在使用它们之前考虑到自动装配的局限性和缺点。
限制 描述
设置来指定依赖关系。
原始数据类型 你不能自动装配所谓的简单类型包括基本类型,字符串和类。
混乱的本质 自动装配不如显式装配精确,所以如果可能的话尽可能使用显式装配。
Spring 自动装配 byName
这种模式由属性名称指定自动装配。Spring 容器看作 beans,在 XML 配置文件中 beans 的 auto-wire 属性设置为 byName。然后,它尝试将它的属性与配置文件中定义为相同名称的 beans 进行匹配和连接。如果找到匹配项,它将注入这些 beans,否则,它将抛出异常。
实例:
public class SoftEngineer { public SoftEngineer() { System.out.println("----------SoftEngineer constructor"); } private Computer computer; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public Computer getComputer() { return computer; } public void setComputer(Computer computer) { this.computer = computer; } public void useComputer(){ computer.coding(); } } public class Computer { public Computer() { System.out.println("-------------Computer constructor"); } public void coding(){ System.out.println("--------------product codes"); } } SoftEngineer softEngineer = (SoftEngineer)context.getBean("softEngineer"); softEngineer.useComputer(); <bean id="softEngineer" class="com.self.SoftEngineer" autowire="byName"> <property name="name" value="lairf"></property> </bean> <bean id="computer" class="com.self.Computer"/>
Spring 自动装配 ‘byType’
这种模式由属性类型指定自动装配。Spring 容器看作 beans,在 XML 配置文件中 beans 的 autowire 属性设置为 byType。然后,如果它的 type 恰好与配置文件中 beans 名称中的一个相匹配,它将尝试匹配和连接它的属性。如果找到匹配项,它将注入这些 beans,否则,它将抛出异常。
<bean id="softEngineer" class="com.self.SoftEngineer" autowire="byType"> <property name="name" value="lairf"></property> </bean> <bean id="computer" class="com.self.Computer"/>
Spring 由构造函数自动装配
在 XML 配置文件中 beans 的 autowire 属性设置为 constructor。然后,它尝试把它的构造函数的参数与配置文件中 beans 名称中的一个进行匹配和连线。如果找到匹配项,它会注入这些 bean,否则,它会抛出异常。
例子:
public class SoftEngineer { public SoftEngineer(Computer computer, String name) { this.computer = computer; this.name = name; } private Computer computer; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void useComputer(){ computer.coding(); } } <bean id="softEngineer" class="com.self.SoftEngineer" autowire="constructor"> <constructor-arg name="name" value="happy"></constructor-arg> </bean> <bean id="computer" class="com.self.Computer"/>
Spring 基于注解的配置
使用相关类,方法或字段声明的注解,将 bean 配置移动到组件类本身。
注解连线在默认情况下在 Spring 容器中不打开。因此,在可以使用基于注解的连线之前,我们将需要在我们的 Spring 配置文件中启用它。
<!--配置注解打开--> <context:annotation-config/>
几个重要的注解:
序号 注解 & 描述
1 @Required@Required 注解应用于 bean 属性的 setter 方法。
2 @Autowired@Autowired 注解可以应用到 bean 属性的 setter 方法,非 setter 方法,构造函数和属性。
3 @Qualifier通过指定确切的将被连线的 bean,@Autowired 和 @Qualifier 注解组合使用可以用来删除混乱。
4 JSR-250 AnnotationsSpring 支持 JSR-250 的基础的注解,其中包括了 @Resource,@PostConstruct 和 @PreDestroy 注解。
Spring @Required 注释
@Required 注释应用于 bean 属性的 setter 方法,它表明受影响的 bean 属性在配置时必须放在 XML 配置文件中,否则容器就会抛出一个 BeanInitializationException 异常。
Caused by: org.springframework.beans.factory.BeanInitializationException: Property 'qualifyStatus' is required for bean 'manager'
例子:
public class Manager { private String name; private String qualifyPaper; private String qualifyStatus; public String getName() { return name; } @Required public void setName(String name) { this.name = name; } public String getQualifyPaper() { return qualifyPaper; } public void setQualifyPaper(String qualifyPaper) { this.qualifyPaper = qualifyPaper; } public String getQualifyStatus() { return qualifyStatus; } @Required public void setQualifyStatus(String qualifyStatus) { this.qualifyStatus = qualifyStatus; } @Override public String toString() { return "Manager{" + "name='" + name + '/'' + ", qualifyPaper='" + qualifyPaper + '/'' + ", qualifyStatus='" + qualifyStatus + '/'' + '}'; } } <!--配置注解打开--> <context:annotation-config/> <bean id="manager" class="com.self.Manager"> <property name="name" value="laist"></property> <property name="qualifyPaper" value="LevelOne"></property> <property name="qualifyStatus" value="1"/> </bean>
Spring @Autowired 注释
@Autowired 注释对在哪里和如何完成自动连接提供了更多的细微的控制。
Setter 方法中的 @Autowired
你可以在 java 文件中的 setter 方法中使用 @Autowired 注释去除xml中的元素配置。当 Spring遇到一个在 setter 方法中使用的 @Autowired 注释,它会在方法中视图执行 byType 自动连接。
注意是执行 byType 自动连接。
例子:
public class SoftEngineer { public SoftEngineer() { System.out.println("----------SoftEngineer constructor"); } private Computer computer; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public Computer getComputer() { return computer; } @Autowired public void setComputer(Computer computer) { this.computer = computer; } public void useComputer(){ computer.coding(); } }
配置文件里只需要把bean配置注册上即可,不需要其他动作。后面还可以通过@Component注解来省略掉注册动作。
<bean id="softEngineer" class="com.self.SoftEngineer"/> <bean id="computer" class="com.self.Computer"/>
属性中的 @Autowired
你可以在属性中使用 @Autowired 注释来除去 setter 方法。当时使用 属性中使用 @Autowired为自动连接属性传递的时候,Spring 会将这些传递过来的值或者引用自动分配给那些属性。
例子:
public class SoftEngineer { public SoftEngineer() { System.out.println("----------SoftEngineer constructor"); } @Autowired private Computer computer; public void useComputer(){ computer.coding(); } }
构造函数中的 @Autowired
你也可以在构造函数中使用 @Autowired。一个构造函数 @Autowired 说明当创建 bean 时,即使在 XML 文件中没有使用 元素配置 bean ,构造函数也会被自动连接。
public class SoftEngineer { @Autowired public SoftEngineer(Computer computer) { this.computer = computer; } private Computer computer; public void useComputer(){ computer.coding(); } }
@Autowired 的(required=false)选项
默认情况下,@Autowired 注释意味着依赖是必须的,它类似于 @Required 注释,然而,你可以使用 @Autowired 的 (required=false) 选项关闭默认行为。
即使你不为 name属性传递任何参数,下面的示例也会成功运行,但是对于 qualifyStatus属性则需要一个参数。
public class Manager { private String name; private String qualifyPaper; private String qualifyStatus; public String getName() { return name; } //@Required @Autowired(required = false) public void setName(String name) { this.name = name; } public String getQualifyPaper() { return qualifyPaper; } public void setQualifyPaper(String qualifyPaper) { this.qualifyPaper = qualifyPaper; } public String getQualifyStatus() { return qualifyStatus; } //@Required @Autowired public void setQualifyStatus(String qualifyStatus) { this.qualifyStatus = qualifyStatus; } @Override public String toString() { return "Manager{" + "name='" + name + '/'' + ", qualifyPaper='" + qualifyPaper + '/'' + ", qualifyStatus='" + qualifyStatus + '/'' + '}'; } } Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'manager': Unsatisfied dependency expressed through method 'setQualifyStatus' parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {} Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'manager': Unsatisfied dependency expressed through method 'setQualifyStatus' parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Spring @Qualifier 注释
可能会有这样一种情况,当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。
也就是说,@Qualifier注解一般用在一个接口对应有多个实现的时候,需要注明要引入的依赖是哪个实现类。
例子:
public interface Language { public void speak(); } public class ChineseLanguage implements Language { @Override public void speak() { System.out.println("-------speak Chinese-------"); } } public class EnglishLanguage implements Language { @Override public void speak() { System.out.println("-------speak English-------"); } } public class SoftEngineer { public SoftEngineer() { System.out.println("----------SoftEngineer constructor"); } @Autowired private Computer computer; @Autowired @Qualifier("chineseLanguage") private Language language; public void useComputer(){ computer.coding(); } public void conmunicate(){ language.speak(); } } <bean id="softEngineer" class="com.self.SoftEngineer"/> <bean id="computer" class="com.self.Computer"/> <bean id="chineseLanguage" class="com.self.ChineseLanguage"/> <bean id="englishLanguage" class="com.self.EnglishLanguage"/> SoftEngineer softEngineer = (SoftEngineer)context.getBean("softEngineer"); softEngineer.useComputer(); softEngineer.conmunicate();
输出:
----------SoftEngineer constructor -------------Computer constructor --------------product codes -------speak Chinese-------
Spring JSR-250 注释
Spring还使用基于 JSR-250 注释,它包括 @PostConstruct, @PreDestroy 和 @Resource 注释。因为你已经有了其他的选择,尽管这些注释并不是真正所需要的,但是关于它们仍然让我给出一个简短的介绍。
@PostConstruct 和 @PreDestroy 注释:
为了定义一个 bean 的安装和卸载,我们使用 init-method 和/或 destroy-method 参数简单的声明一下 。init-method 属性指定了一个方法,该方法在 bean 的实例化阶段会立即被调用。同样地,destroy-method 指定了一个方法,该方法只在一个 bean 从容器中删除之前被调用。
你可以使用 @PostConstruct 注释作为初始化回调函数的一个替代,@PreDestroy 注释作为销毁回调函数的一个替代,其解释如下示例所示。
例子:
public class Message { private Integer code; private String msg; public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } @PostConstruct public void init(){ System.out.println("----------------init-----------------"); } @PreDestroy public void destroy(){ System.out.println("----------------destroy-----------------"); } } <bean id="message" class="com.self.Message"> <property name="msg" value="hello lairf"></property> </bean> Message message = (Message)context.getBean("message"); System.out.println("=============================message==="+message.getMsg()); context.registerShutdownHook();
@Resource 注释:
你可以在字段中或者 setter 方法中使用 @Resource 注释,它和在 Java EE 5 中的运作是一样的。@Resource 注释使用一个 ‘name’ 属性,该属性以一个 bean 名称的形式被注入。你可以说,它遵循 by-name 自动连接语义。
如果没有明确地指定一个 ‘name’,默认名称源于字段名或者 setter 方法。在字段的情况下,它使用的是字段名;在一个 setter 方法情况下,它使用的是 bean 属性名称。
例子:
public class SoftEngineer { public SoftEngineer() { System.out.println("----------SoftEngineer constructor"); } @Resource //@Resource(name = "computer") private Computer computer; public void useComputer(){ computer.coding(); } }
Spring 基于 Java 的配置
即用java写配置类。
基于 Java 的配置选项,可以使你在不用配置 XML 的情况下编写大多数的 Spring 。
@Configuration 和 @Bean 注解
带有 @Configuration 的注解类表示这个类可以使用 Spring IoC 容器作为 bean 定义的来源。@Bean 注解告诉 Spring,一个带有 @Bean 的注解方法将返回一个对象,该对象应该被注册为在 Spring 应用程序上下文中的 bean。配置类可以声明多个 @Bean。一旦定义了配置类,你就可以使用 AnnotationConfigApplicationContext 来加载并把他们提供给 Spring 容器 。
注入 Bean 的依赖性
当 @Beans 依赖对方时,表达这种依赖性非常简单,只要有一个 bean 方法调用另一个。
@Import 注解:
@import 注解允许从另一个配置类中加载 @Bean 定义。
生命周期回调 @Bean(initMethod = "init", destroyMethod = "cleanup" )
@Bean 注解支持指定任意的初始化和销毁的回调方法,就像在 bean 元素中 Spring 的 XML 的初始化方法和销毁方法的属性:
指定 Bean 的范围:
默认范围是单实例,但是你可以重写带有 @Scope 注解的该方法。
例子:
public class SoftEngineer { public SoftEngineer(Computer computer,Language language) { this.computer = computer; this.language = language; } } @Configuration @Import(HiConfig.class) public class HelloConfig { @Scope("prototype") @Bean(initMethod = "init",destroyMethod = "destroy") public Message message(){ return new Message(); } @Bean public Computer computer(){ return new Computer(); } @Bean public SoftEngineer softEngineer(){ return new SoftEngineer(computer(),englishLanguage()); } @Bean public Language englishLanguage(){ return new EnglishLanguage(); } } @Configuration public class HiConfig { } ApplicationContext ctx = new AnnotationConfigApplicationContext(HelloConfig.class); Message message = ctx.getBean(Message.class); message.setMsg("hello feture"); System.out.println("=============================message==="+message.getMsg()); SoftEngineer engineer = ctx.getBean(SoftEngineer.class); engineer.useComputer(); engineer.conmunicate();
Spring 中的事件处理
Spring 的核心是 ApplicationContext,它负责管理 beans 的完整生命周期。当加载 beans 时,ApplicationContext 发布某些类型的事件。例如,当上下文启动时,ContextStartedEvent 发布,当上下文停止时,ContextStoppedEvent 发布。
通过 ApplicationEvent 类和 ApplicationListener 接口来提供在 ApplicationContext 中处理事件。如果一个 bean 实现 ApplicationListener,那么每次 ApplicationEvent 被发布到 ApplicationContext 上,那个 bean 会被通知。
注意:实现了ApplicationListener的消费类需要注册bean到容器中,否则不会去订阅消费。
Spring 提供了以下的标准事件:
序号 Spring 内置事件 & 描述
1 ContextRefreshedEventApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。
2 ContextStartedEvent当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。
3 ContextStoppedEvent当使用 ConfigurableApplicationContext 接口中的 stop() 方法停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作。
4 ContextClosedEvent当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。
5 RequestHandledEvent这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。
由于 Spring 的事件处理是单线程的,所以如果一个事件被发布,直至并且除非所有的接收者得到的该消息,该进程被阻塞并且流程将不会继续。因此,如果事件处理被使用,在设计应用程序时应注意。
为了监听上下文事件,一个 bean 应该实现只有一个方法 onApplicationEvent() 的 ApplicationListener 接口。因此,我们写一个例子来看看事件是如何传播的,以及如何可以用代码来执行基于某些事件所需的任务。
例子:
public class Message { private Integer code; private String msg; public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } @PostConstruct public void init(){ System.out.println("----------------init-----------------"); } @PreDestroy public void destroy(){ System.out.println("----------------destroy-----------------"); } } <bean id="message" class="com.self.Message"> <property name="msg" value="hello lairf"></property> </bean> <bean id="startEventListener" class="com.self.eventlistener.StartEventListener"/> <bean id="refreshEventListener" class="com.self.eventlistener.RefreshEventListener"/> <bean id="stopEventListener" class="com.self.eventlistener.StopEventListener"/> <bean id="closeEventListener" class="com.self.eventlistener.CloseEventListener"/> AbstractApplicationContext context = new ClassPathXmlApplicationContext("spring/appcontext-core.xml"); context.start(); Message message = (Message)context.getBean("message"); System.out.println("=============================message==="+message.getMsg()); context.stop(); context.close();
输出:
----------------init----------------- ---------ContextRefreshedEvent--Consume---------- ---------ContextStartedEvent--Consume---------- =============================message===hello lairf ---------ContextStoppedEvent--Consume---------- ---------ContextClosedEvent--Consume---------- ----------------destroy-----------------
Spring 中的自定义事件
自定义事件需要三个步骤:
1 通过扩展 ApplicationEvent,创建一个事件类 CustomEvent。这个类必须定义一个默认的构造函数,它应该从 ApplicationEvent 类中继承的构造函数。
2 一旦定义事件类,你可以从任何类中发布它,假定 EventClassPublisher 实现了 ApplicationEventPublisherAware。你还需要在 XML 配置文件中声明这个类作为一个 bean,之所以容器可以识别 bean 作为事件发布者,是因为它实现了 ApplicationEventPublisherAware 接口。
3 发布的事件可以在一个类中被处理,假定 EventClassHandler 实现了 ApplicationListener 接口,而且实现了自定义事件的 onApplicationEvent 方法。
例子:
public class CustomEvent extends ApplicationEvent { public CustomEvent(Object source) { super(source); } @Override public String toString() { return "CustomEvent"; } } public class CustomEventPublisher implements ApplicationEventPublisherAware { private ApplicationEventPublisher applicationEventPublisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } public void publish(){ applicationEventPublisher.publishEvent(new CustomEvent(this)); } }
注意,这里的ApplicationEventPublisher是从下面这个操作获得到的对象。在bean实例化时,会对bean进行判断,如果是ApplicationEventPublisherAware类型的bean说明是个发布者,会对其设置ApplicationEventPublisher。
org.springframework.context.support.ApplicationContextAwareProcessor#invokeAwareInterfaces if (bean instanceof ApplicationEventPublisherAware) { ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext); } public class CustomEventListener implements ApplicationListener<CustomEvent> { @Override public void onApplicationEvent(CustomEvent event) { System.out.println("---------CustomEvent--Consume----------"+event.toString()); } }
注册到容器中:
<bean class="com.self.customevent.CustomEventListener" id="customEventListener"/> <bean class="com.self.customevent.CustomEventPublisher" id="customEventPublisher"/> CustomEventPublisher customEventPublisher = (CustomEventPublisher)context.getBean("customEventPublisher"); customEventPublisher.publish(); customEventPublisher.publish();
Spring 框架的 AOP
Spring 框架的一个关键组件是面向切面的编程(AOP)框架。面向切面的编程需要把程序逻辑分解成不同的部分称为所谓的关注点。跨一个应用程序的多个点的功能被称为横切关注点,这些横切关注点在概念上独立于应用程序的业务逻辑。有各种各样的常见的很好的切面的例子,如日志记录、审计、声明式事务、安全性和缓存等。
在 OOP 中,关键单元模块度是类,而在 AOP 中单元模块度是切面。依赖注入帮助你对应用程序对象相互解耦和 AOP 可以帮助你从它们所影响的对象中对横切关注点解耦。AOP 是像编程语言的触发物,如 Perl,.NET,Java 或者其他。
Spring AOP 模块提供拦截器来拦截一个应用程序,例如,当执行一个方法时,你可以在方法执行之前或之后添加额外的功能。
AOP 术语
在我们开始使用 AOP 工作之前,让我们熟悉一下 AOP 概念和术语。这些术语并不特定于 Spring,而是与 AOP 有关的。
项 描述
Aspect 一个模块具有一组提供横切需求的 APIs。例如,一个日志模块为了记录日志将被 AOP 方面调用。应用程序可以拥有任意数量的切面,这取决于需求。
Join point 在你的应用程序中它代表一个点,你可以在插入 AOP 切面。你也能说,它是在实际的应用程序中,其中一个操作将使用 Spring AOP 框架。
Advice 这是实际行动之前或之后执行的方法。这是在程序执行期间通过 Spring AOP 框架实际被调用的代码。
Pointcut 这是一组一个或多个连接点,通知应该被执行。你可以使用表达式或模式指定切入点正如我们将在 AOP 的例子中看到的。
Introduction 引用允许你添加新方法或属性到现有的类中。
Target object 被一个或者多个切面所通知的对象,这个对象永远是一个被代理对象。也称为被通知对象。
Weaving Weaving 把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时,类加载时和运行时完成。
通知的类型
Spring 方面可以使用下面提到的五种通知工作:
通知 描述
前置通知 在一个方法执行之前,执行通知。
后置通知 在一个方法执行之后,不考虑其结果,执行通知。
返回后通知 在一个方法执行之后,只有在方法成功完成时,才能执行通知。
抛出异常后通知 在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。
环绕通知 在建议方法调用之前和之后,执行通知。
实现自定义方面
Spring 支持 @AspectJ annotation style 的方法和基于模式的方法来实现自定义切面。这两种方法已经在下面两个子节进行了详细解释。
方法 描述
XML Schema based 方面是使用常规类以及基于配置的 XML 来实现的。
@AspectJ based @AspectJ 引用一种声明切面的风格作为带有 Java 5 注释的常规 Java 类注释。
Spring 中基于 AOP 的 XML架构
步骤:
声明一个 aspect
一个 aspect 是使用 元素声明的,支持的 bean 是使用 ref 属性引用的。
声明一个切入点
一个切入点有助于确定使用不同建议执行的感兴趣的连接点(即方法)。
声明建议
你可以使用 <aop:{ADVICE NAME}> 元素在一个 中声明五个建议中的任何一个。
例子:
public class Logging { public void beforeAdvice(){ System.out.println("------------before method---------------"); } public void afterAdvice(){ System.out.println("------------after method---------------"); } public void afterReturningAdvice(Object retVal){ System.out.println("------------afterReturning method---------------"+retVal); } public void afterThrowingAdvice(IllegalArgumentException e){ System.out.println("------------afterThrowing method---------------"+e); } } public class Message { private Integer code; private String msg; public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } @PostConstruct public void init(){ System.out.println("----------------init-----------------"); } @PreDestroy public void destroy(){ System.out.println("----------------destroy-----------------"); } public void throwingException() { System.out.println("----------------init-----------------"); throw new IllegalArgumentException(); } } Message message = (Message)context.getBean("message"); System.out.println("=============================message==="+message.getMsg()); System.out.println("=============================code==="+message.getCode()); message.throwingException(); <aop:config> <aop:aspect id="firstAspect" ref="logging"> <aop:pointcut id="methodAll" expression="execution(* com.self.*.*(..))"/> <aop:before method="beforeAdvice" pointcut-ref="methodAll"/> <aop:after method="afterAdvice" pointcut-ref="methodAll"/> <aop:after-returning method="afterReturningAdvice" pointcut-ref="methodAll" returning="retVal"/> <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="methodAll" throwing="e"/> </aop:aspect> </aop:config> <bean id="message" class="com.self.Message"> <property name="msg" value="hello lairf"></property> <property name="code" value="1"></property> </bean> <bean id="logging" class="com.self.aspect.Logging"/>
输出:
----------------init----------------- ------------before method--------------- ------------after method--------------- ------------afterReturning method---------------hello lairf =============================message===hello lairf ------------before method--------------- ------------after method--------------- ------------afterReturning method---------------1 =============================code===1 ------------before method--------------- ----------------init----------------- ------------after method--------------- ------------afterThrowing method---------------java.lang.IllegalArgumentException Exception in thread "main" java.lang.IllegalArgumentException
想要在一个特殊的方法之前或者之后执行你的建议,可以通过替换使用真实类和方法名称的切入点定义中的星号(*)来定义你的切入点来缩短你的执行。
在运行时报错:
原因:缺少了aspectJ的依赖,添加jar或依赖即可
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.6</version> </dependency> 警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.context.event.internalEventListenerProcessor': BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.aop.aspectj.AspectJPointcutAdvisor#0': Cannot create inner bean '(inner bean)#5f36ba9b' of type [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice] while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#5f36ba9b': Failed to introspect bean class [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice] for lookup method metadata: could not find class that it depends on; nested exception is java.lang.NoClassDefFoundError: org/aspectj/lang/JoinPoint Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.context.event.internalEventListenerProcessor': BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.aop.aspectj.AspectJPointcutAdvisor#0': Cannot create inner bean '(inner bean)#5f36ba9b' of type [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice] while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#5f36ba9b': Failed to introspect bean class [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice] for lookup method metadata: could not find class that it depends on; nested exception is java.lang.NoClassDefFoundError: org/aspectj/lang/JoinPoint
Spring 中基于 AOP 的 @AspectJ
Spring容器配置:参考
<aop:aspectj-autoproxy/> 这个标签有一个属性:proxy-target-class,它的值有false(默认)和true。再将它设置成true之后,结果运行成功了。总结一下原因如下: <aop:aspectj-autoproxy proxy-target-class="false"/> 基于接口,使用JDK动态代理 <aop:aspectj-autoproxy proxy-target-class="true"/> 基于类,需要使用cglib库. ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService accountService = app.getBean(AccountServiceImpl.class);//此处是通过AccountService接口的实现类来获取bean。结果报错了: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'cn.xx.service.impl.AccountServiceImpl' available at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:347) 说找不到cn.xx.service.impl.AccountServiceImpl这个类。
声明一个切入点
一个切入点有助于确定使用不同建议执行的感兴趣的连接点(即方法)。在处理基于配置的 XML 架构时,切入点的声明有两个部分:
声明建议
你可以使用 @{ADVICE-NAME} 注释声明五个建议中的任意一个.
实例:
@Aspect public class Monitoring { @Pointcut("execution(* com.self.Message.*(..))") public void monitorAll() { } @Before("monitorAll()") public void beforeAdvice() { System.out.println("===========beforeAdvice============="); } @After("monitorAll()") public void afterAdvice() { System.out.println("===========afterAdvice============="); } @AfterReturning(pointcut = "monitorAll()", returning = "retVal") public void afterReturning(Object retVal) { System.out.println("===========afterReturning=============" + retVal); } @AfterThrowing(pointcut = "monitorAll()", throwing = "e") public void afterThrowing(IllegalArgumentException e) { System.out.println("===========afterThrowing=============" + e); } @Around("monitorAll()") public Object around(ProceedingJoinPoint point) throws Throwable { Object[] args = point.getArgs(); System.out.println("===========around start============="+ JSON.toJSONString(args)); Object result = point.proceed(); System.out.println("===========around end ============="+result); return result; } } public class Message { private Integer code; private String msg; public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Map checkArgs(Map map) { map.containsKey("check"); map.put("check","doubleCheck"); return map; } @PostConstruct public void init(){ System.out.println("----------------init-----------------"); } @PreDestroy public void destroy(){ System.out.println("----------------destroy-----------------"); } public void throwingException() { System.out.println("----------------throwingException-----------------"); throw new IllegalArgumentException(); } } Message message = (Message)context.getBean("message"); //System.out.println("=============================message==="+message.getMsg()); HashMap<Object, Object> hashMap = new HashMap<>(); hashMap.put("check","check"); hashMap.put("submit","ok"); System.out.println("=============================checkArgs==="+message.checkArgs(hashMap)); //message.throwingException(); <aop:aspectj-autoproxy proxy-target-class="true"/> <bean id="message" class="com.self.Message"> <property name="msg" value="hello lairf"></property> <property name="code" value="1"></property> </bean> <bean class="com.self.aspect.Monitoring" id="monitoring"/>
输出:
----------------init----------------- ===========around start=============[{"submit":"ok","check":"check"}] ===========beforeAdvice============= ===========around end ============={submit=ok, check=doubleCheck} ===========afterAdvice============= ===========afterReturning============={submit=ok, check=doubleCheck} =============================checkArgs==={submit=ok, check=doubleCheck}
确实是order越小越是最先执行,但更重要的是最先执行的最后结束。
spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。
如果我们要在同一个方法事务提交后执行自己的AOP,那么把事务的AOP order设置为2,自己的AOP order设置为1,然后在doAfterReturn里边处理自己的业务逻辑。
Spring JDBC 框架
select version(); 查看Mysql版本
5.6.24-72.2-log 生产版本
使用普通的 JDBC 数据库时缺点:写不必要的代码来处理异常,打开和关闭数据库连接等。
Spring JDBC 框架优点:负责所有的低层细节,从开始打开连接,准备和执行 SQL 语句,处理异常,处理事务,到最后关闭连接。
所以当从数据库中获取数据时,你所做的是定义连接参数,指定要执行的 SQL 语句,每次迭代完成所需的工作。
Spring JDBC 提供几种方法和数据库中相应的不同的类与接口。我将给出使用 JdbcTemplate 类框架的经典和最受欢迎的方法。这是管理所有数据库通信和异常处理的中央框架类。
JdbcTemplate 类
JdbcTemplate 类的实例是线程安全配置的。所以你可以配置 JdbcTemplate 的单个实例,然后将这个共享的引用安全地注入到多个 DAOs 中。
使用 JdbcTemplate 类时常见的做法是在你的 Spring 配置文件中配置数据源,然后共享数据源 bean 依赖注入到 DAO 类中,并在数据源的设值函数中创建了 JdbcTemplate。
Spring JDBC 示例
前提需要,数据库支持:
##创建数据库 create DATABASE hello_spring; ##创建表 CREATE TABLE IF NOT EXISTS t_developer ( id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键', name VARCHAR(30) NOT NULL COMMENT '姓名', work_level INT(6) NOT NULL COMMENT '等级', position VARCHAR(30) DEFAULT NULL COMMENT '职位', salary DECIMAL(13,2) DEFAULT '0' COMMENT '薪水', status SMALLINT(2) DEFAULT NULL COMMENT '状态', PRIMARY KEY (id) )ENGINE = INNODB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT '开发员工表';
例子:
public interface DeveloperDao { void setDataSource(DataSource ds); void insert(Developer developer); void deleteById(Integer id); void update(Developer developer); List<Developer> getDevelopers(); Developer getDeveloperById(Integer id); } public class DeveloperDaotJDBCTemplate implements DeveloperDao { private DataSource dataSource; private JdbcTemplate jdbcTemplate; @Override public void setDataSource(DataSource ds) { this.dataSource = ds; this.jdbcTemplate = new JdbcTemplate(dataSource); } @Override public void insert(Developer developer) { String sql = "insert into t_developer(name,work_level,position,salary,status) values (?,?,?,?,?)"; jdbcTemplate.update(sql,developer.getName(),developer.getWorkLevel(),developer.getPosition(),developer.getSalary(), developer.getStatus()); System.out.println("-------------insert------------"); } @Override public void deleteById(Integer id) { String sql = "delete from t_developer where id = ?"; jdbcTemplate.update(sql,id); System.out.println("-------------deleteById------------"+id); } @Override public void update(Developer developer) { String sql = "update t_developer set name = ? ,work_level = ?, position = ? ,salary = ?, status = ? where id = ?"; jdbcTemplate.update(sql,developer.getName(),developer.getWorkLevel(),developer.getPosition(),developer.getSalary(), developer.getStatus(),developer.getId()); System.out.println("-------------update------------"); } @Override public List<Developer> getDevelopers() { String sql = "select id,name,work_level,position,salary,status from t_developer where 1=1"; List<Developer> list = jdbcTemplate.query(sql, new DeveloperMapper()); System.out.println("-------------getDevelopers------------"); return list; } @Override public Developer getDeveloperById(Integer id) { String sql = "select id,name,work_level,position,salary,status from t_developer where id= ?"; Developer developer = jdbcTemplate.queryForObject(sql, new Object[]{id}, new DeveloperMapper()); System.out.println("-------------getDeveloperById------------"+id); return developer; } } public class DeveloperMapper implements RowMapper<Developer> { @Override public Developer mapRow(ResultSet rs, int rowNum) throws SQLException { Developer developer = new Developer(); developer.setId(rs.getInt("id")); developer.setName(rs.getString("name")); developer.setPosition(rs.getString("position")); developer.setSalary(rs.getBigDecimal("salary")); developer.setWorkLevel(rs.getInt("work_level")); developer.setStatus(rs.getInt("status")); return developer; } } <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/hello_spring"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <bean class="com.self.jdbc.impl.DeveloperDaotJDBCTemplate" id="developerDaotJDBCTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <!--接口和抽象类在bean容器中配置需要声明abstract="true",而该接口是不能被获取使用的--> <!--<bean class="com.self.jdbc.DeveloperDao" id="developerDao" abstract="true"></bean>--> //DeveloperDaotJDBCTemplate jdbcTemplate = (DeveloperDaotJDBCTemplate)context.getBean("developerDaotJDBCTemplate"); //无法获得到想要的实现类,还需要研究下像注解@Autowire是怎么把实现类注入到接口中的,或者订单是怎么通过serviceFactory来通过接口获取到实现类的 //DeveloperDao developerDao = (DeveloperDao)context.getBean("DeveloperDao"); //多态可以正常操作 DeveloperDao developerDao = (DeveloperDao)context.getBean("developerDaotJDBCTemplate"); Developer developer = new Developer(); developer.setStatus(1); developer.setWorkLevel(3); developer.setSalary(new BigDecimal("87010.78")); developer.setPosition("贷款"); developer.setName("Master Ting"); developerDao.insert(developer); Developer dev = developerDao.getDeveloperById(1000); System.out.println(JSON.toJSONString(dev)); List<Developer> developers = developerDao.getDevelopers(); System.out.println(JSON.toJSONString(developers)); dev.setName("lairffff"); developerDao.update(dev); developerDao.deleteById(1002); List<Developer> after = developerDao.getDevelopers(); System.out.println(JSON.toJSONString(after)); public class Developer { /** * 主键 */ private Integer id; /** * '姓名' */ private String name; /** * '等级' */ private Integer workLevel; /** * '职位' */ private String position; /** * '薪水' */ private BigDecimal salary; /** * '状态' */ private Integer status; 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 getWorkLevel() { return workLevel; } public void setWorkLevel(Integer workLevel) { this.workLevel = workLevel; } public String getPosition() { return position; } public void setPosition(String position) { this.position = position; } public BigDecimal getSalary() { return salary; } public void setSalary(BigDecimal salary) { this.salary = salary; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } }
输出:
-------------insert------------ -------------getDeveloperById------------1000 {"id":1000,"name":"Boss Liu","position":"中台","salary":32000.78,"status":1,"workLevel":6} -------------getDevelopers------------ [{"id":1000,"name":"Boss Liu","position":"中台","salary":32000.78,"status":1,"workLevel":6},{"id":1001,"name":"Boss Zhou","position":"中台","salary":33000.78,"status":1,"workLevel":6},{"id":1002,"name":"Boss Bin","position":"新零售","salary":53010.78,"status":0,"workLevel":7},{"id":1003,"name":"Student Chen","position":"新零售","salary":13010.78,"status":0,"workLevel":4},{"id":1004,"name":"Student Xu","position":"新零售","salary":17010.78,"status":1,"workLevel":5},{"id":1005,"name":"Master Ting","position":"贷款","salary":87010.78,"status":1,"workLevel":3},{"id":1006,"name":"Master Ting","position":"贷款","salary":87010.78,"status":1,"workLevel":3}] -------------update------------ -------------deleteById------------1002 -------------getDevelopers------------ [{"id":1000,"name":"lairffff","position":"中台","salary":32000.78,"status":1,"workLevel":6},{"id":1001,"name":"Boss Zhou","position":"中台","salary":33000.78,"status":1,"workLevel":6},{"id":1003,"name":"Student Chen","position":"新零售","salary":13010.78,"status":0,"workLevel":4},{"id":1004,"name":"Student Xu","position":"新零售","salary":17010.78,"status":1,"workLevel":5},{"id":1005,"name":"Master Ting","position":"贷款","salary":87010.78,"status":1,"workLevel":3},{"id":1006,"name":"Master Ting","position":"贷款","salary":87010.78,"status":1,"workLevel":3}]
错误:在从容器中获取bean失败:因为这是个接口,不是bean。
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'DeveloperDao' available
还有其他方法来访问你使用 NamedParameterJdbcTemplate 和 SimpleJdbcTemplate 类的数据库,所以如果你有兴趣学习这些类的话,那么你可以查看 Spring 框架的参考手册。
Spring 中 SQL 的存储过程
数据库需要建立存储过程:
DELIMITER $$ DROP PROCEDURE IF EXISTS `hello_spring`.`getRecord` $$ CREATE PROCEDURE `hello_spring`.`getRecord` ( IN in_id INTEGER, OUT out_name VARCHAR(30), OUT out_salary DECIMAL(13,2)) BEGIN SELECT name, salary INTO out_name, out_salary FROM t_developer where id = in_id; END $$ DELIMITER ;
实例:主要变化
public class DeveloperDaotJDBCTemplate implements DeveloperDao { private DataSource dataSource; private JdbcTemplate jdbcTemplate; private SimpleJdbcCall simpleJdbcCall; @Override public void setDataSource(DataSource ds) { this.dataSource = ds; this.jdbcTemplate = new JdbcTemplate(dataSource); this.simpleJdbcCall = new SimpleJdbcCall(dataSource).withProcedureName("getRecord"); } @Override public Developer getDeveloperByProcedure(Integer id) { SqlParameterSource in = new MapSqlParameterSource().addValue("in_id",id); Map<String, Object> map = simpleJdbcCall.execute(in); Developer developer = new Developer(); developer.setId((Integer) map.get("out_id")); developer.setName((String) map.get("out_name")); developer.setSalary((BigDecimal) map.get("out_salary")); return developer; } }
Spring 事务管理
事务管理四个关键属性 ACID:
Spring 支持编程式和声明式事务管理。EJBs 需要一个应用程序服务器,但 Spring 事务管理可以在不需要应用程序服务器的情况下实现。
局部事物 vs. 全局事务
局部事务是特定于一个单一的事务资源,如一个 JDBC 连接,而全局事务可以跨多个事务资源事务,如在一个分布式系统中的事务。
局部事务管理在一个集中的计算环境中是有用的,该计算环境中应用程序组件和资源位于一个单位点,而事务管理只涉及到一个运行在一个单一机器中的本地数据管理器。局部事务更容易实现。
全局事务管理需要在分布式计算环境中,所有的资源都分布在多个系统中。在这种情况下事务管理需要同时在局部和全局范围内进行。分布式或全局事务跨多个系统执行,它的执行需要全局事务管理系统和所有相关系统的局部数据管理人员之间的协调。
编程式 vs. 声明式
Spring 支持两种类型的事务管理:
Spring 事务抽象
Spring 事务抽象的关键是由 org.springframework.transaction.PlatformTransactionManager 接口定义,如下所示:
public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition); throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
序号 方法 & 描述
1 TransactionStatus getTransaction(TransactionDefinition definition)根据指定的传播行为,该方法返回当前活动事务或创建一个新的事务。
2 void commit(TransactionStatus status)该方法提交给定的事务和关于它的状态。
3 void rollback(TransactionStatus status)该方法执行一个给定事务的回滚。
TransactionDefinition 是在 Spring 中事务支持的核心接口,它的定义如下:
public interface TransactionDefinition { int getPropagationBehavior(); int getIsolationLevel(); String getName(); int getTimeout(); boolean isReadOnly(); }
序号 方法 & 描述
1 int getPropagationBehavior()该方法返回传播行为。Spring 提供了与 EJB CMT 类似的所有的事务传播选项。
2 int getIsolationLevel()该方法返回该事务独立于其他事务的工作的程度。
3 String getName()该方法返回该事务的名称。
4 int getTimeout()该方法返回以秒为单位的时间间隔,事务必须在该时间间隔内完成。
5 boolean isReadOnly()该方法返回该事务是否是只读的。
下面是隔离级别的可能值:
序号 隔离 & 描述
1 TransactionDefinition.ISOLATION_DEFAULT这是默认的隔离级别。
2 TransactionDefinition.ISOLATION_READ_COMMITTED表明能够阻止误读;可以发生不可重复读和虚读。
3 TransactionDefinition.ISOLATION_READ_UNCOMMITTED表明可以发生误读、不可重复读和虚读。
4 TransactionDefinition.ISOLATION_REPEATABLE_READ表明能够阻止误读和不可重复读;可以发生虚读。
5 TransactionDefinition.ISOLATION_SERIALIZABLE表明能够阻止误读、不可重复读和虚读。
下面是传播类型的可能值:
序号 传播 & 描述
1 TransactionDefinition.PROPAGATION_MANDATORY支持当前事务;如果不存在当前事务,则抛出一个异常。
2 TransactionDefinition.PROPAGATION_NESTED如果存在当前事务,则在一个嵌套的事务中执行。
3 TransactionDefinition.PROPAGATION_NEVER不支持当前事务;如果存在当前事务,则抛出一个异常。
4 TransactionDefinition.PROPAGATION_NOT_SUPPORTED不支持当前事务;而总是执行非事务性。
5 TransactionDefinition.PROPAGATION_REQUIRED支持当前事务;如果不存在事务,则创建一个新的事务。
6 TransactionDefinition.PROPAGATION_REQUIRES_NEW创建一个新事务,如果存在一个事务,则把当前事务挂起。
7 TransactionDefinition.PROPAGATION_SUPPORTS支持当前事务;如果不存在,则执行非事务性。
8 TransactionDefinition.TIMEOUT_DEFAULT使用默认超时的底层事务系统,或者如果不支持超时则没有。
TransactionStatus 接口为事务代码提供了一个简单的方法来控制事务的执行和查询事务状态。
public interface TransactionStatus extends SavepointManager { boolean isNewTransaction(); boolean hasSavepoint(); void setRollbackOnly(); boolean isRollbackOnly(); boolean isCompleted(); }
序号 方法 & 描述
1 boolean hasSavepoint()该方法返回该事务内部是否有一个保存点,也就是说,基于一个保存点已经创建了嵌套事务。
2 boolean isCompleted()该方法返回该事务是否完成,也就是说,它是否已经提交或回滚。
3 boolean isNewTransaction()在当前事务时新的情况下,该方法返回 true。
4 boolean isRollbackOnly()该方法返回该事务是否已标记为 rollback-only。
5 void setRollbackOnly()该方法设置该事务为 rollback-only 标记。
Spring 编程式事务管理
步骤:
前提:新增表
CREATE TABLE IF NOT EXISTS t_bonus ( id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键', developer_id BIGINT(20) NOT NULL COMMENT '员工id', bonus DECIMAL(13,2) DEFAULT '0' COMMENT '奖金', performance SMALLINT(2) DEFAULT NULL COMMENT '绩效', PRIMARY KEY (id) )ENGINE = INNODB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8 COMMENT '开发员工奖金表';
例子:
public class DeveloperDaotJDBCTemplate implements DeveloperDao { private DataSource dataSource; private JdbcTemplate jdbcTemplate; private SimpleJdbcCall simpleJdbcCall; private PlatformTransactionManager transactionManager; @Override public void setDataSource(DataSource ds) { this.dataSource = ds; this.jdbcTemplate = new JdbcTemplate(dataSource); this.simpleJdbcCall = new SimpleJdbcCall(dataSource).withProcedureName("getRecord"); } @Override public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } @Override public void insertDeveloperAndBonus(Developer developer, Bonus bonus) { TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { String sql = "insert into t_developer(name,work_level,position,salary,status) values (?,?,?,?,?)"; int developerId = jdbcTemplate.update(sql, developer.getName(), developer.getWorkLevel(), developer.getPosition(), developer.getSalary(), developer.getStatus()); String sqlBonus = "insert into t_bonus(developer_id,bonus,performance) values (?,?,?)"; jdbcTemplate.update(sqlBonus,developerId,bonus.getBonus(),bonus.getPerformance()); transactionManager.commit(transactionStatus); System.out.println("-------------commit------------"); } catch (DataAccessException e) { transactionManager.rollback(transactionStatus); System.out.println("-------------rollback------------"); e.printStackTrace(); } System.out.println("-------------insertDeveloperAndBonus------------"); } } public class Bonus { /** * 主键 */ private Integer id; /** * 员工id */ private Integer developerId; /** * '奖金' */ private BigDecimal bonus; /** * ''绩效'' */ private Integer performance; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Integer getDeveloperId() { return developerId; } public void setDeveloperId(Integer developerId) { this.developerId = developerId; } public BigDecimal getBonus() { return bonus; } public void setBonus(BigDecimal bonus) { this.bonus = bonus; } public Integer getPerformance() { return performance; } public void setPerformance(Integer performance) { this.performance = performance; } } DeveloperDao developerDao = (DeveloperDao)context.getBean("developerDaotJDBCTemplate"); Developer developer = new Developer(); developer.setStatus(1); developer.setWorkLevel(3); developer.setSalary(new BigDecimal("87010.78")); developer.setPosition("贷款"); developer.setName("Master Ting"); Bonus bonus = new Bonus(); bonus.setBonus(new BigDecimal("100000.58")); bonus.setPerformance(1); developerDao.insertDeveloperAndBonus(developer,bonus); <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/hello_spring"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <bean class="com.self.jdbc.impl.DeveloperDaotJDBCTemplate" id="developerDaotJDBCTemplate"> <property name="dataSource" ref="dataSource"/> <property name="transactionManager" ref="transactionManager"/> </bean> <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
输出:
失败: -------------rollback------------ -------------insertDeveloperAndBonus------------ org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [insert into t_bonus(developer_id,bonus,performance) values (?,?,?)]; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'hello_spring.t_bonus' doesn't exist 成功: -------------commit------------ -------------insertDeveloperAndBonus------------
Spring 声明式事务管理
这块声明式事务没讲清楚,一些xml配置没说明清楚。
声明式事务管理方法允许你在配置的帮助下而不是源代码硬编程来管理事务。这意味着你可以将事务管理从事务代码中隔离出来。你可以只使用注释或基于配置的 XML 来管理事务。 bean 配置会指定事务型方法。
扫描事务步骤:
实例:
注意:如果没有加 ,表示完全匹配,如果方法名不是insert,则不会加入事务;加 表示只要是前缀是insert的方法都会加入事务中
<!--注意这边要加入tx声明 --> <xmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/hello_spring"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <bean class="com.self.jdbc.impl.DeveloperDaotJDBCTemplate" id="developerDaotJDBCTemplate"> <property name="dataSource" ref="dataSource"/> <!--<property name="transactionManager" ref="transactionManager"/>--> </bean> <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:advice id="txManager" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="insert*"/> <tx:method name="update*"/> <tx:method name="delete*"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* com.self.jdbc.*.*(..))"/> <aop:advisor advice-ref="txManager" pointcut-ref="txPointcut"/> </aop:config> public class DeveloperDaotJDBCTemplate implements DeveloperDao { private DataSource dataSource; private JdbcTemplate jdbcTemplate; @Override public void setDataSource(DataSource ds) { this.dataSource = ds; this.jdbcTemplate = new JdbcTemplate(dataSource); this.simpleJdbcCall = new SimpleJdbcCall(dataSource).withProcedureName("getRecord"); } @Override public void insertDeveloperAndBonus(Developer developer, Bonus bonus) { try { String sql = "insert into t_developer(name,work_level,position,salary,status) values (?,?,?,?,?)"; int developerId = jdbcTemplate.update(sql, developer.getName(), developer.getWorkLevel(), developer.getPosition(), developer.getSalary(), developer.getStatus()); String sqlBonus = "insert into t_bonus(developer_id,bonus,performance) values (?,?,?)"; jdbcTemplate.update(sqlBonus,developerId,bonus.getBonus(),bonus.getPerformance()); throw new RuntimeException("error occures"); //System.out.println("-------------commit------------"); } catch (DataAccessException e) { System.out.println("-------------rollback------------"); e.printStackTrace(); } System.out.println("-------------insertDeveloperAndBonus------------"); } }
输出:
成功: -------------commit------------ -------------insertDeveloperAndBonus------------ 失败: Exception in thread "main" java.lang.RuntimeException: error occures at com.self.jdbc.impl.DeveloperDaotJDBCTemplate.insertDeveloperAndBonus(DeveloperDaotJDBCTemplate.java:133)
Spring Web MVC 框架
Spring web MVC 框架提供了模型-视图-控制的体系结构和可以用来开发灵活、松散耦合的 web 应用程序的组件。MVC 模式导致了应用程序的不同方面(输入逻辑、业务逻辑和 UI 逻辑)的分离,同时提供了在这些元素之间的松散耦合。
DispatcherServlet
Spring Web 模型-视图-控制(MVC)框架是围绕 DispatcherServlet 设计的,DispatcherServlet 用来处理所有的 HTTP 请求和响应。Spring Web MVC DispatcherServlet 的请求处理的工作流程如下图所示:
下面是对应于 DispatcherServlet 传入 HTTP 请求的事件序列:
上面所提到的所有组件,即 HandlerMapping、Controller 和 ViewResolver 是 WebApplicationContext 的一部分,而 WebApplicationContext 是带有一些对 web 应用程序必要的额外特性的 ApplicationContext 的扩展。
web.xml 文件将被保留在你的应用程序的 WebContent/WEB-INF 目录下。好的,在初始化 HelloWeb DispatcherServlet 时,该框架将尝试加载位于该应用程序的 WebContent/WEB-INF 目录中文件名为 [servlet-name]-servlet.xml 的应用程序内容。在这种情况下,我们的文件将是 springhello-servlet.xml。
默认配置web.xml,下面的配置默认回去加载
<servlet> <servlet-name>hellospring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>hellospring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
如果要自定义加载的配置名,不用默认的,可以如下这样配置,加载classpath:spring/appcontext-core.xml配置文件:
<servlet> <servlet-name>hellospring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:spring/appcontext-core.xml </param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>hellospring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
在 web.xml 文件中添加 servlet 监听器 ContextLoaderListener 自定义该文件的名称和位置 ,这个一般是用来加载spring容器,前面的一般用来加载spring mvc容器。两个容器区分开来,我能想到的好处就是避免耦合;可以灵活获取单一容器,单一职责,避免容器过于庞大。
自定义配置加载如下,在web.xml中添加,可以添加多个配置文件:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/appcontext-core.xml, classpath*:com/spring/dataAccessContext-ibatis-IS.xml </param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
加
<init-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:spring/appcontext-core.xml </param-value> </init-param>
Spring MVC Hello World 例子
实例:
@Controller public class HelloController { @RequestMapping("/hello") public String getHello(ModelMap modelMap){ modelMap.addAttribute("message","hello,I'am soul good man"); return "hello"; } }
web.xml:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>hellospring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> WEB-INF/hellospring-servlet.xml </param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>hellospring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/appcontext-core.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
hellospring-servlet.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:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd"> <!--spring mvc容器扫描配置--> <context:component-scan base-package="com.self" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
WEB-INF/jsp/hello.jsp:
<%@ page contentType="text/html; charset=UTF-8" %> <html> <head> <title>Hello World</title> </head> <body> <h2>${message}</h2> </body> </html>
输出:
期间遇到的错误解答:
当输出${message}时,jsp不解析el表达式
用Maven-archetype-webapp 创建项目时,使用的servlet版本是2.3,而servlet2.4以下的版本是不会自动解析el表达式的,改成3.0的即可。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
一开始项目配置tomcat启动不了:
注意检查是否配置pom.xml时打的是否为war包,之前我配置成jar包了,还有就是要注意打印日志输出出来的失败原因。大可以把其中的关键字语句放到百度,Google上搜索一下,看下别人的解答。我这边是因为在idea配置项目配置设置。
错误:
Artifact tlks: com.intellij.javaee.oss.admin.jmx.JmxAdminException: com.intellij.execution.ExecutionException: E: not found for the web module.
找不到这个web module,在project structure 中添加Artifact配置,一直是exploded.
然后配置tomcat,项目访问路径名:
设置项目编译后的文件输出位置:
Spring MVC 表单处理例子
实例:
public class Developer { /** * 主键 */ private Integer id; /** * '姓名' */ private String name; /** * '等级' */ private Integer workLevel; /** * '职位' */ private String position; /** * '薪水' */ private BigDecimal salary; /** * '状态' */ private Integer status; 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 getWorkLevel() { return workLevel; } public void setWorkLevel(Integer workLevel) { this.workLevel = workLevel; } public String getPosition() { return position; } public void setPosition(String position) { this.position = position; } public BigDecimal getSalary() { return salary; } public void setSalary(BigDecimal salary) { this.salary = salary; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } } @Controller public class DeveloperController { @RequestMapping("/developer") public ModelAndView developer(){ return new ModelAndView("developer","command",new Developer()); } @RequestMapping(value = "/addDeveloper",method = RequestMethod.POST) public String addDeveloper(@ModelAttribute("SpringWeb") Developer developer,ModelMap modelMap){ modelMap.addAttribute("name",developer.getName()); modelMap.addAttribute("position",developer.getPosition()); modelMap.addAttribute("salary",developer.getSalary()); modelMap.addAttribute("workLevel",developer.getWorkLevel()); modelMap.addAttribute("status",developer.getStatus()); modelMap.addAttribute("id",developer.getId()); return "result"; } }
developer.jsp :
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <html> <head> <title>Spring MVC Form Handling</title> </head> <body> <h2>Developer Information</h2> <form:form method="POST" action="/hellospring/addDeveloper"> <table> <tr> <td><form:label path="name">Name</form:label></td> <td><form:input path="name" /></td> </tr> <tr> <td><form:label path="workLevel">workLevel</form:label></td> <td><form:input path="workLevel" /></td> </tr> <tr> <td><form:label path="position">position</form:label></td> <td><form:input path="position" /></td> </tr> <tr> <td><form:label path="salary">salary</form:label></td> <td><form:input path="salary" /></td> </tr> <tr> <td><form:label path="status">status</form:label></td> <td><form:input path="status" /></td> </tr> <tr> <td><form:label path="id">id</form:label></td> <td><form:input path="id" /></td> </tr> <tr> <td colspan="2"> <input type="submit" value="Submit"/> </td> </tr> </table> </form:form> </body> </html>
result.jsp :
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <html> <head> <title>Spring MVC Form Handling</title> </head> <body> <h2>Submitted Developer Information</h2> <table> <tr> <td>Name</td> <td>${name}</td> </tr> <tr> <td>workLevel</td> <td>${workLevel}</td> </tr> <tr> <td>position</td> <td>${position}</td> </tr> <tr> <td>salary</td> <td>${salary}</td> </tr> <tr> <td>status</td> <td>${status}</td> </tr> <tr> <td>ID</td> <td>${id}</td> </tr> </table> </body> </html>
web.xml :
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>hellospring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> WEB-INF/hellospring-servlet.xml </param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>hellospring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/appcontext-core.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
hellospring-servlet.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:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd"> <!--spring mvc容器扫描配置--> <context:component-scan base-package="com.self" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
输出:
Spring 页面重定向例子
例子:
@Controller public class RedirectController { @RequestMapping("/index") public String index(){ return "index"; } @RequestMapping("/redirect") public String redirect(){ return "redirect: final"; } @RequestMapping("/final") public String finalPage(){ return "final"; } }
index.jsp :
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <html> <head> <title>Spring Page Redirection</title> </head> <body> <h2>Spring Page Redirection</h2> <p>Click below button to redirect the result to new page</p> <form:form method="GET" action="/hellospring/redirect"> <table> <tr> <td> <input type="submit" value="GO FINAL"/> </td> </tr> </table> </form:form> </body> </html>
final.jsp :
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <html> <head> <title>Spring Page Redirection</title> </head> <body> <h2>Redirected Final Page</h2> </body> </html>
输出:
Spring 使用 Log4J 记录日志
例子:
@Controller public class DeveloperController { private static final Logger LOGGER = LoggerFactory.getLogger(DeveloperController.class); @RequestMapping("/developer") public ModelAndView developer(){ return new ModelAndView("developer","command",new Developer()); } @RequestMapping(value = "/addDeveloper",method = RequestMethod.POST) public String addDeveloper(@ModelAttribute("SpringWeb") Developer developer,ModelMap modelMap){ LOGGER.error("====================start==================="); modelMap.addAttribute("name",developer.getName()); modelMap.addAttribute("position",developer.getPosition()); modelMap.addAttribute("salary",developer.getSalary()); modelMap.addAttribute("workLevel",developer.getWorkLevel()); modelMap.addAttribute("status",developer.getStatus()); modelMap.addAttribute("id",developer.getId()); LOGGER.error("====================end==================="); return "result"; } }
log4j.properties:
log4j.rootLogger = ERROR,CONSOLE #RollingFile Appender log4j.appender.R = org.apache.log4j.DailyRollingFileAppender log4j.appender.R.Threshold = error log4j.appender.R.File = log4j.appender.R.Append = true log4j.appender.R.ImmediateFlush = true log4j.appender.R.DatePattern = '.'yyyy-MM-dd'.txt' log4j.appender.R.layout = org.apache.log4j.PatternLayout log4j.appender.R.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %t %c{15}:%M(%L) - %m%n #DB Appender log4j.appender.DB= log4j.appender.DB.Threshold=INFO log4j.appender.DB.layout=org.apache.log4j.PatternLayout log4j.appender.DB.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %C{8}@(%F:%L)%n [%-5p]: %m%n #Console Appender log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Target=System.out log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %C{8}@(%F:%L)%n [%-5p]: %m%n
pom.xml
<!--=================log4j依赖===============--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.2</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.21</version> </dependency> <!--=================log4j依赖===============-->
注意log4j.properties在resources的根目录下
疑问
Q:Spring 可以使开发人员使用 POJOs 开发企业级的应用程序。只使用 POJOs 的好处是你不需要一个 EJB 容器产品,比如一个应用程序服务器,但是你可以选择使用一个健壮的 servlet 容器,比如 Tomcat 或者一些商业产品。
怎么理解这句话?
Q:Spring Bean 后置处理器在具体的项目中有哪些实际的用法呢?感觉目前接触的项目都没有用到过?
Q: @Resource 如果没有明确地指定一个 ‘name’,默认名称源于字段名或者 setter 方法。在字段的情况下,它使用的是字段名;在一个 setter 方法情况下,它使用的是 bean 属性名称。 在一个 setter 方法情况下,它使用的是 bean 属性名称是指使用的是容器里注册的bean名称?
应该不是,其实还是字段名,也就是要被注入依赖bean的实例的属性名称。
Q:注解里的value()不需要知道value=“happy" ,name()是要指定name = "computer" ?比如下面两个注解
@Resource(name = "computer") @Qualifier("chineseLanguage")
A:一般注解中如果只有一个值,比如@Qualifier注解中只有一个变量 String value() default ""; 则不需要指定变量名;如果有多个值,最好还是要指定变量名与值的键值对,想这样: @Resource(name = "computer");
而像@RequestMapping("/doPrepayment") 这种有多个值的,当不写变量名时,默认是value变量的值。
Q: Spring 的事件处理是单线程的?
Q:Spring事件处理一般在什么情况下使用?
Q:JdbcTemplate 类的实例是线程安全配置的.JdbcTemplate是怎么做到线程安全的?需要并发编程知识解释。
Q:JdbcTemplate 类框架的经典和最受欢迎的方法。这是管理所有数据库通信和异常处理的中央框架类。为什么这么说,是因为其他dao框架都是在Q:JdbcTemplate 上包装出来的么?
Q://无法获得到想要的实现类,还需要研究下像注解@Autowire是怎么把实现类注入到接口中的,或者订单是怎么通过serviceFactory来通过接口获取到实现类的//DeveloperDao developerDao = (DeveloperDao)context.getBean("DeveloperDao"); ?
Q:如下,为什么DriverManagerDataSource及其父类都没有driverClassName,这个属性在哪呢?
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/hello_spring"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean>
A:Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系。有基于构造函数和基于 setter 方法的 两种DI方法。
<property name="driverClassName" value="com.mysql.jdbc.Driver"/> 对应依赖注入的是这样的setter方法。 public void setDriverClassName(String driverClassName) { }
Q:EJBs 需要一个应用程序服务器,但 Spring 事务管理可以在不需要应用程序服务器的情况下实现。应用程序服务器是指一个专门的事务管理系统么?
Q:如果一个方法被注解和配置事务扫描扫到执行事务怎么样?
Q:这里只展示了aop面向切面的事务配置,注解事务呢,如何配置?
Q:Controller类的方法上的参数比如ModelMap model 里的数据是怎么传输的呢?记得好像是键值对映射上了就可以获取和设置值?
Q:新下载的tomcat 8 使用时控制台输出乱码?
纪实
第一个hello world 项目实现。
首先通过maven创建一个maven-archetype-webapp 项目。
创建maven项目约定的项目结构文件夹。
创建一个配置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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd"> <!--<context:component-scan base-package="com"/>--> <!--<context:annotation-config/>--> <bean id="message" class="com.self.Message"> <property name="msg" value="hello lairf"></property> </bean> </beans> //如果是要通过扫描来注入bean,则要加个@Component注解 //@Component public class Message { private Integer code; private String msg; public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } public class MainApp { public static void main(String[] args) { //获得容器 ApplicationContext context = new ClassPathXmlApplicationContext("spring/appcontext-core.xml"); Message message = (Message)context.getBean("message"); String msg = message.getMsg(); System.out.println("================================"+msg); } }
配置中心存储架构
redis + git
缓存 + 版本控制