文章目录
在学习spring之前,我们需要先了解Spring的系统架构,看下图,本文所记录的学习笔记主要是容器(Core Container)、面向切面(AOP、Aspects)、事务(Transactions)、数据传输(Data Access)这几个部分,每一个部分都有他们独特的一些功能作用,下面看详细介绍
一、bean与IOC容器
在我们之前书写代码时,当我们需要使用一个类对象的时候,我们需要new出来一个对象,这样的做法使我们的代码耦合性提高了,为解决这一问题,Spring中实现了IOC(Inversion Of Control)控制反转的思想,当我们在使用对象时,由原本的new一个对象转变成从外部获取一个对象,而在Spring中,我们需要获取的这个对象就叫做bean对象,并且Spring中还提供了一个容器(也叫Spring容器和IOC容器)来充当这个外部,用于存储这些bean对象,当我们需要使用时,直接从这个bean对象中获取,降低代码的耦合性。
1、bean对象
什么是bean对象?在我们开发程序的时候,需要用到的所有对象都称之为bean对象,在Spring中我们需要将所有的bean对象都存入IOC容器来进行管理,那么我们是如何对此进行操作的呢?下面看代码演示。
-
导入依赖
由于Spring中的bean对象的存储与注入都是使用配置文件形式,所以我们需要先导入Spring依赖,否则找不到Spring的配置文件
<!--spring--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency>
-
创建xml配置文件
在resources目录下创建一个xml配置文件,这个配置文件就相当于一个IOC容器。
-
将bean对象放入IOC容器当中
bean标签代表IOC容器中的一个bean对象,class属性绑定的是加入容器的中类,id属性设置的是这个bean在容器当中的名字,scope属性设置这个bean对象的作用范围,它有两个选项,singleton和prototype,分别表示单例和多例的,单例的意思就是当创建对象的时候只会创建一个对象,而多例则是创建多个对象,默认是singleton。这步操作相当于Spring boot中的@Comment注解
<!--配置bean--> <!--bean标签表示配置bean--> <!--class表示加入容器的类--> <!--id表示给bean起的名字--> <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
-
配置Service与Dao的关系
当我们需要在Service中使用Dao中的bean对象,那么我们则需要使用property标签进行设置,property中有两个属性,name属性表示具体哪个类的类名,ref表示这个类在容器中的bean名称
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--配置bean--> <!--bean标签表示配置bean--> <!--class表示加入容器的类--> <!--id表示给bean起的名字--> <bean id="bookDao1" class="com.itheima.dao.impl.BookDaoImpl"/> <!-- 配置service于dao的关系 property表示当前bean的属性 name属性表示配置哪一个具体的属性 ref表示参照哪一个bean--> <bean class="com.itheima.service.impl.BookServiceImpl"> <property name="bookDao" ref="bookDao1"/> </bean> </beans>
我们可以在main入口中获取IOC容器,然后通过容器获取bean对象,再用bean对象来具体调用其方法
package com.itheima; import com.itheima.dao.BookDao; import com.itheima.service.BookService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App2 { public static void main(String[] args) { //获取ioc容器 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //从容器中获取bean BookDao bookdao = (BookDao)applicationContext.getBean("bookDao"); bookdao.save(); } }
bean对象也不是随意的添加到容器当中,而是要根据具体的需要添加。
-
适合交给容器管理的bean
- 表现层的对象
- 业务层的对象
- 数据层的对象
- 工具类对象
-
不适合交给容器管理的bean对象
- 封装实体类的对象
2、bean对象的创建方式
bean对象的创建方式在底层都是通过new的形式创建,在new的基础上Spring还提出了工厂创建bean对象的方式。
-
构造方法实例化对象
最简单的一种方法就是通过构造方法实例化一个对象,上面所展示的使用bean标签直接设置name属性和id属性的方式便是通过这个类的构造方法来实例化对象。
<!--方式一:构造方法实例化bean--> <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
-
静态工厂实例化bean对象
静态工厂实例化对象时需要分两步,第一步先创建一个工厂类,然后在这个类里面写一个静态方法,在这个静态方法中返回一个类对象,这个类对象便是我们需要通过静态工厂实例化的对象。第二步在配置文件当中使用静态工厂实例化bean对象,如下方代码所示:class是表示工厂类,factory-method表示的是创建这个实例化对象的静态方法。
package com.itheima.factory; import com.itheima.dao.OrderDao; import com.itheima.dao.impl.OrderDaoImpl; //静态工厂创建对象 public class OrderDaoFactory { public static OrderDao getOrderDao(){ System.out.println("factory setup...."); return new OrderDaoImpl(); } }
<!--方式二:使用静态工厂实例化bean--> <bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>
-
实例工厂创建bean对象
实力工厂创建bean对象跟静态工厂一样,也需要一个工厂类,只不过工厂类中的创建bean对象的方法不再是静态方法,在配置文件当中,我们需要先将这个实例工厂的bean对象加入到容器当中,在从容器当中将这个bean对象拿出来创建我们需要创建的对象。通过factory-bean属性指定使用我们加入到容器中的工厂,factory-method指定工厂对象中使用的方法。
package com.itheima.factory; import com.itheima.dao.UserDao; import com.itheima.dao.impl.UserDaoImpl; //实例工厂创建对象 public class UserDaoFactory { public UserDao getUserDao(){ return new UserDaoImpl(); } }
<!--方式三:使用实例工厂实例化bean--> <bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/> <bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
实例工厂创建bean对象除了此方法之外还有另外一直更为简便的方法,我们可以在工厂类中实现一个FactoryBean
接口,这个接口有三个方法,getObject()用于创建对象,getObjectType()用于指定对象的类型,还有一个isSingleton()用于指定单例和多例。创建完成之后。直接在配置文件中将工厂类加入到容器中即可。
package com.itheima.factory; import com.itheima.dao.UserDao; import com.itheima.dao.impl.UserDaoImpl; import org.springframework.beans.factory.FactoryBean; //FactoryBean创建对象 public class UserDaoFactoryBean implements FactoryBean<UserDao> { //代替原始实例工厂中创建对象的方法 public UserDao getObject() throws Exception { return new UserDaoImpl(); } //指定创建bean的类型 public Class<?> getObjectType() { return UserDao.class; } }
<!--方式四:使用FactoryBean实例化bean--> <bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>
3、bean的生命周期
bean的生命周期控制,bean在加入到容器时,可以设置初始化时执行的方法和销毁时执行的方法。有两种设置方法,一是配置文件,二是实现接口方式。
配置文件:
- init-method
- destory-method
实现接口
- InitializingBean
- DesposebleBean
关闭容器
ConfigurableApplicationContext
-
close()(比较暴力)
-
registerShutdownHook()
bean的生命周期
-
初始化容器
-
创建对象(new 内存分配)
-
执行构造方法
-
执行属性并注入(set)
-
执行bean的初始化方法(init-method)
-
-
使用bean
- 执行业务操作
-
关闭容器
- 执行bean的销毁方法(destory-method)
4、依赖注入
依赖注入有构造器注入和、setter注入、集合注入等,其中setter注入是重点
-
setter注入
<!--注入简单类型--> <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"> <!--property标签:设置注入属性--> <!--name属性:设置注入的属性名,实际是set方法对应的名称--> <!--value属性:设置注入简单类型数据值--> <property name="connectionNum" value="100"/> <property name="databaseName" value="mysql"/> </bean> <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/> <!--注入引用类型--> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"> <!--property标签:设置注入属性--> <!--name属性:设置注入的属性名,实际是set方法对应的名称--> <!--ref属性:设置注入引用类型bean的id或name--> <property name="bookDao" ref="bookDao"/> <property name="userDao" ref="userDao"/> </bean>
setter注入需要提高对应的set方法
package com.itheima.dao.impl; import com.itheima.dao.BookDao; public class BookDaoImpl implements BookDao { private String databaseName; private int connectionNum; //setter注入需要提供要注入对象的set方法 public void setConnectionNum(int connectionNum) { this.connectionNum = connectionNum; } //setter注入需要提供要注入对象的set方法 public void setDatabaseName(String databaseName) { this.databaseName = databaseName; } public void save() { System.out.println("book dao save ..."+databaseName+","+connectionNum); } }
-
构造器注入
<!--标准书写--> <!--普通数据类型注入--> <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"> <!--根据构造方法参数名称注入--> <constructor-arg name="connectionNum" value="10"/> <constructor-arg name="databaseName" value="mysql"/> </bean> <!--引用数据类型注入--> <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"> <constructor-arg name="userDao" ref="userDao"/> <constructor-arg name="bookDao" ref="bookDao"/> </bean>
<!--解决形参名称的问题,与形参名不耦合--> <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"> <!--根据构造方法参数类型注入--> <constructor-arg type="int" value="10"/> <constructor-arg type="java.lang.String" value="mysql"/> </bean> <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"> <constructor-arg name="userDao" ref="userDao"/> <constructor-arg name="bookDao" ref="bookDao"/> </bean>
<!--解决参数类型重复问题,使用位置解决参数匹配--> <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"> <!--根据构造方法参数位置注入--> <constructor-arg index="0" value="mysql"/> <constructor-arg index="1" value="100"/> </bean> <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"> <constructor-arg name="userDao" ref="userDao"/> <constructor-arg name="bookDao" ref="bookDao"/> </bean>
同样的,对应的实体类需要提供对应的构造方法
package com.itheima.dao.impl; import com.itheima.dao.BookDao; public class BookDaoImpl implements BookDao { private String databaseName; private int connectionNum; public BookDaoImpl(String databaseName, int connectionNum) { this.databaseName = databaseName; this.connectionNum = connectionNum; } public void save() { System.out.println("book dao save ..."+databaseName+","+connectionNum); } }
package com.itheima.service.impl; import com.itheima.dao.BookDao; import com.itheima.dao.UserDao; import com.itheima.service.BookService; public class BookServiceImpl implements BookService{ private BookDao bookDao; private UserDao userDao; public BookServiceImpl(BookDao bookDao, UserDao userDao) { this.bookDao = bookDao; this.userDao = userDao; } public void save() { System.out.println("book service save ..."); bookDao.save(); userDao.save(); } }
-
集合、数组注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"> <!--数组注入--> <property name="array"> <array> <value>100</value> <value>200</value> <value>300</value> </array> </property> <!--list集合注入--> <property name="list"> <list> <value>itcast</value> <value>itheima</value> <value>boxuegu</value> <value>chuanzhihui</value> </list> </property> <!--set集合注入--> <property name="set"> <set> <value>itcast</value> <value>itheima</value> <value>boxuegu</value> <value>boxuegu</value> </set> </property> <!--map集合注入--> <property name="map"> <map> <entry key="country" value="china"/> <entry key="province" value="henan"/> <entry key="city" value="kaifeng"/> </map> </property> <!--Properties注入--> <property name="properties"> <props> <prop key="country">china</prop> <prop key="province">henan</prop> <prop key="city">kaifeng</prop> </props> </property> </bean>
5、注解开发
由于Spring中的配置文件过于繁琐,为了提高开发效率,Spring3.0提出了纯注解开发,大大提高了开发效率。下面看Spring都提供了哪些注解,这些注解都有哪些作用。
@Component:放在类上方,将该类加入IOC容器里面管理,它的衍生注解有以下几个:
- @Controller:用于表现层(Controller层)的bean定义
- @Service:用于业务层(Service层)的bean定义
- @Repository:用于数据层(Dao层)的bean定义
- @Mapper:同样也是用于数据层(map层)的bean定义
@Configuration:设置当前类为配置类,
@ComponentScan(“”):用于设置扫描路径,后面括号里中的路径的所有使用了Component及其衍生注解的类都会加入到容器当中,该注解只能使用一次,如需扫描多个路径采用数组的形式。
@Scope(“”):用于设置bean的作用范围,设置单例或多例。
@PostConstruct:用于定义bean的生命周期,使用了这个注解的方法会在该注解所在类的构造方法运行之后执行。
@PreDestroy:用于定义bean的生命周期,使用了这个注解的方法会在这个bean销毁之前执行。
@Autowired:自动注入bean对象,底层是通过反射创建对象所以无需提供setter方法,但需要提供唯一的构造方法。
@Qualifier(“”):指定bean名称装配bean对象,不能单独使用,必须配合@Autowired注解一起使用。
@Value(“”):用于给简单类型的变量注入值,括号里填注入的值。
@Propertysource(“”):用于加载外部的property文件,括号中写需要加载的文件名,多文件需要用数据格式,不允许使用通配符。
@Bean:用于将第三方的bean加入到容器当中,如有第三方bean需要注入的话,直接在方法形参中添加即可,Spring会自动从容器中注入。
@Import(“”):将配置类加入到核心配置中,该注解只能使用一次,多个配置类需要用数组的形式。
二、事务
Spring中的事务和Mysql里面的事务是同一概念,都是指同一个操作,要么都发生,要么都不发生!
1、开启事务
Spring中的事务作用是保证一系列操作数据库的的行为同成功或同失败,以便保证数据的安全性,典型的例子是银行转账操作。
要在Spring中使用事务,需要按照以下操作:
- 在需要添加事务的接口上方添加**@Transactional** 注解,配置当前接口方法具有事务,注意:Spring中的注解式事务通常添加在接口上方而不是业务层实现类中,这样做的目的是为了降低耦合。@Transactional 注解既可以添加在具体的业务方法中开启事务也可以添加到接口中开启事务。
package com.itheima.service;
import org.springframework.transaction.annotation.Transactional;
import java.io.FileNotFoundException;
import java.io.IOException;
public interface AccountService {
/**
* 转账操作
* @param out 传出方
* @param in 转入方
* @param money 金额
*/
//配置当前接口方法具有事务
@Transactional
public void transfer(String out,String in ,Double money) throws IOException;
}
- 配置事务管理器,mybatis使用的是jdbc事务
//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
- 在spring配置类中添加**@EnableTransactionManagement**注解开启注解式驱动
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
2、事务属性
在学习事务属性之前,需要先了解一下事务角色,事务中有两种角色,事务管理员和事务协调员
- 事务管理员:发起事务方,在Spring中通常指业务层中开启事务的方法
- 事务协调员:加入事务方,在Spring中通常指代数据层中的方法,也可以是业务层中的方法
我们可以对事务进行一些相关的配置,如下图,其中最为重要的便是rollbackFor属性,这个属性可以设置事务回滚的异常,
事务除了运行时异常和error型的异常,其他的异常都不回滚 需要设置事务的rollbackFor属性设置回滚异常
package com.itheima.service;
import org.springframework.transaction.annotation.Transactional;
import java.io.FileNotFoundException;
import java.io.IOException;
public interface AccountService {
/**
* 转账操作
* @param out 传出方
* @param in 转入方
* @param money 金额
*/
//配置当前接口方法具有事务
//事务除了运行时异常和error型的异常,其他的异常都不回滚 需要设置事务的相关配置,这里使用rollbackFor设置回滚异常
@Transactional(rollbackFor = {IOException.class})
public void transfer(String out,String in ,Double money) throws IOException;
}
事务的属性还有一个比较重要的属性,propagation ,设置事务的传播行为 什么是事务的传播行为呢?事务的传播行为是指事务协调员对于事务管理员的态度,具体点讲就是一个A事务加入B事务时,A事务对B事务采取一种怎样的行为态度 ,一般的传播行为如下图:
package com.itheima.service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public interface LogService {
//propagation设置事务属性:传播行为设置为当前操作需要新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
void log(String out, String in, Double money);
}
三、AOP
在java基础阶段,我们学习了面向对象编程(OOP),这是一种编程思想,在进入框架学习之后,我们将学习一种新的编程思想,AOP,面向切面编程,面向切面编程就是在不改动原有方法的基础上给方法添加新的功能,对其进行增强 。
1、AOP的核心概念
2、AOP切入点表达式
3、通知的类型
package com.itheima.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2(){}
//@Before:前置通知,在原始方法运行之前执行
@Before("pt()")
public void before() {
System.out.println("before advice ...");
}
//@After:后置通知,在原始方法运行之后执行
// @After("pt2()")
public void after() {
System.out.println("after advice ...");
}
//@Around:环绕通知,在原始方法运行的前后执行
// @Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示对原始操作的调用
Object ret = pjp.proceed();
System.out.println("around after advice ...");
return ret;
}
// @Around("pt2()")
public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示对原始操作的调用
Integer ret = (Integer) pjp.proceed();
System.out.println("around after advice ...");
return ret;
}
//@AfterReturning:返回后通知,在原始方法执行完毕后运行,且原始方法执行过程中未出现异常现象
// @AfterReturning("pt2()")
public void afterReturning() {
System.out.println("afterReturning advice ...");
}
//@AfterThrowing:抛出异常后通知,在原始方法执行过程中出现异常后运行
@AfterThrowing("pt2()")
public void afterThrowing() {
System.out.println("afterThrowing advice ...");
}
}