Spring:轻量级,非入侵式的IOC容器管理工具
主学spring framework
spring这个框架利用IOC和AOP的编程思想,帮我们做对象的创建,管理和装配.
- 根本作用:所有对象不用new了
- 根本重点:IOC(对象控制权反转)和AOP(面向切面编程)
-
spring的根本是在interface21框架上发展而来的.一步步发展出一个个的spring framework(spring框架)
-
Spring是一个概念,springmvc,springboot等都属于Spring家族,都是一个个的spring framework
spring-webmvc依赖集成了如spring-core,spring-web等基本spring framework
核心内容:IOC对象控制权反转
DI是进行IOC容器内对象的赋值操作的
IOC对象控制权反转是一种编程思想
-
DI(依赖注入,也可以理解为对象属性自动赋值)只是实现IOC的一种方式,其他的还有如XML文件,注解的实现IOC的方式
-
IOC容器的根本目的是让代码类之间解耦,所有人都去IOC容器中找自己需要的资源
<!--这个beans就是IOC容器,在这里面定义的所有东西都会放在IOC容器中-->
<beans xmlns="http://www.springframework.org/schema/beans"
</beans>
惰性加载
-
在实体类的构造函数中输出一句话,用Application加载beans.xml,就会执行输出语句
-
而用BeanFactory就不会.
Application的顶级父类是BeanFactory,这是spring1.0时的容器创建对象.
它在创建对象时是惰性加载,实际就是单例设计模式的懒汉模式
<!--开启惰性加载-->
<bean lazy-init="true"/>
这里我们只运行这一行,就不会运行输出语句了
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
注解开发
定义bean
@Component("student")
public class Student {
全注解开发,去掉配置文件
//把配置文件剩下的东西变成下面这个类
@Configuration
@ComponentScan("pojo")
public class SpringConfig {
}
//改变获取spring容器的方式为注解配置类获取
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
bean管理
@Component("student")
@Scope("prototype")
public class Student {
@PostConstruct
public void init(){
System.out.println("初始化");
}
@PreDestroy
public void destroy(){
System.out.println("销毁前");
}
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
Student student = context.getBean("student", Student.class);
//如果是@Scope("singleton"),会调用destroy()
context.close();
自动装配
@Autowired//byType自动装配
@Qualifier("student2")//byName自动装配
private Student student;
//或者
@Autowired
@Qualifier("student1")
public void setStudent(Student student){
this.student = student;
}
简单类型自动注入
//配置类上加
@PropertySource("1.properties")//不支持通配符
public class SpringConfig {
@Value("${name}")
private String name;
管理第三方的bean
@Configuration
public class jdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername("root");
return dataSource;
}
}
//主config扫描副config所有的包
@ComponentScan("pojo")
public class SpringConfig {}
//或者直接导入
@Import({jdbcConfig.class})
public class SpringConfig {
spring扫描包智能注入所需bean
@ComponentScan("pojo")
public class SpringConfig {}
//只要Student在pojo包下,即使不写@aotuwried,spring会智能帮我们注入进去
@Bean
public DataSource dataSource(Student student){
整合Mybatis
mybatis只管理sqlSessionFactroy对象,因为无论你业务怎么变化,对于数据库的基本连接是不会变的.而spirng管理的是业务面.
基本环境
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
driver=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///springreview?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"></properties>
<typeAliases>
<package name="domain"/>
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="jdbc"></transactionManager>
<!--pooled是利用了连接池的思想,其他还有unPooled-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${password}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="dao"/>
</mappers>
</configuration>
UserDao
package dao;
import domain.Student;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface UserDao {
//没有实现类,用自动代理来接收值
//Mybatis会用自动代理的形式,扫描配置文件,创建对应的实现类
@Select("select * from student where id = #{id}")
List<Student> findById(Integer id);
}
实体类
package domain;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class Student {
private Integer id;
private String name;
private Integer age;
}
import dao.UserDao;
import domain.Student;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
public class Test {
public static void main(String[] args) {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
try {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream);
SqlSession sqlSession = build.openSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<Student> byId = mapper.findById(2);
Iterator<Student> iterator = byId.iterator();
while (iterator.hasNext()){
Student next = iterator.next();
System.out.println(next);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
整合mybatis
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!--spring操作数据库-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.19</version>
</dependency>
配置DataSource
package com.itCast.configs;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class jdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setPassword("root");
dataSource.setUrl("jdbc:mysql:///springreview?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC");
dataSource.setUsername("root");
return dataSource;
}
}
将mybatis-config.xml替换成配置类
package com.itCast.configs;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
public class MybatisConfig {
@Bean//Spring快速构建SqlSessionFactory的对象
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage("com.itCast.domain");
return sqlSessionFactoryBean;
}
//spring-jdbc依赖中有jdbc的事务管理,不用配置
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.itCast.dao");
return mapperScannerConfigurer;
}
}
主配置类融合所有副配置类
package com.itCast.configs;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan("com.itCast")
@Import({jdbcConfig.class,MybatisConfig.class})
@PropertySource("jdbc.properties")
public class SpringConfig {}
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserDao bean = context.getBean(UserDao.class);
List<Student> byId = bean.findById(2);
整合junit
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--Spring框架用来做单元测试的工具。使用SpringTest需要结合Junit一起使用-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.14</version>
</dependency>
package com.itCast;
import com.itCast.configs.SpringConfig;
import com.itCast.dao.UserDao;
import com.itCast.domain.Student;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Iterator;
import java.util.List;
//运行器:让junit4运行于spirng测试环境
@RunWith(SpringJUnit4ClassRunner.class)
//指定上下文配置类,还可以指定配置文件
@ContextConfiguration(classes = SpringConfig.class)
public class Test {
@Autowired
private UserDao userDao;
@org.junit.Test
public void test() {
List<Student> byId = userDao.findById(2);
Iterator<Student> iterator = byId.iterator();
}
}
Aop面向切面编程
<!--spring-webmvc默认导入了spring-aop依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.8.RC1</version>
</dependency>
定义通知类
package com.itCast.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect//标明这是一个通知类
public class MyAop {
//执行,筛选方法:没有返回值的对应类路径下的对应参数列表的方法,执行时都被定义成切入点
@Pointcut("execution(* com.itCast.dao.UserDao.findById(Integer))")
private void point(){}
//定义通知
@Before("point()")
public void aspect(){
System.out.println("这是一个通知");
}
}
@EnableAspectJAutoProxy//开启自动代理通知
public class SpringConfig {}
工作流程
spirng只读取被切面配置了的切入点
spirng如果通知和切入点匹配成功,会创建原始对象(目标对象)的代理对象.
然后用代理对象来执行操作.
spring的aop的本质是代理模式
System.out.println(userDao);
System.out.println(userDao.getClass());
//执行结果
org.apache.ibatis.binding.MapperProxy@146587a2
class com.sun.proxy.$Proxy31
切入点表达式
*是必须有,…可以为0
通知类型
//环绕通知
@Around("point()")
public Object aspect(ProceedingJoinPoint joinPoint) throws Throwable {
//获取当前连接点签名
Signature signature = point.getSignature();
//连接点名字 = 方法名
String name = signature.getName();
System.out.println("通知前");
Object proceed = joinPoint.proceed();
System.out.println("通知后" + name);
return proceed;
}
//方法运行结束后织入
@AfterReturning("execution(* com.itCast.dao.UserDao.findById(Integer))")
public void aspect(){
System.out.println("方法运行结束后织入");
}
//方法出现异常后织入
@AfterThrowing("execution(* com.itCast.dao.UserDao.findById(Integer))")
public void aspect(){
System.out.println("方法出现异常后织入");
}
获取aop通知的数据
@Before("point()")
public void aspect(JoinPoint joinPoint){
//获取连接点参数
Object[] args = joinPoint.getArgs();
System.out.println("参数列表是"+ Arrays.toString(args));
}
@Around("point()")
public Object aspect(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
//修改连接点参数
args[0] = 100;
//把新的参数给连接点进行
Object proceed = joinPoint.proceed(args);
System.out.println("这是一个通知");
return proceed;
}
@AfterReturning(value = "point()",returning = "ret")
//point对象必须在第一个,ret是获取连接点的返回值
public void aspect(JoinPoint joinPoint, Object ret){
System.out.println("返回值是"+ret);
}
@AfterThrowing(value = "point()",throwing = "ret")
//获取异常信息
public void aspect(Throwable ret){
System.out.println("异常信息是"+ret);
}
案例:数据兼容处理
@Around("point()")
public Object aspect(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
//如果是字符串,去空格
if(args[i].getClass().equals(String.class)){
args[i] = args[i].toString().trim();
}
}
//原方法执行后把返回值交给了我们,我们需要最后再给他返回出去
return joinPoint.proceed(args);
}
Spirng事务
spirng事务是可以在业务层上加的,实现业务层调用的数据层方法同成功同失败
基本的转账业务
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;
@Repository
public interface UserDao {
//@Param("money")给形参命名,到时sql语句可对应自动匹配
@Update("update student set money = money-#{money} where id = #{id}")
void outMoney(@Param("id")Integer id,@Param("money") Double money);
//每个增删改操作都有自己单独的事务,如果后面的操作出问题,前面的操作不会改变事务
@Update("update student set money = money+#{money} where id = #{id}")
void inMoney(@Param("id")Integer id,@Param("money") Double money);
}
开启spring事务
@EnableTransactionManagement//开启spring事务管理
public class SpringConfig {}
jdbcConfig配置Spring平台事务管理器
//Spring的平台事务管理器允许你在业务层进行事务管理
@Bean//@Transactional需要有一个TransactionManager的bean
public PlatformTransactionManager platformTransactionManager(DataSource dataSource){
//spring使用jdbc的事务管理,需要一个数据源
// Mybatis默认使用的是jdbc的事务,以后要变直接改这里
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
业务层配置事务属性
import org.springframework.transaction.annotation.Transactional;
@Transactional//使成员拥有spring事务属性,可以写在方法上.
public interface UserService {
void money(int outId,int inId,Double money);
}
package com.itCast.service.impl;
import com.itCast.dao.UserDao;
import com.itCast.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void money(int outId, int inId, Double money) {
userDao.outMoney(outId,money);
//出现异常,测试事务
int i = 1/0;
userDao.inMoney(inId,money);
}
}
Spring事务角色
Spring的事务管理员(事务发起方)也是一个事务,将**方法中的所有其他事务(事务协调员)**方法(事务加入方)加入自己的事务,实现同成功同失败.
Spring事务属性
//mysql事务默认只对error和运行时异常生效
@Transactional(rollbackFor = {IOException.class})
//只读事务,永不超时
@Transactional(readOnly = true,timeout = -1)
Spring事务传播行为
设置事务协调员对于事务管理员的态度
//自己单独成为一个事务,不加入事务管理员,不被同成功同失败
@Transactional(propagation = Propagation.REQUIRES_NEW)
void LogService(Integer outId,Integer inId,Double money);
//测试
try {
userDao.outMoney(outId,money);
int i = 1/0;
userDao.inMoney(inId,money);
}finally {
logService.LogService(outId,inId,money);
}