只是个人学习笔记,肯定有错漏,请童鞋谨慎参考~
Spring
理解Spring
第一章 IOC
理解IOC
IOC:Inversion Of Contro 控制反转,资源的获取方式
主动式:(要什么资源都自己创建即可)
BookServlet{
BookService bs = new BookService();//主动创建对象new
AirPlane ap = new AirPlane();
}
被动式:资源的获取不是我们自己创建,而是交给一个容器来创建和设置;
BookServlet{
BookService bs;//反射获取对象
public void test01(){
bs.checkout();//直接调用,无需创建对象
}
}
DI:Dependency Injection 依赖注入
- 容器能知道哪个组件(类)运行的时候,需要另外一个类(组件)
- 容器通过反射的形式,将容器中准备好的BookService对象注入(利用反射给属性赋值)到BookServlet中
IOC容器在Spring中的实现
IOC容器读取Bean的实例之前,需先将IOC容器本身实例化
- Spring提供了IOC容器的两种实现方式
- BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的
- ApplicationContext:BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的
ApplicationContext的主要实现类
-
ClassPathXMlApplicationContext(“ioc.xml”);ioc容器的配置文件在类路径下
-
FileSystemXmlApplicationContext(“F://ioc.xml”);ioc容器的配置文件在磁盘路径下
ConfigurableApplicationContext
- 是ApplicationContext的子接口,包含一些扩展方法
- refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力
WebApplicationContext
专为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作
小细节
- src,源码包开始的路径,称为类路径的开始,所有源码包里面的东西都会被合并放在类路径里面
java:/bin/
web:/WEB-INF/classes/ - 同一个组件在ioc容器中是单例的,容器启动时就创建好组件的对象
- ioc容器在创建组件对象的时候,标签利用setter方法为javaBean的属性进行赋值
- javaBean的属性名是由setter方法名去掉set后决定的
IOC使用步骤
导包
//核心容器
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
//Spring运行的时候依赖一个日志包,没有会报错
commons-logging-1.1.3.jar
写配置
-
spring的配置文件中,集合了spring的ioc容器管理的所有组件
-
创建一个Spring Bean Configuration File(Spring的bean配置文件)
-
一个标签可以注册一个组件(对象、类)
class:注册的组件的全类名
id:这个对象的唯一标示
-
使用标签为属性赋值
name=“lastName”:指定属性名
value=“张三”:为这个属性赋值
-
<!-- 注册一个Person对象,Spring会自动创建这个Person对象 --> <bean id="person01" class="com.atguigu.bean.Person"> <property name="lastName" value="张三"></property> <property name="age" value="18"></property> <property name="email" value="zhangsan@atguigu.com"></property> <property name="gender" value="男"></property> </bean>
-
-
测试
- ApplicationContext:代表ioc容器
- ClassPathXmlApplicationContext:当前应用的xml配置文件在 ClassPath下
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.atguigu.bean.Person;
public class IOCTest {
@Test
public void test() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");
Person bean = (Person) ioc.getBean("person01");
System.out.println(bean);
}
实验
实验1:通过ioc容器创建对象,并为属性赋值
<bean id="person01" class="com.atguigu.bean.Person">
<property name="lastName" value="张三"></property>
<property name="age" value="18"></property>
<property name="email" value="zhangsan@atguigu.com"></property>
<property name="gender" value="男"></property>
</bean>
public class IOCTest {
@Test
public void test() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");
Person bean = (Person) ioc.getBean("person01"); //强转为Person类型
}
实验2:根据bean类型从ioc容器中获取bean实例
同一类型有多个实例,通过ID获取指定实例对象
<bean id="person01" class="com.atguigu.bean.Person">
<property name="lastName" value="张三"></property>
</bean>
<bean id="person02" class="com.atguigu.bean.Person">
<property name="lastName" value="小花"></property>
</bean>
private ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");
public void test(){
//获取ID为“person02”,类型为Person的实例
Person bean = ioc.getBean("person02",Person.class);
}
实验3:通过构造器为bean的属性赋值
指定属性名(掌握)
<bean id="person03" class="com.atguigu.bean.Person">
<constructor-arg name="lastName" value="小花"></constructor-arg>
<constructor-arg name="email" value="12345678@qq.com"></constructor-arg>
<constructor-arg name="gender" value="女"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
</bean>
指定索引(了解)
<bean id="person03" class="com.atguigu.bean.Person">
<constructor-arg value="小花" index=1></constructor-arg>
<constructor-arg value="12345678@qq.com" index=2></constructor-arg>
<constructor-arg value="女" index=3></constructor-arg>
<constructor-arg value="18" index=4></constructor-arg>
</bean>
获取对象
private ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");
public void test(){
Person bean = ioc.getBean("person03"); //调用有参构造器赋值
}
实验4:通过<p>名称空间为bean赋值
名称空间作用:防止标签重复
引入<p>名称空间
xmlns:p="http://www.springframework.org/schema/p"
使用<p>名称空间
实验5:正确地为各种属性赋值
基本数据类型直接使用标签赋值
-
测试使用null值,默认引用类型是nul,基本类型是默值
获取引用数据类型对象
ref 引用外部bean
<property name = "xxx" ref = "xxx">
引用内部bean
<property name = "全类名">
<bean class = "全类名">
<property name = "属性名" value = "属性值"><\property>
<bean>
<property>
获取list类型对象
<property name = "属性名">
<list>
<bean class = "全类名">
<property name = "属性名" value = "属性值"><\property>
<bean>
<\list>
<property>
获取map类型对象
<property name="maps">
<!-- maps = new LinkedHashMap<>(); -->
<map>
<!-- 一个entry代表一个键值对 -->
<entry key="key01" value="张三"></entry>
<entry key="key02" value="18"></entry>
<entry key="key03" value-ref="book01"></entry>
<entry key="key04">
<bean class="com.atguigu.bean.Car">
<property name="carName" value="宝马"></property>
</bean>
</entry>
<entry key="key05">
<value>李四</value>
</entry>
<!-- <entry key="key05"> <map></map> </entry> -->
</map>
</property>
获取property对象
<property name="properties">
<!-- properties = new Properties();所有的k=v都是string -->
<props>
<!-- k=v都是string;值直接写在标签体中 -->
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
获取util名称空间创建集合类型的bean对象
引入<util>名称空间
<util>名称空间可以创建<util:map>、<util:list>、<util:set>、<util:properties>、<util:constant>、<util:properties-path>等引用对象
xmlns:util="http://www.springframework.org/schema/util"
创建<util:map>引用对象
<!-- 相当于new LinkedHashMap<>() -->
<util:map id="myMap">
<!-- 添加元素 -->
<entry key="key01" value="张三"></entry>
<entry key="key02" value="18"></entry>
<entry key="key03" value-ref="book01"></entry>
<entry key="key04">
<bean class="com.atguigu.bean.Car">
<property name="carName" value="宝马"></property>
</bean>
</entry>
<entry key="key05">
<value>李四</value>
</entry>
</util:map>
引用<util:map>对象
<bean id="person03" class="com.atguigu.bean.Person">
<property name="maps" ref="myMap"></property>
</bean>
测试
@Test
public void test05(){
Person person03 = (Person) ioc.getBean("person03");
Map<String, Object> maps = person03.getMaps();
System.out.println(maps);
Map<String, Object> bean = (Map<String, Object>) ioc.getBean("myMap");//可以全局获取引用的<util:map>对象
System.out.println(bean.getClass());//获取到的对象是java.util.LinkHashMap类型的
创建<util:list>引用对象
<util:list id="myList">
<list></list>
<bean class="com.atguigu.bean.Person"></bean>
<value>12</value>
<ref bean="myMap"/>
</util:list>
引用<util:list>
获取及联属性对象
及联属性:类里的属性是一个引用类
Person{
Car car;
}
级联属性可以修改属性的属性,注意:原来的bean的值可能会被修改
<!-- 级联属性赋值: 级联属性:属性的属性-->
<bean id="person04" class="com.atguigu.bean.Person">
<!--为car赋值的时候。改变car的价格 -->
<property name="car" ref="car01"></property>
<!-- -->
<property name="car.price" value="900000"></property>
</bean>
测试
@Test
public void test06(){
Person person04 = (Person) ioc.getBean("person04");
Object car = ioc.getBean("car01");
System.out.println("容器中的car:"+car); //price=900000
System.out.println("person中的car:"+person04.getCar()); //price=900000,原来的bean被修改了
}
继承属性
通过parent属性继承其他bean的属性,子bean重写就用子bean的属性值,不重写就用父bean的属性值
<bean id="person05" class="com.atguigu.bean.Person" >
<property name="lastName" value="张三"></property>
<property name="age" value="18"></property>
<property name="gender" value="男"></property>
<property name="email" value="zhangsan@atguigu.com"></property>
</bean>
<!--parent:指定当前bean的配置信息继承于哪个 -->
<bean id="person06" class="com.atguigu.bean.Person" parent="person05">
<property name="lastName" value="李四"></property>
</bean>
abstract模板属性
通过abstract属性创建一个bean模版,该bean只能被继承,不能被修改
<!-- abstract="true":这个bean的配置是一个抽象的,不能获取他的实例,只能被别人用来继承 -->
<bean id="person05" class="com.atguigu.bean.Person" abstract="true">
<property name="lastName" value="张三"></property>
<property name="age" value="18"></property>
<property name="gender" value="男"></property>
<property name="email" value="zhangsan@atguigu.com"></property>
</bean>
实验6:测试bean的作用域
bean的作用域默认是单实例的
prototype:多实例的;
1)、容器启动默认不会去创建多实例bean
2)、获取的时候创建这个bean
3)、每次获取都会创建一个新的对象
singleton:单实例的;默认的;
1)、在容器启动完成之前就已经创建好对象,保存在容器中了。
2)、任何获取都是获取之前创建好的那个对象;
request:在web环境下,同一次请求创建一个Bean实例(没用)
session:在web环境下,同一次会话创建一个Bean实例(没用)
-->
<bean id="book" class="com.atguigu.bean.Book" scope="prototype"></bean>
实验7:静态工厂实现bean
什么是静态工厂、实例工厂?
静态工厂:工厂本身不用创建对象static,通过静态方法调用
对象 = 工厂类.工厂方法名()
实例工厂:工厂本身需要创建对象,没有static
工厂类 工厂对象 = new 工厂类()
工厂对象.getAirPlane("张三")
//静态工厂
public class AirPlaneStaticFactory {
//AirPlaneStaticFactory.getAirPlane()
public static AirPlane getAirPlane(String jzName){
System.out.println("AirPlaneStaticFactory...正在为你造飞机");
AirPlane airPlane = new AirPlane();
airPlane.setFdj("太行");
airPlane.setFjsName("lfy");
airPlane.setJzName(jzName);
airPlane.setPersonNum(300);
airPlane.setYc("198.98m");
return airPlane;
}
}
配置静态工厂创建bean
factory-method:指定哪个方法是工厂方法
class:指定静态工厂全类名
factory-method:指定工厂方法
constructor-arg:可以为方法传参
<bean id="airPlane01" class="com.atguigu.factory.AirPlaneStaticFactory"
factory-method="getAirPlane">
<!-- 可以为方法指定参数 -->
<constructor-arg value="李四"></constructor-arg>
</bean>
实验8:实例工厂实现bean
//实例工厂
public class AirPlaneInstanceFactory {
// new AirPlaneInstanceFactory().getAirPlane();
public AirPlane getAirPlane(String jzName){
System.out.println("AirPlaneInstanceFactory...正在造飞机");
AirPlane airPlane = new AirPlane();
airPlane.setFdj("太行");
airPlane.setFjsName("lfy");
airPlane.setJzName(jzName);
airPlane.setPersonNum(300);
airPlane.setYc("198.98m");
return airPlane;
}
}
配置实例工厂创建bean
factory-method:指定哪个方法是工厂方法
factory-bean:指定使用哪个工厂创建对象
<bean id="airPlane02" class="com.atguigu.bean.AirPlane"
factory-bean="airPlaneInstanceFactory"
factory-method="getAirPlane">
<constructor-arg value="王五"></constructor-arg>
</bean>
实验9:实现FactoryBean的工厂
创建
//创建实现FactoryBean接口的book()类型工厂
public class MyFactoryBeanImple implements FactoryBean<Book>{
/**
* getObject:工厂方法;
* 返回创建的对象
*/
@Override
public Book getObject() throws Exception {
System.out.println("MyFactoryBeanImple。。帮你创建对象...");
Book book = new Book();
book.setBookName(UUID.randomUUID().toString());
return book;
}
/**
* 返回创建的对象的类型;
* Spring会自动调用这个方法来确认创建的对象是什么类型
*/
@Override
public Class<?> getObjectType() {
// TODO Auto-generated method stub
return Book.class;
}
/**
* isSingleton:是单例?
* false:不是单例
* true:是单例
*/
@Override
public boolean isSingleton() {
// TODO Auto-generated method stub
return true;
}
}
配置
配置FactoryBeanImple创建bean
<bean id="myFactoryBeanImple"
class="com.atguigu.factory.MyFactoryBeanImple"></bean>
</beans>
测试
@Test
public void test09(){
//Object bean = ioc.getBean("airPlane01");
//System.out.println(bean); //输出book()对象
Object bean = ioc.getBean("airPlane02");
System.out.println("容器启动完成...."+bean);
实验9:创建带有生命周期的bean
Bean的生命周期
单例:(容器启动)构造器---->初始化方法---->(容器关闭)销毁方法
多实例:获取bean(构造器--->初始化方法)--->容器关闭不会调用bean的销毁方法
后置处理器:(容器启动)构造器------后置处理器before..-----初始化方法-----后置处理器after...-----bean初始化完成
无论bean是否有初始化方法;后置处理器都会默认其有,还会继续工作;
配置bean
生命周期:bean的创建到销毁;
ioc容器中注册的bean;
1)、单例bean,容器启动的时候就创建好,容器关闭也销毁创建的bean
2)、多实例bean,获取的时候才创建;
可以为bean自定义一些生命周期方法,spring在创建或者销毁的时候就会调用指定的方法;
自定义初始化方法和销毁方法: The method must have no arguments,but may throw any exception
<bean id="book01" class="com.atguigu.bean.Book"
destroy-method="myDestory" init-method="myInit" >
</bean>
bean实例
public class Book {
private String bookName;
private String author;
public void myInit(){
System.out.println("这是图书的初始化方法...");
}
public void myDestory(){
System.out.println("这是图书的销毁方法...");
}
public Book() {
super();
// TODO Auto-generated constructor stub
System.out.println("book被创建");
}
}
ConfigurableApplicationContext中有close()方法
测试单例bean生命周期
public class IOCTest {
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext03.xml");
@Test
public void test() {
//单例,直接运行test方法即可创建bean销毁需要自己销毁
ioc.close();
}
}
自己销毁bean
public class IOCTest {
ConfigurableApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext03.xml");
@Test
public void test01() {
ioc.close();
}
}
测试多实例bean生命周期
public class IOCTest {
ConfigurableApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext03.xml");
@Test
public void test01() {
Object bean = ioc.getBean("book01");
}
}
实验10:测试bean的后置处理器
BeanPostProcessor后置处理器是Spring的接口,可以在bean的初始化前后调用方法
postProcessBeforeInitialization:初始化之前调用
postProcessAfterInitialization:初始化方法之后调用,初始化之后返回的bean;返回的是什么,容器中保存的就是什么
实现BeanPostProcessor接口
public class MyBeanPostProcessor implements BeanPostProcessor{
/**
* postProcessBeforeInitialization:初始化之前调用
* Object bean:将要初始化的bean
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
System.out.println("postProcessBeforeInitialization...【"+beanName+"】bean将要调用初始化方法了....这个bean是这样:【"+bean+"】");
//返回传入的bean
return bean;
}
/**
* postProcessAfterInitialization:初始化方法之后调用
* Object bean,
* String beanName:bean在xml中配置的id
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
System.out.println("postProcessAfterInitialization...【"+beanName+"】bean初始化方法调用完了...AfterInitialization..");
//初始化之后返回的bean;返回的是什么,容器中保存的就是什么
return bean;
}
}
配置bean
<bean id="book01" class="com.atguigu.bean.Book"
destroy-method="myDestory" init-method="myInit" ></bean>
<bean id="beanPostProcessor" class="com.atguigu.bean.MyBeanPostProcessor"></bean>
测试
@Test
public void test() {
Object bean = ioc.getBean("book01");
System.out.println(""+bean); //打印的是postProcessAfterInitialization返回的bean
}
实验11:Spring管理数据库连接池引用外部配置文件
引用名称空间context
xmlns:context="http://www.springframework.org/schema/context"
<!--加载外部配置文件 固定写法classpath:,表示引用类路径下的一个资源-->
<context:property-placeholder location="classpath:dbconfig.properties"/>
<!-- username是Spring的key中的一个关键字;
为了防止配置文件中的key和spring自己的关键字冲突。我们可以给配置文件中的key加上一个前缀 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.username}"></property>
<!-- ${key}动态取出配置文件中某个key对应的值 -->
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
</bean>
外部文件
jdbc.username=root
jdbc.password=123456
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/test
jdbc.driverClass=com.mysql.jdbc.Driver
测试连接池
@Test
public void test02() throws SQLException{
//1、从容器中拿到连接池
//DataSource bean = (DataSource) ioc.getBean("dataSource");
//2、按照类型获取组件,可以获取到这个类型下的所有实现类子类等等...
DataSource bean = ioc.getBean(DataSource.class);
System.out.println(bean.getConnection());
}
实验12:基于xml的自动装配
为Person里面的自定义类型Car的属性赋值
<property>:手动赋值
autowire:自动赋值(自动装配)
自动装配:只能自定义类型的属性赋值,不能给基本数据类型赋值
autowire="default/no":不自动装配
autowire="byName":按照名字自动赋值
private Car car====>car = ioc.getBean("car")
以属性名(car)作为id去容器中找到这个组件,给他赋值;如果找不到就装配null;
autowire="byType"
private Car car=====>car = ioc.getBean(Car.class)
1)、以属性的类型作为查找依据去容器中找到这个组件;如果容器中有多个这葛类型的组件,报错;
NoUniqueBeanDefinitionException:
No qualifying bean of type [com.atguigu.bean.Car] is defined:
expected single matching bean but found 2: car01,car02
2)、没找到呢?装配null
autowire="constructor" 按照构造器进行赋值
public Person(Car car)
1)、先按照有参构造器参数的类型进行装配(成功就赋值);没有就直接为组件装配null
2)、如果按照类型找到了多个;参数的名作为id继续匹配;找到就装配;找不到就null;
3)、不会报错;
实验13: spEL
<bean id="person04" class="com.atguigu.bean.Person">
<!-- 字面量:${}; #{} -->
<property name="salary" value="#{12345.67*12}"></property>
<!-- 引用其他bean的某个属性值、 -->
<property name="lastName" value="#{book01.bookName}"></property>
<!-- 引用其他bean、 -->
<property name="car" value="#{car}"></property>
<!--
调用静态方法: UUID.randomUUID().toString();
#{T(全类名).静态方法名(1,2)}
-->
<property name="email" value="#{T(java.util.UUID).randomUUID().toString().substring(0,5)}"></property>
<!-- 调用非静态方法; 对象.方法名 -->
<property name="gender" value="#{book01.getBookName()}"></property>
</bean>
实验14: 注解创建DAO、Service、Controller
①普通组件:@Component
标识一个受Spring IOC容器管理的组件
②持久化层组件:@Respository
标识一个受Spring IOC容器管理的持久化层组件
③业务逻辑层组件:@Service
标识一个受Spring IOC容器管理的业务逻辑层组件
④表述层控制器组件:@Controller
标识一个受Spring IOC容器管理的表述层控制器组件
使用步骤
1、给组件添加注解
2、开启Spring注解扫描,依赖context名称空间
<context:component-scan base-package="com.atguigu"></context:component>
3、导入aop包,支持注解模式的包
4、获取组件,id默认就是组件的首字母小写,组件的作用域默认是单例
Object bean = ioc.getBean("bookDao")
修改id名称,@Service("bookDaohaha")
Object bean = ioc.getBean("bookDaohaha")
修改组件作用域
@Scope(Value="property")
public class BookDao{}
实验15:组件扫描过滤
<context:excude-filter>指定扫描时不包含的类
type:annotation==>排除指定注解的组件,expression="注解的全类名"
type:assignable==>排除指定类的注解,expression="类的全类名"
<context:component-scan base-package="com.atguigu">
<context:excude-filter typer="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component>
<context:include-filter>指定扫描时包含的类
只扫描指定的组件,默认扫描全部组件,所以要禁用默认过滤规则use-default- filter="false"
type、expression同理
实验16:使用注解@Autowired实现根据类型自动装配
1、开启注解扫描
<context:component-scan base-package="com.atguigu"></context:component>
-----------------------------------------------------------------------
2、加注解@Autowired
@Service
public class BookService{
@Autowired
private BookDao bookDao;
public void save(){
bookDao.saveBook();
}
}
-----------------------------------------------------------------------
3、加注解@Autowired
@Controller
public class BookServlet{
@Autowired
private BookService bookService;
public void doGet(){
bookService.save();
}
}
------------------------------------------------------------------------
4、测试
@Test
public void test(){
BookService bookService = ioc.getBean(BookServlet.class);
bookServlet.doget();
}
实验16:@Autowired原理
@Autowired
private BookService bookService;
先按照类型在容器中找对应的组件:bookService = ioc.getBean(BookService)
找到一个,就赋值
没找到,抛异常
找到多个,按照id继续匹配BookService、BookServiceExt
匹配上,装配
匹配不上,报错
@Qualifier("名"):指定一个名作为id,让Spring不使用变量名作为id,使用指定字符串作为新id
找到,装配
找不到,报错
@Autowired默认是一定能装配上的
设置@Autowired(required=false),找到就装配,找不到就放弃
方法上有@Autowired
这个方法也会在bean创建时自动运行
这个方法上的每个参数都会自动装配
@Autowired
public void hahaha(BookDao bookDao,
@Qualifier("bookServiceExt")BookService bookService ){
//自动赋值bookDao、bookService
System.out.println("BookDao===>"+bookDao,"BookService===>"+bookService)
}
实验18: 泛型依赖注入
父类的类型:com.atguigu.service.BaseService
带泛型的父类类型:com.atguigu.service.BaseService<com.atguigu.bean.Book>
Spring支持使用带泛型的父类类型来确定子类的类型
bean
public class Book {
}
----------------------------------------------------------
public class User {
}
dao
public abstract class BaseDao<T> {
//定义了基本的增删改查方法
public abstract void save();
}
----------------------------------------------------------
Repository
public class BookDao extends BaseDao<Book>{
@Override
public void save() {
System.out.println("BookDao....保存图书。。。");
}
}
----------------------------------------------------------
@Repository
public class UserDao extends BaseDao<User>{
@Override
public void save() {
System.out.println("UserDao...保存用户....");
}
}
service
public class BaseService<T> {
@Autowired
private BaseDao<T> baseDao;
public void save(){
System.out.println("自动注入的dao:"+baseDao);
baseDao.save();
}
}
-----------------------------------------------------------
Service
public class BookService extends BaseService<Book>{
}
-----------------------------------------------------------
@Service
public class UserService extends BaseService<User>{
}
测试
public class IOCTest {
@Test
public void test() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = ioc.getBean(BookService.class);
UserService userService = ioc.getBean(UserService.class);
bookService.save();
userService.save();
}
}
Spring的单元测试
1、导包 spring-test
2、@ContextConfiguration(location="")指定bean的配置文件
3、@RunWith(SpringJUnit4ClassRunner.Class)指定使用Spring的单元测试模块来执行@Test注解的测试方法
4、好处:不用ioc.getBean()获取组件,直接@Autowired,Spring自动装配
第二章 AOP
理解AOP
AOP:(Aspect Oriented Programming)
面向切面编程 : 指在程序运行期间,将某段代码动态切入到指定位置
AOP专业术语
横切关注点
从每个方法中抽取出来的同一类非核心业务。
切面(Aspect)
封装横切关注点信息的类,每个关注点体现为一个通知方法。
通知(Advice)
切面必须要完成的各个具体工作
目标(Target)
被通知的对象
代理(Proxy)
向目标对象应用通知之后创建的代理对象
连接点(Joinpoint)
横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置
例如:类某个方法调用前、调用后、方法捕获到异常后等
在应用程序中可以使用横纵两个坐标来定位一个具体的连接点
切入点(pointcut)
定位连接点的方式。每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物。
如果把连接点看作数据库中的记录,那么切入点就是查询条件——AOP可以通过切入点定位到特定的连接点。
切点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
动态代理的理解
代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上
- 创建动态代理对象
public class CalculatorProxy {
/**
* 为传入的参数对象创建一个动态代理对象
* Calculator calculator:被代理对象;(宝宝)
* 返回的:宋喆
*/
public static Calculator getProxy(final Calculator calculator) {
//方法执行器。帮我们目标对象执行目标方法
InvocationHandler h = new InvocationHandler() {
/**
* Object proxy:代理对象;给jdk使用,任何时候都不要动这个对象
* Method method:当前将要执行的目标对象的方法
* Object[] args:这个方法调用时外界传入的参数值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 利用反射执行目标方法
//目标方法执行后的返回值
//System.out.println("这是动态代理将要帮你执行方法...");
Object result = null;
try {
LogUtils.logStart(method, args);
result = method.invoke(calculator, args);
LogUtils.logReturn(method, result);
} catch (Exception e) {
LogUtils.logException(method,e);
}finally{
LogUtils.logEnd(method);
}
//返回值必须返回出去外界才能拿到真正执行后的返回值
return result;
}
};
Class<?>[] interfaces = calculator.getClass().getInterfaces();
ClassLoader loader = calculator.getClass().getClassLoader();
//Proxy为目标对象创建代理对象;
Object proxy = Proxy.newProxyInstance(loader, interfaces, h);
return (Calculator) proxy;
}
}
- 测试动态代理
public class AOPTest {
@Test
public void test() {
//原生对象方法执行
Calculator calculator = new MyMathCalculator();
calculator.add(1, 2);
calculator.div(2, 1);
//代理对象方法执行
Calculator proxy = CalculatorProxy.getProxy(calculator);
proxy.add(2, 1);
proxy.div(2, 0);
//代理对象和被代理对象唯一能产生的关联就是实现了同一个接口====>com.sun.proxy.$Proxy2也是实现了Calculator接口
System.out.println(Arrays.asList(proxy.getClass().getInterfaces()));
}
}
AOP使用步骤(注解)
导包
基础版:
commons-logging-1.1.3.jar
spring-aop-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
增强版:
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
写配置
- 把目标类和切面类加入ioc容器中
* 目标类
@Service
public class MyMathCalculator /*implements Calculator*/ {}
* 切面类
@Component
public class LogUtils {}
* 设置切面类,告诉Spring哪个是切面类@Aspect
@Aspect
@Component
public class LogUtils {}
- 开启组件扫描
xmlns:context="http://www.springframework.org/schema/context
<context:component-scan base-package="com.atguigu"></context:component-scan>
- 开启基于注解的AOP模式
开启基于注解的AOP功能;aop名称空间
xmlns:aop="http://www.springframework.org/schema/aop
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!-- 开启基于注解的AOP功能;aop名称空间-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 告诉Spring每个方法什么时候执行,写切入点表达式
@AfterReturning(value="execution(public int com.atguigu..MyMath*.*(..))",returning="result")
public static void logReturn(JoinPoint joinPoint,Object result){
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("【"+name+"】方法正常执行完成,计算结果是:"+result);
}
* try{
* @Before
* method.invoke(obj,args);
* @AfterReturning
* }catch(e){
* @AfterThrowing
* }finally{
* @After
* }
*
* 5个通知注解
* @Before:在目标方法之前运行; 前置通知
* @After:在目标方法结束之后 后置通知
* @AfterReturning:在目标方法正常返回之后 返回通知
* @AfterThrowing:在目标方法抛出异常之后运行 异常通知
* @Around:环绕 环绕通知
* 切入点表达式的写法;
* 固定格式: execution(访问权限符 返回值类型 方法全类名(参数表))
*
* 通配符:
* *:
* 1)匹配一个或者多个字符:execution(public int com.atguigu.impl.MyMath*r.*(int, int))
* 2)匹配任意一个参数:第一个是int类型,第二个参数任意类型;(匹配两个参数)
* execution(public int com.atguigu.impl.MyMath*.*(int, *))
* 3)只能匹配一层路径
* 4)权限位置*不能;权限位置不写就行;public【可选的】
* ..:
* 1)匹配任意多个参数,任意类型参数
* 2)匹配任意多层路径:
* execution(public int com.atguigu..MyMath*.*(..));
*
* 记住两种;
* 最精确的:execution(public int com.atguigu.impl.MyMathCalculator.add(int,int))
* 最模糊的:execution(* *.*(..)):千万别写;
*
* &&”、“||”、“!
*
* &&:要切入的位置同时满足这两个表达式
* MyMathCalculator.add(int,double)
* execution(public int com.atguigu..MyMath*.*(..))&&execution(* *.*(int,int))
*
*
* ||:满足任意一个表达式即可
* execution(public int com.atguigu..MyMath*.*(..))&&execution(* *.*(int,int))
*
* !:只要不是这个位置都切入
* !execution(public int com.atguigu..MyMath*.*(..))
测试
public class AOPTest {
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
@Test
public void test() {
Calculator bean = ioc.getBean(Calculator.class);
bean.add(2, 1);
AOP细节
1、用类型获取ioc容器目标对象
- 代理对象和目标对象的关联是实现了同样的接口
- 目标对象MyMathCalculator
public class MyMathCalculator implements Calculator
- 代理对象切面类中动态代理对象和目标类实现了同样的接口
invoke
- 目标对象MyMathCalculator
- 想要用类型从ioc容器中拿到目标对象,一定用他的接口类型,不要用它本类
Calculator bean = ioc.getBean(Calculator.class);
2、cglb创建代理对象
- jdk创建代理对象必须有接口
public class MyMathCalculator implements Calculator*{}
- 没有接口
public class MyMathCalculator
- Spring可以用cglb创建代理对象(用原生类型获取ioc对象)
MyMathCalculator bean = (MyMathCalculator) ioc.getBean("myMathCalculator");
bean.add(1, 2);
System.out.println(bean.getClass());
3、通知方法的执行顺序
- 正常执行: @Before(前置通知)==== @After(后置通知)==== @AfterReturning(正常返回)
- 异常执行: @Before(前置通知)==== @After(后置通知)==== @AfterThrowing(方法异常)
4、JionPoint获取目标方法的详细信息
- JoinPoint joinPoint:封装了当前目标方法的详细信息
//获取到目标方法运行是使用的参数
Object[] args = joinPoint.getArgs();
//获取到方法签名
Signature signature = joinPoint.getSignature();
5、throwing , returning指定参数
//想在目标方法出现异常的时候执行
@AfterThrowing(value="execution(public int com.atguigu.impl.MyMathCalculator.*(..))",throwing="exception")
public static void logException(JoinPoint joinPoint,Exception exception) {
System.out.println("【"+joinPoint.getSignature().getName()+"】方法执行出现异常了,异常信息是【"+exception+"】:;这个异常已经通知测试小组进行排查");
}
//想在目标方法正常执行完成之后执行
@AfterReturning(value="execution(public int com.atguigu..MyMath*.*(..))",returning="result")
public static void logReturn(JoinPoint joinPoint,Object result){
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("【"+name+"】方法正常执行完成,计算结果是:"+result);
}
6、抽取可重用的切入点表达式
- 随便声明一个没有实现的返回void的空方法
@Pointcut("execution(public int com.atguigu.impl.MyMathCalculator.*(..))")
public void hahaMyPoint(){};
- 给方法上标注@Pointcut注解
@Before("hahaMyPoint()")
public static void logStart(JoinPoint joinPoint){}
7、环绕通知
- 依赖参数ProceedingJoinPoint pjp ,相当于method.invoke()
@Around("hahaMyPoint()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjp.getArgs();
String name = pjp.getSignature().getName();
//args[0] = 100;
Object proceed = null;
try {
//@Before
System.out.println("【环绕前置通知】【"+name+"方法开始】");
//就是利用反射调用目标方法即可,就是method.invoke(obj,args)
proceed = pjp.proceed(args);
//@AfterReturing
System.out.println("【环绕返回通知】【"+name+"方法返回,返回值"+proceed+"】");
} catch (Exception e) {
//@AfterThrowing
System.out.println("【环绕异常通知】【"+name+"】方法出现异常,异常信息:"+e);
//为了让外界能知道这个异常,这个异常一定抛出去
throw new RuntimeException(e);
} finally{
//@After
System.out.println("【环绕后置通知】【"+name+"】方法结束");
}
//反射调用后的返回值也一定返回出去
return proceed;
}
}
Spring-IOC-AOP(动态代理):多层代理
LogAspectpRroxy{
try{
@Before
method.invoke()//pjp.procced(args){
BAspectProxy{
@Before
method.invoke()//---目标方法
@AfterReturning
//xxxxxxxx
//修改了返回值
}
}
@AfterReturning
}catch(e){
@AfterThrowing
}finally{
@After
}
}
AOP基于XML配置
- 将目标类和切面类加入Spring
- 告诉Spring哪个时切面类
- 指定通知方法
<!-- 基于配置的AOP-->
<bean id="myMathCalculator" class="com.atguigu.impl.MyMathCalculator"></bean>
<bean id="BValidateApsect" class="com.atguigu.utils.BValidateApsect"></bean>
<bean id="logUtils" class="com.atguigu.utils.LogUtils"></bean>
<!-- 需要AOP名称空间 -->
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.impl.*.*(..))" id="globalPoint"/>
<!-- 普通前置 ===== 目标方法 =====(环绕执行后置/返回)====s普通后置====普通返回 -->
<!-- 指定切面:@Aspect -->
<aop:aspect ref="logUtils" order="1">
<!-- 配置哪个方法是前置通知;method指定方法名
logStart@Before("切入点表达式")
-->
<!-- 当前切面能用的 -->
<aop:around method="myAround" pointcut-ref="mypoint"/>
<aop:pointcut expression="execution(* com.atguigu.impl.*.*(..))" id="mypoint"/>
<aop:before method="logStart" pointcut="execution(* com.atguigu.impl.*.*(..))"/>
<aop:after-returning method="logReturn" pointcut-ref="mypoint" returning="result"/>
<aop:after-throwing method="logException" pointcut-ref="mypoint" throwing="exception"/>
<aop:after method="logEnd" pointcut-ref="mypoint"/>
</aop:aspect>
<aop:aspect ref="BValidateApsect" order="3">
<aop:before method="logStart" pointcut-ref="globalPoint"/>
<aop:after-returning method="logReturn" pointcut-ref="globalPoint" returning="result"/>
<aop:after-throwing method="logException" pointcut-ref="globalPoint" throwing="exception"/>
<aop:after method="logEnd" pointcut-ref="globalPoint"/>
</aop:aspect>
<!-- 在切面类中使用五个通知注解来配置切面中的这些通知方法都何时何地运行 -->
</aop:config>
第三章 声明式事务
环境搭建
- 配置数据源
- 配置jdbcTemplate
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!--引入外部配置文件 -->
<context:property-placeholder location="classpath:dbconfig.properties"/>
<!--配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
</bean>
<!-- Spring提供了一个类JdbcTemplate,我们用它操作数据库;
导入Spring的数据库模块
spring-jdbc-4.0.0.RELEASE.jar
spring-orm-4.0.0.RELEASE.jar
spring-tx-4.0.0.RELEASE.jar
-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
开启事务控制
- XML配置
<!-- 事务控制 -->
<!--1:配置事务管理器(切面)让其进行事务控制;一定导入面向切面编程的几个包
spring-aspects-4.0.0.RELEASE.jar
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
-->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 控制住数据源 -->
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
<!--2:开启基于注解的事务控制模式;依赖tx名称空间 -->
<tx:annotation-driven transaction-manager="tm"/>
<!--3:给事务方法加注解@Transactional -->
- 给事务方法加注解@Transactional
public class BookService {
@Autowired
BookDao bookDao;
/**
* 结账;传入哪个用户买了哪本书
* @param username
* @param isbn
*/
@Transactional
public void checkout(String username,String isbn){
//1、减库存
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
//2、减余额
bookDao.updateBalance(username, price);
}
}
- 测试
public class TxTest {
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
@Test
public void test() {
BookService bookService = ioc.getBean(BookService.class);
bookService.checkout("Tom", "ISBN-001");
System.out.println("结账完成");
}
}
事务细节
1、@Transactional参数
1、timeout-int(秒为单位):超时:事务超出指定执行时长后自动终止并回滚
2、readOnly-boolean:设置事务为只读事务,默认是false
readOnly=true:加快查询速度,不用管事务那一堆操作了
3、异常分类
运行时异常,回滚
编译时异常,不回滚
noRollbackFor:哪些异常事务可以不回滚;(可以让原来默认回滚的异常给他不回滚)
noRollbackFor={ArithmeticException.class,NullPointerException.class}
noRollbackForClassName
rollbackFor:原本不回滚(原本编译时异常是不回滚的)的异常指定让其回滚;
4、隔离级别:isolation-Isolation
①读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改
②读已提交:READ COMMITTED
Transaction01只能读取Transaction02已提交的修改
③可重复读:REPEATABLE READ
Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新
④串行化:SERIALIZABLE
Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作,可以避免任何并发问题,但性能十分低下
5、事务的传播行为
2、数据库事务并发问题
假设有两个事务:Transaction01和Transaction02并发执行。
①脏读
[1]Transaction01将某条记录的AGE值从20修改为30。
[2]Transaction02读取了Transaction01更新后的值:30。
[3]Transaction01回滚,AGE值恢复到了20。
[4]Transaction02读取到的30就是一个无效的值。
②不可重复读
[1]Transaction01读取了AGE值为20。
[2]Transaction02将AGE值修改为30。
[3]Transaction01再次读取AGE值为30,和第一次读取不一致。
③幻读
[1]Transaction01读取了STUDENT表中的一部分数据。
[2]Transaction02向STUDENT表中插入了新的行。
[3]Transaction01读取了STUDENT表时,多出了一些行。
3、传播行为
Spring定义了7种传播行为:
1、REQUIRED:如果当前有事务正在运行,就加入这个事务,否则自己启动一个新事务
2、REQUIRED_NEW:自己启动一个新事务,如果有事务正在运行,将之前的事务挂起
3、SUPPORTS:如果当前有事务正在运行,就加入该事务,如果没有事务正在运行,可以不运行在事务中
4、NOT_SUPPORTS:不运行在事务中,如果当前有正在运行的事务,将事务挂起
5、MANDATORY:必须运行在事务中,如果当前没有正在运行的事务,就抛异常
6、NEVER:不运行在事务中,如果当前有正在运行的事务,就抛异常
7、NESTED:如果当前有事务正在运行,加入这个事务的嵌套事务中运行,否则启动一个新事务,运行在自己的事务中
设置传播行为
//事务一
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void checkout(String username,String isbn){
//1、减库存
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
//2、减余额
bookDao.updateBalance(username, price);
}
//事务二
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void updatePrice(String isbn,int price){
bookDao.updatePrice(isbn, price);
}
测试
@Service
public class MulService {
@Autowired
private BookService bookService;
@Transactional
public void mulTx(){
bookService.checkout("Tom", "ISBN-001"); //事务一
bookService.updatePrice("ISBN-002", 998);//事务二
}
}
事务控制 XML版
配置前准备
<!-- 0、引入外部配置文件 -->
<context:property-placeholder location="classpath:dbconfig.properties" />
<!-- 1、配置数据源 -->
<bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
</bean>
<!-- 2、配置JdbcTemplate操作数据库 value="#{pooledDataSource}" ref="pooledDataSource"-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" value="#{pooledDataSource}"></property>
</bean>
<!-- 3、配置声明式事务
1)、Spring中提供事务管理器(事务切面),配置这个事务管理器
2)、开启基于注解的事务式事务;依赖tx名称空间
3)、给事务方法加注解
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
正式配置XML
基于xml配置的事务:依赖tx名称空间和aop名称空间
1)、Spring中提供事务管理器(事务切面),配置这个事务管理器
2)、配置出事务方法;
3)、告诉Spring哪些方法是事务方法;
(事务切面按照我们的切入点表达式去切入事务方法)
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.ser*.*.*(..))" id="txPoint"/>
<!-- 事务建议;事务增强 advice-ref:指向事务管理器的配置 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/>
</aop:config>
<!-- 配置事务管理器; 事务建议;事务增强;事务属性;
transaction-manager="transactionManager":指定是配置哪个事务管理器;
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--事务属性 -->
<tx:attributes>
<!-- 指明哪些方法是事务方法;
切入点表达式只是说,事务管理器要切入这些方法,
哪些方法加事务使用tx:method指定的 -->
<tx:method name="*"/>
<tx:method name="checkout" propagation="REQUIRED" timeout="-1"/>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
测试
@Service
public class BookService {
@Autowired
BookDao bookDao;
public void checkout(String username,String isbn){
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
bookDao.updateBalance(username, price);
int i = 10/0;
}
}
public class TxTest {
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");
@Test
public void test() throws FileNotFoundException {
BookService bookService = ioc.getBean(BookService.class);
bookService.checkout("Tom", "ISBN-001");
}
}