Spring是什么:Spring是一个轻量级的开源的JavaEE容器框架
核心技术:
控制反转IOC(Inversion Of Control,IOC):对组件的依赖项的控制由组件内部管理改为由外部容器提供。控制反转包括两种方式:
依赖查找(Dependency Lookup,DL):组件向容器索取依赖项(主要用于单元测试)。依赖查找包括两种方式:
依赖拉取(Dependency Pull,DP):组件通过指定的容器获取依赖项。
例如:new ClassPathXmlApplicationContext("applicationContext.xml").getBean("dao", Dao.class);
上下文依赖查找(Contextualized Dependency Lookup,CDL):组件通过动态传入的容器获取依赖项。
例如:public Dao getDaoBean(String data, BeanFactroy beanFactory) {return beanFactory.getBean("dao", Dao.class);}
依赖注入(Dependency Injection,DI):容器向组件注入依赖项。依赖注入包括三种方式:
构造器注入:容器通过组件的有参构造器传入依赖项。
Setter注入:容器通过组件的Setter方法传入依赖项。
接口注入(过时):容器通过组件实现的接口的依赖注入方法传入依赖项。
例如:@Override public void inject(Dao dao) {this.dao = dao;}
参考文章:
Spring学习笔记(1.1):什么是依赖注入,依赖注入的实现_什么是依赖注入,如何实_大苏打seven的博客-优快云博客
Spring学习笔记(1.2):什么是依赖查找,依赖查找的实现方式_大苏打seven的博客-优快云博客
面向切面编程(aspect-oriented programming,AOP):把遍布应用各处的相同功能分离出来形成可重用的组件。(实现通用功能)
下载地址
http://repo.spring.io/release/org/springframework/spring/
体系结构:
IoC | Bean(BeanFactory) | 负责依赖类之间的创建、拼接、管理、获取 |
Core | ||
Context(ApplicationContext) | 邮件服务、任务调度、JNDI获取、EJB集成、远程访问 | |
Expression 表达式语言(统一表达式语言Unified EL的扩展) | 查询和管理运行期对象(设置/获取对象属性、调用对象方法、操作数组集合等)、逻辑表达式运算、变量定义 | |
Context Support | ||
AOP | Spring AOP | 满足AOP Alliance规范的实现 |
Aspects | 第三方框架 | |
Instrument | java.lang.instrument,允许在JVM启动时启用一个代理类,在运行时修改类字节的代码,改变一个类的功能,从而实现AOP功能。 | |
Instrument Tomcat | ||
DAO | Spring JDBC | |
ORM | Hibernate、JPA、TopLink、JDO、OJB、iBatis | |
OXM | ||
JMS | ||
Messaging | ||
Transaction | 借住AOP技术,Spring提供了声明式事务的功能。 | |
Web及远程调用 | MVC | 整合Struts、WebWork等MVC框架,自己也提供SpringMVC框架 整合FreeMarker、Thymeleaf、Velocity、Jsp等页面模板技术 |
Portlet | ||
Web Service | ||
WebSocket | 提供了一个在Web应用中高效、双向的通信。应用场景:在线交易、游戏、协作、数据可视化等。 | |
测试框架 | Test |
Spring容器是什么:是Spring框架的一个核心模块,用来管理对象。有两种类型:接口BeanFactory和子接口ApplicationContext。
ApplicationContext在加载配置文件时就创建bean对象,BeanFactory在获取bean时才会创建对象。
ApplicationContext拥有更多的企业级方法,推荐使用。ApplicationContext接口常用实现类:
AnnotationConfigApplicationContext | 从配置类中加载Spring应用上下文 |
AnnotationConfigWebApplicationContext | 从配置类中加载Spring Web应用上下文 |
ClassPathXmlApplicationContext | 从类路径下的XML配置文件中加载上下文 |
FileSystemXmlApplicationContext | 从文件系统下的XML配置文件中加载上下文 |
XmlWebApplicationContext | 从Web应用下的XML配置文件中加载上下文 |
创建Spring容器
XML方式 | 注解方式 | |
步骤一、导包:Tomcat包、spring-webmvc包。spring-webmvc包如下:
| ||
步骤二、添加Spring配置文件src\main\resources\applicationContext.xml (idea在resources文件夹下右键-》New-》XML Configuration File-》Spring Config) 如果是Spring5,配置文件内容: | 步骤二、添加Spring配置类: (如果是Spring Boot,启动类已集成配置类,不需要另加配置类) | |
<?xml version="1.0" encoding="UTF-8"?> </beans> | package com.tongwx.demo.entity.config;
public class SpringConfig { | |
步骤三、加载配置文件以创建Spring容器。 | 步骤三、加载配置类以创建Spring容器: | |
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); | ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); |
Bean不同配置方式比较
基于XML配置 | 基于注解配置 | 基于Java类配置 | |
定义 | 在XML文件中通过<bean>元素定义Bean,例如<bean class="com.bbt.UserDao"/> | 在Bean实现类处通过标注@Component或衍型类(@Repository @Service @Controller)定义Bean | 在标注了@Configuration的java类中,通过在类方法上标注@Bean定义一个Bean,方法必须提供Bean的实例化逻辑 |
名称 | 通过<bean>的id或name属性定义,例如:<bean id="userDao" class="com.bbt.UserDao">默认名称为com.bbt.UserDao#0 | 通过注解的value属性定义,如@Component("userDao")默认名称为小写字母打头的类名(不带包名)userDao | 通过@Bean的name属性定义,如@Bean("userDao")默认名称为方法名 |
注入 | 通过<property>子元素或通过p命名空间的动态属性,如p:userDao-ref="userDao"进行注入 | 通过在成员变量或方法入参处标注@Autowired,按类型匹配自动注入。还可以配合使用@Qualifier按名称匹配方式注入 | 比较灵活,可以在方法处通过@Autowired使方法入参绑定bean,然后在方法中通过代码进行注入,还可以通过调用配置类的@Bean方法进行注入 |
生命过程方法 | 通过<bean>的init-method和destroy-method属性指定Bean实现类的方法名,最多只能指定一个初始化和一个销毁方法 | 通过在目标方法上标注@PostConstruct和@PreDestroy注解指定初始化或销毁方法,可以定义任意多个方法 | 通过@Bean的initMethod或destoryMethod指定一个初始化或销毁方法。对于初始化方法来说,可以直接在方法内部通过代码的方式灵活初始化逻辑 |
作用范围 | 通过<bean>的scope属性指定,常用于指定原型作用域:<bean class="..." scope="prototype"> | 通过在类定义处标注@Scope指定。如@Scope("prototype") | 通过在Bean方法定义处标注@Scope指定 |
延迟初始化 | 通过<bean>的lazy-init属性指定,默认为default,继承与<beans>的default-lazy-init设置,该值默认为false | 通过在类定义处标注@lazy指定,如@Lazy(true) | 通过在Bean方法定义处标注@Lazy指定 |
适用场合 | Bean实现类来源于第三方类库,如DataSource,JdbcTemplate等,因无法在类中标注注解,通过XML配置方式较好。命名空间的配置,如aop context等,只能采用基于XML的配置 | Bean的实现类是当前项目开发的,可以直接在Java类中基于注解的配置 | 基于Java类配置的优势在于可以通过代码控制Bean初始化的整体逻辑。所以如果实例化Bean的逻辑比较复杂,则比较适合用基于Java类配置的方式 |
IOC创建对象的四种方式
xml中: | java中: |
<!— 方式一:用无参构造器定义对象 id或name属性指定Bean的名称,用于从Spring中查找这个Bean对象。 name属性是早期为struct1提供的,可含特殊字符。 可用alias属性指定另外一个名称引用。 class属性指定Bean的类型。Spring容器将自动调用无参构造器创建此Bean对象。 id或name要唯一,class要全名, --> <bean id="date1" class="java.util.Date" /> <!— 方式二:用静态工厂方法定义对象 (了解) class属性指定工厂类,factory-method属性指定工厂方法 --> <bean id="cal1" class="java.util.Calendar" factory-method="getInstance" /> <!— 方式三:用实例工厂方法定义对象 (了解) factory-bean属性指定工厂bean对象,factory-method属性指定工厂方法 --> <bean id="date2" factory-bean="cal1" factory-method="getTime" /> | //方式一:用无参构造器创建对象 //Date date1 = new Date(); Date date1 = ac.getBean("date1", Date.class); //方式二:用静态工厂方法创建对象 (了解) //Calendar cal1 = Calendar.getInstance(); Calendar cal1 = ac.getBean("cal1", Calendar.class); //方式三:用实例工厂方法创建对象 (了解) //Date date2 = cal1.getTime(); Date date1 = ac.getBean("date2", Date.class); |
方式四:用工厂bean创建对象 | |
<bean id="myFactoryBean" class="com.tongwx.demo.entity.MyFactoryBean"/> | package com.tongwx.demo.entity; |
Emp emp = ac.getBean("myFactoryBean", Emp.class); |
IOC注入参数(DI)的四种方式
@Data @NoArgsConstructor @AllArgsConstructor class Computer implements Serializable { private String brand; private Usb usb; } | interface Usb {} class UsbMouse implements Usb {} class UsbKeyboard implements Usb {} |
第一种方式:Setter注入(重点) | Setter注入时,bean类要提供相应的set方法。 |
<bean id="usbMouse" class="ioc.UsbMouse" /> <bean id="usbKeyboard" class="ioc.UsbKeyboard" /> <bean id="computer1" class="ioc.Computer"> <property name="brand" value="苹果" /> <property name="usb" ref="usbMouse" /> </bean> | <!— 使用property子元素进行Setter注入,其中: name属性:参数名。 value属性:基本类型参数值 ref属性:引用类型参数值(即要注入的bean的id)。 --> |
第二种方式:构造器注入(了解) | 构造器注入时,bean类要提供相应的带参构造器。 |
<bean id="usbMouse" class="ioc.UsbMouse" /> <bean id="usbKeyboard" class="ioc.UsbKeyboard" /> <bean id="computer2" class="ioc.Computer"> <constructor-arg name="brand" value="微软"/> <constructor-arg name="usb" ref="usbKeyboard"/> </bean> <bean id="computer3" class="ioc.Computer"> <constructor-arg index="0" value="微软"/> <constructor-arg index="1" ref="usbKeyboard"/> </bean> | <!— 使用constructor-arg子元素进行构造器注入,其中: name属性:参数名 value属性:基本类型参数值 ref属性:引用类型参数值(即要注入的bean的id)。 --> <!— index属性:构造器参数下标(从0开始) type属性:构造器参数类型(防止误注)。 --> |
第三种方式:p名称空间注入(对Setter注入的简化)(了解) | |
<?xml version="1.0" encoding="UTF-8"?> </bean> | Step1:在顶级的<beans/>元素中增加一个命名空间属性:xmlns:p="http://www.springframework.org/schema/p" Step2:在bean元素中使用“p:参数名”属性注入基本类型参数值,使用“p:参数名-ref”属性注入引用类型参数值。 |
第四种方式:自动装配注入(autowiring)(了解)
默认情况下,容器不会自动装配,可以设置autowire属性,让容器依据某些规则来注入相应的对象。autowire属性值有5个:
no:禁用自动装配,默认值。
byName:容器以属性名作为bean的id来查找符合条件的bean,找到之后,调用对应的set方法来注入。如果找不到符合条件的bean,注入null。
byType:容器以属性类型作为bean的类型来查找符合条件的bean,找到之后,调用对应的set方法来注入。若无符合条件的bean,注入null;若有多个,则报错。
constructor:容器以属性类型作为bean的类型来查找符合条件的bean,找到之后,调用对应的构造器来注入。
autodetect:通过Bean类来决定是使用constructor还是byType方式进行自动装配。如果发现默认的构造器,那么将使用byType方式。
在顶级的<beans/>元素中的default-autowire属性,可以为容器所有<bean>指定自动装配。
package auto; @Data @NoArgsConstructor public class Restaurant { private Waiter wt; } | package auto; @NoArgsConstructor public class Waiter { } |
<bean id="wt1" class="auto.Waiter"/> <bean id="wt2" class="auto.Waiter"/> <bean id="rest" class="auto.Restaurant" autowire="constructor"/> |
非Spring框架使用的其他DI方式:接口注入(不提倡)
将调用类所有依赖注入的方法抽取到一个接口中,调用类通过实现该接口提供相应的注入方法。
//导演(调用类 public class Director { public static void main(String[] args) { MovieScript script = new MovieScript(); Actor actor = new LiuDeHua(); script.injectActor(actor); //指定注入的演员 script.begin(); } } | //演员 class Actor { public void say(String lines) { System.out.println(lines); } } class LiuDeHua extends Actor { } class ZhouRunFa extends Actor { } |
//剧本(实现依赖注入的接口,重写注入方法) class MovieScript implements ActorArrangable { private Actor actor; //重写依赖注入方法 @Override public void injectActor(Actor actor) { this.actor = actor; } public void begin() { actor.say("Hello DI"); } } | //演员安排(依赖注入的接口) interface ActorArrangable { //依赖注入方法 public void injectActor(Actor actor); } |
IOC注入不同类型参数值(以Setter注入为例)
package value; import java.util.*; @Data public class ValueBean { private String name; private String country; private List<String> interest; private Set<String> city; private Map<String, Double> score; private Properties db; private SpelBean sb; } | package value; @Date public class SpelBean { private String name; private String interest; private Double score; private String pageSize; } |
注入基本或String类型的值:使用value属性或value子元素
方式一:使用value属性: | 方式二:或使用value子元素: |
<bean id="vb1" class="value.ValueBean"> <property name="name" value="小明" /> </bean> | <bean id="vb1" class="value.ValueBean"> <property name="name"> <value>小明</value> </property> </bean> |
注入null、空字符串、特殊字符:
<bean id="vb1" class="value.ValueBean"> <property name="name"> <null/> </property> <property name="country" value=""/> </bean> | <bean id="vb1" class="value.ValueBean"> <property name="name"> <![CDATA[<苹果>]]> </property> <property name="country" value="<China>"/> </bean> |
注入其他bean对象(可同时修改所注入bean的属性):
方式一:使用注入外部bean方式: | 方式二:使用注入内部bean方式(类似于Java内部类): |
<bean id="sb" class="value.SpelBean" /> <bean id="vb" class="value.ValueBean"> <property name="sb" ref="sb" /> <!— 注入其他bean对象可同时修改其属性 --> <property name="sb.name" value="new name" /> </bean> | <bean id="vb" class="value.ValueBean"> <property name="sb"> <bean id="sb" class="value.SpelBean" /> </property> </bean> |
注入集合类型的值(array、List、Set、Map、Properties):用子元素方式或用引用方式
用子元素方式: | 或用引用方式(方便值的重用,须引入util名称空间): |
<bean id="vb1" class="value.ValueBean"> <!-- 注入array/List/Set,三者可通用 --> <property name="skill"> <array> <!—集合元素为基本类型时--> <value>吃</value> <value>喝</value> </array> </property> <property name="interest"> <list> <!—集合元素为其他bean类型时--> <ref bean="其他bean的id"></ref> <ref bean="其他bean的id"></ref> </list> </property> <property name="loves"> <set> <value>梅</value> <value>兰</value> <value>菊</value> <value>竹</value> </set> </property> <!-- 注入Map --> <property name="score"> <map> <entry key="英语" value="80"/> <entry key="math" value="90"/> </map> </property> <!-- 注入Properties --> <property name="db"> <props> <prop key="username">King</prop> <prop key="password">1234</prop> </props> </property> </bean> | <?xml version="1.0" encoding="UTF-8"?> <!-- 被注入的List --> <util:list id="interestBean"> <value>游泳</value> <value>台球</value> </util:list> <!-- 被注入的Set --> <util:set id="cityBean"> <value>北京</value> <value>长沙</value> </util:set> <!-- 被注入的Map --> <util:map id="scoreBean"> <entry key="english" value="80"/> <entry key="math" value="90"/> </util:map> <!-- 被注入的Properties --> <util:properties id="dbBean"> <prop key="username">Sally</prop> <prop key="password">1234</prop> </util:properties> <!-- 注入List、Set、Map、Properties --> <bean id="vb2" class="value.ValueBean"> <property name="interest" ref="interestBean"/> <property name="city" ref="cityBean"/> <property name="score" ref="scoreBean"/> <property name="db" ref="dbBean"/> </bean> </beans> |
注入其他bean的属性:
<bean id="sb1" class="value.SpelBean"> <!-- 注入其他Bean的属性 --> <property name="name" value="#{vb1.name}"/> <!-- 注入其他Bean的集合类型属性中的元素 --> <property name="skill" value="#{vb1.skill[1]}"/> <property name="score" value="#{vb1.score['英语']}"/> </bean> |
注入properties文件的内容
<?xml version="1.0" encoding="UTF-8"?>
|
Bean的作用域
Bean的作用域由scope属性指定,默认值为singleton(单例),容器只会创建一个实例。
当值为prototype(原型)时,会创建多个实例。
singleton(默认): 在每个Spring IoC容器中一个Bean定义对应一个对象实例,实例在加载配置文件时就创建。
prototype: 一个Bean对应多个对象实例,每个实例在Spring容器调用getBean方法时才创建。
request: 在一次HTTP请求中,一个Bean定义对应一个实例,仅限于Web环境。
session: 在一个HTTPSession中,一个Bean定义对应一个实例,仅限于Web环境。
global Session: 在一个全局的HTTPSession中,一个Bean定义对应一个实例,仅在基于portlet的Web应用中才有意义,Portlet规范定义了全局Session的概念。
Bean的生命周期
- 调用无参构造函数创建bean实例并调用Setter方法设置属性值,或者调用有参构造函数创建bean实例。
- 若配置有前后置处理器BeanPostProcessor,执行后置处理器中的postProcessBeforeInitialization方法。
- 若配置有init-method或default-init-method,执行所指定的初始化方法。
- 若配置有前后置处理器BeanPostProcessor,执行后置处理器中的postProcessAfterInitialization方法。
- 若单例bean配置有destroy-method或default-destroy-method,且调用了classPathXmlApplicationContext.close()方法,执行所指定的单例bean销毁方法
在单个<bean>元素中:
init-method属性:指定初始化方法(常用于获取资源)
destroy-method属性:指定销毁方法(常用于释放资源)(由classPathXmlApplicationContext.close()触发,只对单例bean有效)
在顶级的<beans/>元素中:
default-init-method属性:为容器所有<bean>指定初始化回调方法
default-destroy-method属性:为容器所有<bean>指定销毁回调方法
在顶级的<beans/>元素下:
前后置处理器<bean id="bean的id" class="org.springframework.beans.factory.config.BeanPostProcessor的实现类"/>为容器所有<bean>指定初始化前的处理逻辑(postProcessBeforeInitialization)和初始化后的处理逻辑(postProcessAfterInitialization)
注:属性值中只需写方法名,不要附加括号()。
Bean的延迟加载 (了解)
默认情况下,当Spring容器启动之后,会创建所有singleton bean。
在单个<bean>元素中的lazy-init="true"属性:设置该单例bean等到Spring容器调用getBean方法时才创建。
在顶级的<beans/>元素中的default-lazy-init="true"属性,可以为容器所有<bean>指定延迟加载。
适用于使用频率很低的单例对象。
JavaConfig配置用来代替Spring XML配置(完全使用注解开发)
使用场景一:配置IOC、AOP注解扫描(Spring Boot默认开启,无需此配置)
package com.tongwx.demo.entity.config;
@EnableAspectJAutoProxy(proxyTargetClass = true)//Spring Boot默认开启 public class SpringConfig { |
@Test |
使用场景三:装配第三方库中的组件(无法使用自动化装配)
编程步骤
- 在要扫描的包中创建一个带有@Configuration注解的配置类(配置类本身也是Spring的Bean)。
- 在配置类中创建返回要使用的类的实例的方法,并在方法上添加@Bean注解,返回的实例就会成为Spring的Bean实例,方法名就是Bean实例的id,可使用@Bean的属性来自定义Bean实例的id
package soundsystem; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CDPlayerConfig {
@Bean public CompactDisc compactDisc() { return new SgtPeppers(); }
@Bean("myCdPlayer") public CDPlayer cdPlayer(CompactDisc compactDisc) { return new CDPlayer(compactDisc); } } |
@Configuration的proxyBeanMethods属性
@Configuration的proxyBeanMethods属性(始于Spring 5.2)指定该配置类是否被代理(CGLIB),默认为true
值为true时,调用配置类的方法获取的bean是单例的(Full全模式)。
值为false时,调用配置类的方法获取的bean是多例的(Lite轻量级模式)。
(?)如果配置类组件之间无依赖关系,可用Lite模式加速容器启动过程,减少判断。
package com.example.demo; | |
package com.example.demo.config; | |
package com.example.demo.entity; | package com.example.demo.entity; |
组件扫描相关注解类型
组件扫描和参数相关注解
组件扫描(component scanning)相关注解:
指定一个包路径,容器会扫描指定包及其子包下的所有类,如果类前面添加了以下注解,则容器会将这个类纳入容器进行管理(相当于配置了一个bean,bean的id默认值等于类名首字母小写,亦可自定义,自定义形式为在注解后面加上:("自定义名称"))。
@Controller 控制层组件注解
@Service 业务层组件注解
@Repository 持久化层组件注解
@Component 通用注解
@Named 通用注解(需额外导包:JSR-330的javax.inject-1.jar,不常用)
指定组件作用域的注解:Spring组件作用域通常为singleton,可在组件类前指定其他作用域:
@Scope("prototype") 指定组件作用域为原型
指定组件延迟加载的注解:在组件类前添加
@Lazy(true) 延迟加载
指定初始化方法和销毁回调方法的注解:在初始化和销毁回调的方法前添加:
@PostConstruct 指定初始化方法
@PreDestroy 指定销毁方法(只对singleton模式的bean有效)
依赖注入相关注解
注入参数类型 | 可注入范围 | 写法及装配规则 | |
@Value("值") 必须写参数 | 基本类型 Spring表达式 | 字段注入 Setter注入 |
|
@Resource JDK的注解(始于1.6) 默认按照名称装配 | 引用类型 | 字段注入 Setter注入(推荐) | 写法:写在字段前、set方法前。 装配:默认按照名称装配,具体装配顺序: |
@Autowired和@Qualifier 默认按照类型装配 | 引用类型 | 字段注入 Setter注入 构造器注入(推荐) | 写法: @Autowired写在字段前、set方法前、构造方法前(写在字段前时不会执行set方法中除属性赋值外其他代码),声明要为其注入bean;(@Autowired不要写在参数前) @Qualifier写在字段前、set方法或其参数前、构造方法参数前,声明要注入的bean的id。 装配:默认按照类型装配(如注入单例bean)。 若要按名称装配,追加注解@Qualifier("名称") 若要允许null值,@Autowired(required=false) |
@Inject和@Named 需额外导包:JSR-330的javax.inject-1.jar | 引用类型 | 字段注入 Setter注入 构造器注入 | 和@Autowired和@Qualifier用法一致,需要额外导包。 |
三种注入详解
注入基本类型参数 | 注入引用类型参数 | |
属性注入(不建议,破坏封装) | 在属性前添加@Value | 在属性前添加@Resource 或者@Autowired和@Qualifier |
Setter注入 | 在set方法前添加@Value 或者在set方法前添加@Autowired,在参数前添加@Value | 在set方法前添加@Resource 或者在set方法前添加@Autowired,在set方法前或参数前添加@Qualifier |
构造器注入 | 在构造器前添加@Autowired 在基本类型参数前添加@Value | 在构造器前添加@Autowired 在引用类型参数前添加@Qualifier |
总结
属性注入推荐使用@Value或@Resource,Setter注入推荐使用@Resource,构造器注入推荐使用@Autowired和@Qualifier
注入顺序:构造器注入 → 属性注入 → Setter注入 (后者覆盖前者)
属性注入 | setter注入 | 构造器注入 | 默认装配方式 | |
@Value("值") 必须写参数 | 基本类型推荐 | 基本类型推荐 | ||
@Autowired和@Value("值") | 基本类型推荐 | |||
@Resource JDK的注解(始于1.6) | 引用类型推荐 | 引用类型推荐 | 按名称 | |
@Autowired和@Qualifier | 可以 | 可以 | 引用类型推荐 | 按类型 |
事务注解
@Transactional
编程步骤
step1. 在配置文件中使用context:component-scan元素来指定要扫描的包(须引入context名称空间,多个包用,隔开):<context:component-scan base-package="annotation" />
step2. 在类前添加组件扫描相关注解,比如@Component。
step3. 给基本类型的参数注入值(使用@Value)。
step4. 给引用类型的参数指定依赖注入关系(类属性和Setter参数使用@Resource;构造器参数使用@Autowired和@Qualifier)
package annotation; import org.springframework.stereotype.Component; @Component("wt") public class Waiter { public Waiter() { System.out.println("Waiter()"); } } |
package annotation; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; // 在类前添加组件扫描相关注解,参数为bean的id,若无参数则id默认为类名首字母小写 @Component("ht") // 如有需要,指定组件的作用域,不指定则默认为“singleton” // @Scope("prototype") // 如有需要,指定组件延迟加载 // @Lazy(true) public class Hotel { // 给基本类型的属性注入值 @Value("假日酒店") private String name; // 给基本类型的属性注入配置文件中的值 @Value("#{config['jdbc.totalRooms']}") private String totalRooms; // 注入的对象为单例时,可省略(name="…"),此时按照类型匹配参数 @Resource(name = "wt") // 或者加在属性前,此时不会执行set方法中除属性赋值外其他代码 private Waiter wt; // 注入的对象为单例时,可省略(name="…"),此时按照类型匹配参数 @Resource(name = "wt") // 或者加在set方法前 public void setWt(Waiter wt) { System.out.println("setWt()"); this.wt = wt; } // set方式注入(方法二:在属性前使用注解,此时不会执行set方法中除属性赋值外其他代码) @Autowired @Qualifier("wt") private Waiter wt; // set方式注入(方法一:在setXxx()方法前、中使用注解) @Autowired // 注入的对象为单例时,可省略@Qualifier,此时按照类型匹配参数 public void setWt(@Qualifier("wt") Waiter wt) { System.out.println("setWt()"); this.wt = wt; } public Hotel() { System.out.println("Hotel()"); } //构造器方式注入:在构造器前、中使用注解 @Autowired // 注入的对象为单例时,可省略@Qualifier,此时按照类型匹配参数 public Hotel(@Qualifier("wt") Waiter wt) { this.wt = wt; } @PostConstruct // 指定初始化方法 public void init() { System.out.println("init()"); } @PreDestroy // 指定销毁回调方法 public void destroy() { System.out.println("destroy()"); } } |
# config.properties Floor1.totalRooms=10 |
<!-- annotation.xml --> <!—其他代码 --> <context:component-scan base-package="annotation" /> <util:properties id="config" location="classpath:config.properties" /> <!—其他代码 --> |
如果只想扫描某个注解例如@Controller:
<context:component-scan base-package="com.tongwx.demo" use-default-filters="false"> |
如果要排除扫描某个注解例如@Controller:
<context:component-scan base-package="com.tongwx.demo"> |
AOP所需jar包
主包:
spring-aop-4.0.2.RELEASE.jar
依赖包:
aopalliance.jar
aspectjrt.jar
aspectjweaver.jar
Spring3.2起已集成AOP所需包。
AOP术语介绍
一句话搞定AOP术语:切点表达式规定了目标的哪些连接点作为切面的切点,通知类型规定了何时可以被切面的方法织入
什么是AOP
面向切面编程(aspect-oriented programming,AOP),是指将遍布应用各处的相同功能分离出来形成可重用的组件,例如日志记录、性能统计、安全控制、事务处理、异常处理等,在应用运行时,动态地将可重用组件切入到指定类、指定方法、指定位置上去。
优点:重用、解耦。
什么是切面(Aspect):封装共通处理的组件,该组件被作用到其他目标组件方法上。(切面:增强的逻辑)
什么是目标(Target):被一个或多个切面所作用的对象。(目标:被增强的对象)
什么是连接点(Join Point):能够插入切面方法的所有点(连接点:可被增强的方法)。
什么是切入点(Pointcut):需要使用切面功能的组件或方法(需要插入切面方法的某些点)(切入点:实际被增强的方法),
在Spring中用切入点表达式指定切入目标。常用的切入点表达式有:
方法限定表达式:execution(修饰符? 返回类型 方法名(参数) throws 异常类型?)
类型限定表达式:within(包名.类型)
Bean名称限定表达式:bean("Bean的id或name属性值")
非Bean名称限定表达式:!bean("Bean的id或name属性值")
Spring还支持以下切点表达式:
arg() 限制连接点匹配参数为指定类型的执行方法
@args() 限制连接点匹配参数由指定注解标注的执行方法
this() 限制连接点匹配AOP代理的bean引用为指定类型的类
target 限制连接点匹配目标对象为指定类型的类
@target() 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
@within() 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里)
@annotation 限定匹配带有指定注解的连接点
什么是通知(Advice):用于指定切面组件在目标组件上作用的时机(执行的先后顺序)。
Spring框架提供以下5种类型的通知:
前置通知(Before): 切面方法在目标方法之前执行;
环绕通知(Around): 执行切面方法前半部分 → 执行目标方法 → 执行切面方法后半部分。
返回通知/后置通知(After-returning): 切面方法在目标方法之后执行(目标需无异常,可自动获取目标方法的返回值如果有);
异常通知(After-throwing): 切面方法在目标方法抛出异常后执行;
后置通知/最终通知(After): 切面方法在目标方法之后执行(不管目标方法异常否,无法获取目标方法的返回值即使有);
5种通知的代码结构:
try{
//执行环绕通知前置部分对应的切面方法
//执行前置通知对应的切面方法
//执行目标组件方法
//执行环绕通知后置部分对应的切面方法
//执行返回通知对应的切面方法
}catch(){
//执行异常通知对应的切面方法
}finally{
//执行后置通知对应的切面方法
}
什么是引入(Introduction)
引入允许我们向现有的类添加新方法或属性。例如,我们可以创建一个Auditable通知类,该类记录了对象最后一次修改时的状态。这很简单,只需一个方法,setLastModified(Date),和一个实例变量来保存这个状态。然后,这个新方法和实例变量就可以被引入到现有的类中,从而可以在无需修改这些现有的类的情况下,让它们具有新的行为和状态。
什么是织入(Weaving)
织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:
编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入(load-time weaving,LTW)就支持以这种方式织入切面。
运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。
SpringAOP实现原理:基于动态代理技术
AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理的代表为Spring AOP。
(1)所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(2)所谓动态代理,就是AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring采用了两种动态代理实现:
利用JDK Proxy API:目标类有接口时,代理类实现目标类的接口并代理目标类的方法(重写接口方法并加入切面组件功能)
利用CGLib工具包:目标类无接口时,代理类继承目标类并代理目标类(父类)的方法(重写目标方法并加入切面组件功能)
Spring对AOP的支持局限于运行时织入,无法进行编译期织入和类加载期织入;
Spring对AOP的支持局限于方法拦截,无法拦截字段(无法拦截对字段的修改),无法拦截构造器(无法在bean创建时应用切面);
但是方法拦截可以满足绝大部分的需求。如果需要方法拦截之外的连接点拦截功能,那么我们可以利用Aspect来补充Spring AOP的功能。
XML配置实现AOP
开发步骤:
创建切面组件:创建一个类,充当切面组件,实现通用业务逻辑。
声明切面组件:在applicationContext.xml中,声明切面组件。
使用切面组件:在applicationContext.xml中,将切面组件作用到目标组件方法上并设置通知类型以确认切面组件调用的时机。
使用前置通知:
切面组件中编写通用业务逻辑:
public void log(){…}
applicationContext.xml中配置(须引入aop名称空间):
<aop:config>
<aop:aspect ref="optLogger">
<aop:before method="log" pointcut="within(controller..*)" />
</aop:aspect>
</aop:config>
使用后置通知:方法同前置通知,只需将aop:before改为aop:after-returning即可。
使用最终通知:方法同前置通知,只需将aop:before改为aop:after即可。
使用环绕通知:
切面组件中编写通用业务逻辑:
public Object log(ProceedingJoinPoint p)throws Throwable{
//此处代码在目标组件前执行
Object obj = p.proceed(); //执行目标组件方法
//此处代码在目标组件后执行
return obj;
}
applicationContext.xml中配置:
<aop:aspect ref="optLogger">
<aop:around method="log" pointcut="within(controller..*)" />
</aop:aspect>
使用异常通知:
切面组件中编写通用业务逻辑:
public void log(Exception e){…}
applicationContext.xml中配置:
<aop:aspect ref="optLogger">
<aop:after-throwing
method="log" throwing="e" pointcut="within(controller..*)" />
</aop:aspect>
<aop:aspect>与<aop:advisor>的区别
在面向切面编程时,我们会使用<aop:aspect>;在进行事务管理时,我们会使用<aop:advisor>。
两者的区别:
实现方式不同
<aop:aspect>定义切面时,只需要定义一般的bean就行,而定义<aop:advisor>中引用的通知时,通知必须实现Advice接口。
下面我们举例说明。
首先,我们定义一个接口Sleepable和这个接口的实现Human,代码如下:
public interface Sleepable {
public void sleep();
}
public class Human implements Sleepable {
@Override
public void sleep() {
System.out.println("我要睡觉了!");
}
}
下面是<aop:advisor>的实现方式:
//定义通知
public class SleepHelper implements MethodBeforeAdvice,AfterReturningAdvice{
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("睡觉前要脱衣服!");
}
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("起床后要穿衣服!");
}
}
//aop配置
<bean id="sleepHelper" class="com.ghs.aop.SleepHelper"></bean>
<aop:config>
<aop:pointcut expression="execution(* *.sleep(..))" id="sleepPointcut"/>
<aop:advisor advice-ref="sleepHelper" pointcut-ref="sleepPointcut"/>
</aop:config>
<bean id="human" class="com.ghs.aop.Human"/>
下面是<aop:aspect>的实现方式:
//定义切面
public class SleepHelperAspect{
public void beforeSleep(){
System.out.println("睡觉前要脱衣服!");
}
public void afterSleep(){
System.out.println("起床后要穿衣服!");
}
}
//aop配置
<bean id="sleepHelperAspect" class="com.ghs.aop.SleepHelperAspect"></bean>
<aop:config>
<aop:pointcut expression="execution(* *.sleep(..))" id="sleepPointcut"/>
<aop:aspect ref="sleepHelperAspect">
<!--前置通知,method指定切面方法,pointcut-ref指定切入点-->
<aop:before method="beforeSleep" pointcut-ref="sleepPointcut"/>
<!--后置通知,method指定切面方法,pointcut-ref指定切入点-->
<aop:after method="afterSleep" pointcut-ref="sleepPointcut"/>
</aop:aspect>
</aop:config>
<bean id="human" class="com.ghs.aop.Human"/>
测试代码如下:
public class TestAOP {
public static void main(String[] args) {
method1();
// method2();
}
private static void method1() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml");
Sleepable sleeper = (Sleepable) context.getBean("human");
sleeper.sleep();
}
private static void method2() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
Sleepable sleeper = (Sleepable) context.getBean("human");
sleeper.sleep();
}
//执行结果
睡觉前要脱衣服!
我要睡觉了!
起床后要穿衣服!
使用场景不同
<aop:advisor>大多用于事务管理。
例如:
<!-- 会重复读,不会脏读事务 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" timeout="120" propagation="REQUIRED" rollback-for="Exception" />
</tx:attributes>
</tx:advice>
<aop:config proxy-target-class="true">
<aop:pointcut id="txPointCut" expression="..."/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
</aop:config>
<aop:aspect>大多用于日志,缓存
小结:<aop:advisor>和<aop:aspect>其实都是将通知和切面进行了封装,原理基本上是一样的,只是使用的方式不同而已。
1、Adivisor是一种特殊的Aspect,Advisor代表spring中的Aspect
2、区别:advisor只持有一个Pointcut和一个advice,而aspect可以多个pointcut和多个通知
注解实现AOP
开发步骤:
创建切面组件:创建一个类,充当切面组件,实现通用业务逻辑。
声明切面组件:
- 在applicationContext.xml中,开启IOC注解扫描(须引入context名称空间)
- 在applicationContext.xml中,开启AOP注解扫描(须引入aop名称空间):
<aop:aspectj-autoproxy proxy-target-class="true" />
注意:如要AOP要切入Mybatis的Mapper文件,需将proxy-target-class="true"删去,因为Mapper是final的,不能基于类继承的代理。
- 使用@Component注解该类,将其声明为组件。
- 使用@Aspect注解该类,将其声明为切面组件。
- 在切面组件方法上,使用注解将切面组件作用到目标组件方法上,并设置通知类型以确认切面组件的调用时机。
使用前置通知:在切面组件的方法上增加注解:@Before("within(controller..*)*)
使用后置通知:在切面组件的方法上增加注解:@AfterReturning("within(controller..*)*)
使用最终通知:在切面组件的方法上增加注解:@After("within(controller..*)*)
使用环绕通知:在切面组件的方法上增加注解:@Around("within(controller..*)*)
使用异常通知:在切面组件的方法上增加注解:@AfterThrowing(pointcut="within(controller..*)", throwing="e")
- 如果不同切面方法上有相同的切入点表达式,可将相同的切入点表达式抽取成公共的切入点表达式。
- 如果不同切面同时织入相同切入点,可在切面类上用@Order注解定义织入优先级,@Order的值越小,切面的织入优先级越高
package com.tongwx.demo.config; import org.springframework.stereotype.Component; @Order(1) public class AopConfog { |
String事务简介
编程式事务
声明式事务
注解实现声明式事务
XML配置实现声明式事务
事务回滚
事务传播机制
事务传播机制 | 当前有事务 | 当前无事务 | |
PROPAGATION_REQUIRED(Spring默认) | 加入当前事务 | 新建事务 | |
PROPAGATION_SUPPORTS | 加入当前事务 | 非事务方式执行 | |
PROPAGATION_MANDATORY | 加入当前事务 | 抛异常 | |
PROPAGATION_REQUIRES_NEW | 挂起当前事务-》新建事务执行-》恢复当前事务 | 新建事务 | |
PROPAGATION_NOT_SUPPORTED | 挂起当前事务-》非事务方式执行-》恢复当前事务 | 非事务方式执行 | |
PROPAGATION_NEVER | 抛异常 | 非事务方式执行 | |
PROPAGATION_NESTED | 新建事务,并且在当前事务中嵌套其他事务 | 新建事务 |
REQUIRES_NEW
标志REQUIRES_NEW会新开启事务,外层事务不会影响内部事务的提交/回滚
标志REQUIRES_NEW的内部事务的异常,会影响外部事务的回滚
三种异常情况:
外层调内层前异常未捕获:外层事务回滚,内层事务未发生
内层异常未捕获:外层事务回滚,内层事务回滚
外层调内层后异常未捕获:外层事务回滚,内层事务已提交
事务不生效情况
- 数据库引擎不支持事务(如MySQL的MyISAM)
- 数据源未配置事务管理器
- 事务的传播行为设置为了Propagation.NOT_SUPPORTED
- 注解所在类未被加载成Bean
- 注解所在方法不是public方法
- 发生了自调用的情况:调用了本类中(this)的事务方法,但this指向的不是代理对象(未经过spring的代理),而是对象本身,可以通过注入一个本类变量的引用来调用方法。
- 异常被吃掉了
- 异常类型不正确,默认只对RuntimeException进行回滚
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
事务隔离级别
-
- 整合JDBC
Spring对DAO提供了哪些支持
Spring对DAO异常提供了统一处理
Spring把特定某种技术的异常,如SQLException,统一转化为自己的异常类型,这些异常以DataAccessException为父类。它们封装了原始异常对象,不会丢失原始错误信息。
DataAccessException继承于RuntimeException,是非检查异常,不会因为没有处理异常而出现编译错误。
异常必须处理,可以用拦截器或者在界面层统一处理。
Spring对DAO编写提供了支持的抽象类
Spring为了便于以一种一致的方式使用各种数据访问技术,如JDBC和Hibernate, Spring提供了一套抽象的DAO类。
这些抽象类提供了一些方法,通过它们可以获得与数据访问技术相关的数据源和其他配置信息。
• JdbcDaoSupport JDBC数据访问对象的基类
• HibernateDaoSupport Hibernate数据访问对象的基类
• JdbcTemplate 封装常用JDBC方法
• HibernateTemplate 封装常用Hibernate方法
SpringJDBC相关API
JdbcDaoSupport是利用JDBC技术编写DAO的父类,通过该类提供的方法,可便于获取Connection和JdbcTemplate等对象信息。
JdbcDaoSupport使用时需要注入一个DataSource对象。
JdbcDaoSupport对代码有一定的侵入性。
JdbcTemplate封装了连接获取以及连接释放等工作,从而简化了我们对JDBC的使用,避免了忘记关闭连接等错误。
JdbcTemplate提供了增删改查的方法:query(...)、queryForInt(...)、queryForObject(...)、update()、execute()
基于JDBC技术编写DAO组件可以采用下面两种模式
• DAO继承JdbcDaoSupport,通过getJdbcTemplate()方法获取JdbcTemplate对象,需要在DAO实现类中注入—个DataSource对象来完成JdbcTemplate的实例化
• (推荐)DAO不继承JdbcDaoSupport,在Spring容器中配置一个JdbcTemplate的bean,然后注入给DAO实现类
SpringJDBC编程步骤
step1. 导包 | step2. 在Spring配置文件(此例名为springjdbc.xml),配置连接池和JdbcTemplate |
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>3.2.8.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> | beans根元素 <!-- 读取config.properties文件 --> <util:properties id="config" location="classpath:config.properties" /> <!-- 配置连接池 --> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="#{config.classname}" /> <property name="url" value="#{config.url}" /> <property name="username" value="#{config.username}" /> <property name="password" value="#{config.password}" /> </bean> <!-- 配置JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="myDataSource" /> </bean> <!-- 开启组件扫描 --> <context:component-scan base-package="com.tongwx.dao" /> beans根元素 |
config.properties文件: | step3. 数据库测试表和实体类 数据库测试表 /day01/emp |
#mysql (Abbreviated form of URL for localhost: jdbc:mysql:///databaseName) classname=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/day01?useUnicode=true&characterEncoding=UTF-8 username=root password=root maxactive=1 maxwait=3000 | CREATE TABLE `emp` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
实体类 | step4. DAO接口(代码略) |
package com.tongwx.entity; @Data public class Emp { private Integer id; private String name; private Integer age; } | |
step5. 编写DAO实现类和修改Spring配置文件 | step5.测试代码 |
package com.tongwx.dao; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import com.tongwx.entity.Emp; /** * JdbcTemplate提供了很多方法,这些方法对jdbc api做了封装,简化了代码(比如不再需要考虑获取连接,关闭连接)。 * 另外,如果发生了异常,会转换成RuntimeException抛出。 */ @Repository("empDAO") public class JdbcEmpDAOImpl implements EmpDAO { @Resource private JdbcTemplate template; /** * 增 */ public void save(Emp emp) { String sql = "INSERT INTO emp(name,age) VALUES(?,?)"; Object[] params = { emp.getName(), emp.getAge() }; template.update(sql, params); } /** * 查数量 */ public int count() { String sql = "SELECT count(0) FROM emp"; return template.queryForObject(sql, Integer.class); } /** * 查全部 */ public List<Emp> findAll() { String sql = "SELECT * FROM emp"; return template.query(sql, new EmpRowMapper()); } /** * 查单条 */ public Emp findById(int id) { String sql = "SELECT * FROM emp WHERE id=?"; //Object[] args = { id }; //return template.queryForObject(sql, args, new EmpRowMapper()); return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Emp>(Emp.class), id); } /** * 查多条 */ public Emp findById2(int id) { Emp emp = null; String sql = "SELECT * FROM emp WHERE id=?"; Object[] args = { id }; List<Emp> emps = template.query(sql, args, new EmpRowMapper()); if (emps != null && emps.size() > 0) { emp = emps.get(0); } return emp; } /** * 告诉spring如何将记录转换成相应的实体对象 */ class EmpRowMapper implements RowMapper<Emp> { // index:记录的下标(从0开始) public Emp mapRow(ResultSet rs, int index) throws SQLException { Emp emp = new Emp(); emp.setName(rs.getString("name")); emp.setAge(rs.getInt("age")); emp.setId(rs.getInt("id")); return emp; } } /** * 改 */ public void modify(Emp emp) { String sql = "UPDATE emp SET name=?, age=? WHERE id=?"; Object[] args = { emp.getName(), emp.getAge(), emp.getId() }; template.update(sql, args); } /** * 删 */ public void delete(int id) { String sql = "DELETE FROM emp WHERE id=?"; Object[] args = { id }; template.update(sql, args); } } | package test; import java.sql.SQLException; import java.util.List; import javax.sql.DataSource; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.tongwx.dao.EmpDAO; import com.tongwx.dao.JdbcEmpDAOImpl; import com.tongwx.entity.Emp; public class TestCase { private EmpDAO dao; //Before注解修饰的方法会在其它测试方法运行之前先运行 @Before //启动spring容器 public void init() { String config = "springjdbc.xml"; ApplicationContext ac = new ClassPathXmlApplicationContext(config); dao = ac.getBean("empDAO", JdbcEmpDAOImpl.class); } @Test public void testGetConnection() throws SQLException { String config = "springjdbc.xml"; ApplicationContext ac = new ClassPathXmlApplicationContext(config); DataSource ds = ac.getBean("myDataSource", DataSource.class); System.out.println(ds.getConnection()); } @Test public void testSave() { Emp emp = new Emp(); emp.setName("Sally"); emp.setAge(22); dao.save(emp); } @Test public void testFindAll() { List<Emp> emps = dao.findAll(); System.out.println(emps); } @Test public void testFindById() { Emp emp = dao.findById(1); System.out.println(emp); } @Test public void testModify() { Emp emp = dao.findById2(1); emp.setAge(emp.getAge() * 2); dao.modify(emp); } @Test public void testDelete() { dao.delete(1); } } |
Spring与RESTful
RESTful简介
Spring对RESTful的支持
@RequestMapping应用
@PathVariable应用
客户端发送PUT、DELETE请求
静态资源访问处理
-
- 原理
Spring中的Bean是线程安全的吗?
多例Bean每次都会新创建新实例,也就是说线程之间不存在Bean共享的问题。因此,多例Bean是不存在线程安全问题的。
而单例Bean是所有线程共享一个实例,因此,就可能会存在线程安全问题。但是单例Bean又分为无状态Bean和有状态Bean。在多线程操作中只会对Bean的成员变量进行查询操作,不会修改成员变量的值,这样的Bean称之为无状态Bean。所以,可想而知,无状态的单例Bean是不存在线程安全问题的。但是,在多线程操作中如果需要对Bean中的成员变量进行数据更新操作,这样的Bean称之为有状态Bean,所以,有状态的单例Bean就可能存在线程安全问题。
处理有状态单例Bean的线程安全问题有以下三种方法:
1、在Bean对象中尽量避免定义可变的成员变量。
2、将Bean的作用域由单例singleton改为多例prototype。
3、在类中定义ThreadLocal的成员变量,并将需要的可变成员变量保存在ThreadLocal中,ThreadLocal本身就具备线程隔离的特性,这就相当于为每个线程提供了一个独立的变量副本,每个线程只需要操作自己的线程副本变量,从而解决线程安全问题。
Controller成员变量注入HttpServletRequest安全吗?
使用方法参数传递的HttpServletRequest,框架没有做封装,直接使用的是原生的HttpServletRequest 对象。
使用方法成员变量和@Autowired注解注入的HttpServletRequest,实际上SpringMVC做了一层代理,最终获取的是RequestContextHolder中的类型为ThreadLocal<RequestAttributes>中存放的RequestAttributes中的HttpServletRequest对象,因为使用了ThreadLocal,所以保证了每条线程HttpServletRequest对象的隔离,从而实现线程安全。