目录
1 概述
- Spring 是轻量级的开源的JavaEE框架
- Spring 可以解决企业应用开发的复杂性
- Spring 有两个核心部分:IOC 和 AOP
- IOC:控制反转,把创建对象过程交给 Spring 容器进行管理
- AOP:面向切面,不修改源代码进行功能增强
Spring 特点
- ①方便解耦,简化开发
②AOP 编程支持
③方便程序测试
④方便和其他框架进行整合
⑤方便进行事物操作
⑥降低 API 开发难度
2 IOC
- 控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理
- 使用IOC目的:为了降低耦合度
2.1 IOC 实现原理
xml解析、工厂模式、反射
对于<bean id="dao" class="com.hy.UserDao"/>
配置来说,有如下动作:↓
class UserFactory {
public static UserDao getDao() {
String classValue = class属性值; // 由xml解析获得,获得类的全路径名
Class clazz = Class.forName(classValue); // 由反射获取类对象
return (UserDao)clazz.newInstance(); // 由类对象进行创建新的对象
}
}
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂 Factory
- Spring提供IOC容器实现两种方式:(两个接口)
①BeanFactory
是Spring内部的使用接口,不提供给开发人员进行使用
加载配置文件时,不会创建里面的对象,而是在使用时才创建对象
②ApplicationContext
是BeanFactory接口的子接口,提供了更多功能,一般由开发人员使用 - 加载配置文件时,就会把配置文件中的对象进行创建
2.2 bean 注入
Bean管理包含两个操作:Spring创建对象、Spring注入属性
①Spring创建对象
- 在Spring配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象创建
<bean id="user" class="com.hy.User"/>
②Spring注入属性
- set注入
<property name="name" value="张三"/>
- 构造器注入
<constructor-arg name="name" value="张三"/>
- p命名空间注入
xmlns:p="http://www.springframework.org/schema/p"
<bean id="user" class="com.hy.User" p:name="张三"/ >
注入属性时可以为属性注入null值
<property name="name"><null/></property>
如果属性中含有特殊符号(…处为需要转义的字符串),两种方法
使用< >进行转义
<property name="name"><value><![CDATA[......]]></value></property>
- 注入外部bean
<property name="userDao" ref="userDaoImpl"/>
可以把注入集合的部分提取出来
- 在spring配置文件中引入命名空间util
- 使用util标签完成list集合注入提取
<util:list id="listBall">
<value>羽毛球</value>
<value>乒乓球</value>
<value>足球</value>
</util:list>
<bean id="user" class="com.hy.bean.User">
<property name="list" ref="listBall"/>
</bean>
2.3 bean 的类型
- Spring有两种类型的bean,一种是普通bean,另一种是工厂bean (FactoryBean)
①普通bean:在配置文件中定义bean的类型就是返回类型
②工厂bean:在配置文件中定义bean的类型可以和返回类型不一样
2.4 bean 的作用域
- 在spring里面,可以设置创建bean实例是单实例(默认)还是多实例
在<bean>
标签中,属性 scope 用于指定单实例还是多实例 - singleton:单例,整个容器中只有一个对象实例,并且在加载配置文件时就创建,因为是单例的,类似于饿汉式单例模式
- prototype:多例,每次获取bean都产生一个新的对象,但并不是在加载配置文件时创建,而是在getBean()方法调用时创建
当然scope属性值不止这些,还有request、session,但并不常见
2.5 bean 的生命周期
如果使用了bean后置处理器,则会有7步,否则就5步
- 通过构造器创建bean实例(无参构造)
- 为bean的属性设置值和对其他bean的引用(调用set方法)
[把bean实例传递bean后置处理器的postProcessBeforeInitialization()方法] - 调用bean的初始化方法(需要进行配置初始化方法)
[把bean实例传递bean后置处理器的postProcessAfterInitialization()方法] - bean可以使用了(即对象已经获取到了)
- 当容器关闭时,调用bean的销毁方法(需要进行配置销毁方法)
下面来测试一下 bean 的各个阶段
<bean id="orders" class="com.hy.beanCycle.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"/>
</bean>
public class Orders {
private String oname;
public Orders() {
System.out.println("无参构造执行了,创建bean实例");
}
public void setOname(String oname) {
this.oname = oname;
System.out.println("set方法执行了,设置成员变量值");
}
// 创建执行的初始化方法
public void initMethod() {
System.out.println("初始化方法执行了");
}
// 创建销毁方法
public void destroyMethod() {
System.out.println("销毁方法执行了");
}
}
public class BeanCycleTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans6.xml");
Orders orders = context.getBean("orders", Orders.class);
//手动将bean销毁
context.close();
}
}
现在我们测试带后置处理器的 bean 生命周期
<!--配置后置处理器,会为所有bean配置后置处理器-->
<bean id="myBeanPost" class="com.hy.beanCycle.MyBeanPost"/>
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后置处理器的postProcessBeforeInitialization()方法执行了");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后置处理器的postProcessAfterInitialization()方法执行了");
return bean;
}
}
2.6 自动装配
对于实体类,可以进行自动装配
public class Emp {
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
}
<bean id="emp" class="com.hy.bean.Emp" autowire="byType"></bean>
<bean id="dept" class="com.hy.bean.Dept">
<property name="dname" value="Tom"/>
</bean>
- byName:根据实体类中的属性名,与已装配的bean的id进行比较(即dept),找到则注入成功
- byType:根据实体类中的属性类型,与已装配的bean的class进行比较(即Dept),找到则注入成功
- 注意:被注入的属性仍需自己装配
2.7 引入外部属性文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=
在 spring 配置文件中配置,这里我们使用的是 Spring 自带的数据源
<!-- 引入外部属性文件,注意需要导入context 外部命名空间 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</bean>
2.8 注解开发
- 要想使用注解开发,需要引入 aop 依赖
- jar包:
spring-aop-5.2.6.RELEASE.jar - Maven:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.0.RELEASE</version>
</dependency>
- 并开启注解扫描(在 spring 配置文件中配置)
<context:component-scan base-package="com.hy.entity"/>
2.8.1 创建对象的注解
①@Component:用在任意地方
②@Service:用在业务层
③@Controller:用在web层,控制层
④@Repository:用在dao层(持久层)
- 以上四个注解功能是一样的,都可以用来创建bean实例
@Component(value = "user") // 与<bean id="user" class="com.hy.entity.User"/>等价
public class User {
public void hello(){
System.out.println("hello");
}
}
- 如果不指定value,默认是类名首字母小写user
- 可以自定义扫描哪个注解,下面是只扫描entity包下的所有Controller注解
<context:component-scan base-package="com.hy.entity" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
也可以自定义不扫描哪个注解,下面是扫描entity包下的所有除Controller以外的注解
<context:component-scan base-package="com.hy.entity">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
2.8.2 注解方式进行属性注入
- @AutoWired:根据属性类型进行自动注入,byType,是Spring包中提供的
- 第一步,将service和dao对象创建,在service和dao类添加创建对象的注解Service和Repository
@Repository
public class UserDaoImpl implements UserDao { }
@Service
public class UserService { }
- 第二步,在service注入dao对象,在service添加dao类型属性,在属性上面使用AutoWired注解
@Service
public class UserService {
@Autowired
private UserDao userDao;
}
这时,Service层就可以使用userDao对象了
- @Qualifier:根据属性名称进行注入,byName(在一个接口有多个实现类的情况下使用)
- 需要和AutoWired一起使用
@Repository(value = "userDaoImpl")
public class UserDaoImpl implements UserDao { }
@Service
public class UserService {
@Autowired
@Qualifier(value = "userDaoImpl")
private UserDao userDao;
}
- @Resource:可以根据类型注入,也可以根据名称注入是java扩展包提供的
@Resource //根据类型注入
@Resource(name = "userDaoImpl") //根据名称注入
private UserDao userDao;
- @Value:注入普通类型属性
@Value("Tom")
private String name;
此时的 name 值就为 Tom 了
2.8.3 完全注解开发(一般在SpringBoot中使用)
- 创建配置类,替代xml配置文件
@Configuration // 将当前类设置为配置类,替代xml配置文件
@ComponentScan(basePackages = {"com.hy"}) // 开启注解扫描
public class SpringConfig { }
- 测试类
// 加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.hello();
3 AOP
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率
- 通俗点说:在不修改源代码的基础上进行功能的添加、增强
3.1 AOP底层原理
- 底层使用了动态代理,有两种情况:
①有接口情况下,使用JDK动态代理,创建接口实现类的代理对象,进行增强类中的方法
②没接口情况下,使用CGLIB动态代理,创建子类的代理对象,进行增强类中的方法
3.2 jdk动态代理
- 使用Proxy类里面的newProxyInstance()方法创建代理类的实例
参数:
- ClassLoader:类加载器
- interfaces:增强方法所在的类,这个类所实现的接口,支持多个接口
- InvocationHandler:实现这个接口InvocationHandler,创建代理对象,写增强方法
public static void main(String[] args) {
Class[] interfaces = {UserDao.class};
// 创建接口实现类的代理对象
UserDao proxy = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),
interfaces, new UserDaoProxy(new UserDaoImpl()));
proxy.add();
}
// 创建代理类
class UserDaoProxy implements InvocationHandler{
// 把创建的是谁的代理对象,把谁传递过来,即UserDaoImpl
// 有参构造传递
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
// 增强的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法之前
System.out.println("方法之前执行...");
// 被增强的方法执行
Object res = method.invoke(obj, args);
// 方法之后
System.out.println("方法之后执行...");
return res;
}
}
原有的接口实现类:
public class UserDaoImpl implements UserDao{
@Override
public void add() {
System.out.println("add方法正在执行...");
}
@Override
public void update() {
System.out.println("update方法正在执行");
}
}
运行后可发现原有接口中的方法已被增强,但并未改变原有接口实现类的代码,这就是 AOP 的本质
3.3 相关术语
- ①连接点:类中的哪些方法可以被增强,那么这些方法就称为连接点
- ②切入点:实际被增强的方法,这些方法称为切入点
- ③**通知(增强)**:实际被增强的逻辑部分称为通知(增强)
通知的种类:
前置通知:在方法前执行
后置通知:在方法后执行
环绕通知:在方法前后均执行
异常通知:抛出异常后执行
最终通知:类似于finally,一定会执行 - ④切面:是一个动作,把通知应用到切入点的过程
3.4 AOP 的使用
Spring 框架一般都是基于 AspectJ 实现 AOP 操作,AspectJ 不是 Spring 组成部分,是独立的 AOP 框架,一般把 AspectJ 和 Spring 框架一起使用,进行 AOP 操作
3.4.1 xml方式
配置切入点表达式,用于指定对哪个类里面的哪个方法进行增强
语法:execution([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]))
<bean id="book" class="com.hy.aopxml.Book"></bean>
<bean id="bookProxy" class="com.hy.aopxml.BookProxy"></bean>
<aop:config>
<aop:pointcut id="cut" expression="execution(* com.hy.aopxml.Book.buy(..))"/>
<aop:aspect ref="bookProxy">
<aop:before method="before" pointcut-ref="cut"></aop:before>
</aop:aspect>
</aop:config>
3.4.2 注解方式
①在spring配置文件中,开启注解扫描
②使用注解创建 User 和 UserProxy 对象
③在增强类上面添加注解 @Aspect
④在 spring 配置文件中开启生成代理对象
<aop:aspectj-autoproxy/>
⑤在代理类(增强类)中配置通知
// 前置通知
@Before(value = "execution(* com.hy.aopanno.User.add(..))")
public void before(){
System.out.println("before......");
}
// 后置通知
@After(value = "execution(* com.hy.aopanno.User.update(..))")
public void after(){
System.out.println("after......");
}
// 环绕通知
@Around(value = "execution(* com.hy.aopanno.User.delete(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("around:before......");
proceedingJoinPoint.proceed(); // 如果不执行此方法,那么原方法是不会执行的
System.out.println("around:after......");
}
@AfterThrowing(value = "execution(* com.hy.aopanno.User.select(..)))")
public void afterThrowing(){
System.out.println("afterThrowing......");
}
@AfterReturning(value = "execution(* com.hy.aopanno.User.other(..)))")
public void afterReturning(){
System.out.println("afterReturning......");
}
- 如果有相同的切入点,可以对相同切入点进行抽取
@Pointcut(value = "execution(* com.hy.aopanno.User.add(..)))")
public void pointDemo(){ }
- 在需要使用相同切入点时配置相应的方法即可
@Before(value = "pointDemo()")
public void before(){
System.out.println("before......");
}
- 当有多个增强类对同一个方法进行增强,则可以设置增强类的优先级
- 在增强类上面添加注解@Order(数字类型值),值越小优先级越高,优先执行值小的增强类
@Order(1)
public class UserProxy1 { }
@Order(3)
public class UserProxy2 { }
- 同样可以使用完全注解开发,创建配置类
@Configuration
@ComponentScan(basePackages = {"com.hy"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig { }
4 JDBCTemplate
- Spring框架对JDBC进行封装,使用JDBCTemplate方便实现对数据库操作,当然日后开发我们不会使用JDBCTenplate,而是使用Mybatis进行集成,使用其中的SqlSession核心对象进行操作
使用步骤:
①引入相关依赖
spring-orm-5.2.6.RELEASE
spring-jdbc-5.2.6.RELEASE
spring-tx-5.2.6.RELEASE
mysql-connector-java-5.1.47
②在spring配置文件配置数据库连接池,这里使用阿里巴巴的德鲁伊连接池,使用前需要引入依赖
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql://user_db"/>
<property name="username" value="root"/>
<property name="password" value=""/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
</bean>
③配置JdbcTemplate对象,注入DataSource
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
④创建service类,创建dao类,在dao注入jdbcTemplate对象
@Repository
public class BookDaoImpl implements BookDao {
//注入jdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
}
@Service
public class BookService {
@Autowired
private BookDao bookDao;
}
⑤进行增删改操作(三者做法类似)
public void insertUser(User user) {
String sql = "insert into t_user values(?, ?, ?)";
jdbcTemplate.update(sql, user.getUser_id(), user.getUsername(), user.getUstatus());
}
public void updateUser(User user) {
String sql = "update t_user set username=?, ustatus=? where user_id=?";
jdbcTemplate.update(sql, user.getUsername(), user.getUstatus(), user.getUser_id());
}
public void deleteUserById(int id) {
String sql = "delete from t_user where user_id=?";
jdbcTemplate.update(sql, id);
}
⑥进行查询操作
- 查询某个值
requiredType:sql语句的返回值类型
public int selectCountFromUser() {
String sql = "select count(*) from t_user";
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
return integer;
}
- 查询某个对象
rowMapper:是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装
public User selectUserById(int id) {
String sql = "select * from t_user where user_id=?";
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), id);
return user;
}
- 查询集合
public List<User> selectAllUser() {
String sql = "select * from t_user";
List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class));
return users;
}
⑦批量添加
public void batchInsert(List<Object[]> args) {
String sql = "insert into t_user values(?, ?, ?)";
jdbcTemplate.batchUpdate(sql, args);
}
5 事物
- 一般将事物添加到三层结构里的Service层,即业务逻辑层
- 使用Spring进行事务管理操作有两种方式:编程式事物、声明式事物
编程式事物较为繁琐,需要在代码中手动进行编程并配置
声明式事物实现简单,做出相应的配置即可 - Spring的声明式事物底层使用的是AOP
- Spring事物管理API:提供了一个PlatformTransactionManager接口,代表事务管理器,这个接口针对不同的框架提供了不同的实现类
声明式事物管理
我们先看基于注解的方式:
- ①在Spring配置文件中配置事物管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
- ②引入tx名称空间
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
- ③开启事物注解,传入事物管理器
<tx:annotation-driven transaction-manager="transactionManager"/>
- ④在service类或其中的方法上,添加事物注解 @Transactional
如果添加到类上面,这个类里面所有方法都会添加事物
如果添加到方法上面,只为这个方法添加事物
@Service
@Transactional
public class UserService { }
相关参数配置:
propagation:事物传播行为
- 多个事物方法之间进行调用,这个过程中事物是如何进行管理的
- spring定义了7个传播行为:required 和 requires_new 较为常见
- required:如果有事物在运行,当前的方法就在这个事物内运行,否则就启动一个新的事物,并在自己的事物内运行
- requires_new:当前的方法必须启动新事物,并在它自己的事物内运行,如果有实物正在运行,应该将它挂起
- supports、not_supported、mandatory、never、nested
isolation:事物隔离级别
- 多事物操作之间产生的影响,脏读、不可重复读、幻读
- 脏读:一个事物读取了另一个事物中未提交的数据
- 不可重复读:在一个事物中,两次读取的数据不一致,因为其他事物进行了修改操作
- 幻读:在一个事物中,读取到另一提交事物所添加的数据
- 四个隔离级别:
- 读未提交:READ_UNCOMMITTED
- 读已提交:READ_COMMITTED
- 可重复读:REPEATABLE_READ
- 串行化:SERIALIZABLE
timeout:超时时间
- 事物需要在一定时间内提交,如果不提交则进行回滚
- 默认值-1,设置时间是以秒为单位进行计算
readOnly:是否只读
- 默认值false,表示可以查询和增删改
- 设置为true后,只能查询
rollbackFor:回滚
- 设置出现哪些异常进行事物回滚
noRollbackFor:不回滚
- 设置出现哪些异常不进行事物回滚
继续看基于 xml 配置文件方式:
①配置事物管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
②配置通知
<tx:advice id="txadvice">
<!--配置事物相关参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事物-->
<tx:method name="accountMoney"/>
</tx:attributes>
</tx:advice>
③配置切入点和切面
<aop:config>
<!--配置切入点-->
<aop:pointcut id="cut" expression="execution(* com.hy.service.User1Service.accountMoney(..))"/>
<!--配置切面-->
<aop:advisor pointcut-ref="cut" advice-ref="txadvice"/>
</aop:config>
我们也可以像spring一样,完全注解开发
- 创建配置类,使用配置类替代xml配置文件
@Configuration // 配置类
@ComponentScan(basePackages = "com.hy") // 注解扫描
@EnableTransactionManagement // 开启事物
public class TxConfig {
// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/springtest");
dataSource.setUsername("root");
dataSource.setPassword("");
return dataSource;
}
// 创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
// 到ioc容器中根据类型找到DataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 注入DataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}