人生中第一次学Spring,简单的总结一下。有没有高人给小弟指点指点。还没学SpringMVC,MyBatis,只学了SpringFramework,我将狂肝。
1.广义:Spring的整个生态框架,包括听说过的SpringMVC,SpringBoot, SpringCloud等,整个Spring全家桶。
2.狭义:Spring Framework,意思是一个基础小框架 , 所有的Spring全家桶都是基于它构建的。反正就是一个简化开发的小框架。
总之,我们学的就是Spring Framework, 也就是狭义上的Spring, 当然你也逃不过Spring全家桶。
Spring Framework到底有哪些功能,我们为啥要学它?
一句话概括:有些事,就不告诉你,因为框架已经帮我们做了我们以前的麻烦事。
功能模块 | 功能介绍 |
---|---|
Core Container | 核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器 |
AOP&Aspects | 面向切面编程 |
TX | 声明式事务管理 |
Spring MVC | 提供了面向Web应用程序的集成功能 |
这里只讲前三种。
SpringIoc是一个实现Ioc思想的Spring构建的一个复杂容器,为啥复杂呢,因为这玩意不仅能管理组件,还能储存组件,还能管理组件之间的依赖关系,并创建和销毁组件等。
Ioc是一种控制反转的思想,控制反转就是将对象创建、依赖管理和生命周期交给容器来管理,而不是交给程序员本身来管理。
就是实现不同功能,能够复用,可独立开发的代码单元。不只是java里面的类,只要满足前面的情况就行。
当你需要进行AOP,依赖注入,与其他Spring组件进行交互,管理生命周期的时候,建议放入SpringIoc容器,其他的看具体情况,不是所有的组件都要放进SpringIoc容器。
1.标记。
Spring IoC 有三种主要的配置方式,分别是 基于 XML 的配置、基于注解的配置 和 基于 Java 配置,推荐使用注解配置,在你需要装入SpringIoc容器的组件类上加以下注解:
@Component:标记一个类为 Spring 的 Bean
@Service:标记一个类为服务层的 Bean
@Controller:标记一个类为控制器层的 Bean
@Repository:标记一个类为数据访问层的 Bean
实际上他们都是Component,只是叫法不同而已
还没讲到的代码可以忽略,只要看懂怎么加入Ioc容器即可
冷知识:可以给Bean取别名id,记住是id
例如:@Component(“aBean”)
代码示例:
// 这是一个Dao层的实现类,加入了SpringIoc容器
@Repository
public class StudentDaoImpl implements StudentDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public List<StudentEntity> findAllStudents() {
String sql = "select * from students";
List<StudentEntity> allStudents = jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(StudentEntity.class));
return allStudents;
}
}
2.扫描。
当你声明了SpringIoc容器之后需要进行扫描,扫描需要加入到SpringIoc容器的组件,这里可以用xml和配置类的模式进行实现,我推荐使用配置类,以下是配置类的一些配置,连接的druid连接池。
@ComponentScan(basePackages = {“com.SpringZhuJieDemo02”})
代码示例:
// 1.标注配置类
@Configuration
// 2.引入配置文件
@PropertySource("classpath:jdbc.properties")
// 3.扫描包
@ComponentScan(basePackages = {"com.SpringZhuJieDemo02"})
public class config01 {
// 4.将数据库配置文件进行读入 返回DataSource值,自动加入SpringIoc容器中
@Bean
public DruidDataSource dataSource(
@Value("${firstdemo.url}") String url,
@Value("${firstdemo.driver}") String driverClassName,
@Value("${firstdemo.username}") String username,
@Value("${firstdemo.password}") String password
) {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(url);
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DruidDataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
}
3.构建。
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(config01.class);
根据配置文件生成Ioc的实现类
SpringIoc实现类以及接口的继承链:
BeanFactory(规定了ioc的基本动作,接口)
↓
ApplicationContext(做了一些扩展功能,接口)
↓
ClassPathXmlApplicationContext(配置文件是xml格式,读取在该项目路径下的配置文件,如resource)
FileSystemXmlApplicationContext(配置文件是xml格式,读取在该项目路径外的配置文件)
WebApplicationContext(Web项目对应的ioc容器)
AnnotationConfigApplicationContext(配置文件是java类格式,也就是配置类模式)
代码示例:
public class Test01 {
@Test
public void test01() {
// 读取配置类创建Ioc容器 注解配置
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(config01.class);
StudentControllerImpl studentController = context.getBean("studentControllerImpl", StudentControllerImpl.class);
List<StudentEntity> allStudents = studentController.findAllStudents();
for (StudentEntity studentEntity : allStudents) {
System.out.println(studentEntity);
}
}
}
4.补充。
4.1 @Configuration是标注配置类。
4.2 @PropertySource(“classpath:jdbc.properties”)是引入配置文件,我们这里假设我引入了外部的数据库的配置文件。
4.3 @ComponentScan(basePackages = {“com.SpringZhuJieDemo02”})是扫描包,将com包下的SpringZhuJieDemo02的包及其子包进行扫描。
4.4 @Bean 注解通常用在配置类的方法上,告诉 Spring 容器这个方法的返回值应该被注册为一个 Bean组件。
4.5 JdbcTemplate 是 Spring Framework 提供的一个 核心 JDBC 工具类。
4.6 jdbcTemplate.setDataSource(dataSource);是设置数据源 。
4.7 @Test注解支持单元测试,非常强大。
4.7 对于测试我们可以不用手动创建测试容器,太麻烦了,可以使用junit5的测试注解
4.8 @Autowired是自动注入,待会详细说明,可以理解为在这个测试类里面注入了StudentControllerImpl类,注入以后我们就可以使用它里面的方法了。
代码示例:
// 指定了配置类
@SpringJUnitConfig(value= {config01.class})
public class Test02 {
// 搭配了测试环境之后不用手动创建Ioc容器了
// 需要自动装配组件 利用注解
@Autowired
private StudentControllerImpl studentControllerImpl;
@Test
public void test02() {
List<StudentEntity> allStudents = studentControllerImpl.findAllStudents();
for (StudentEntity studentEntity : allStudents) {
System.out.println(studentEntity);
}
}
}
DI(依赖注入) 是一种设计模式,它允许我们将对象之间的依赖关系交给一个专门的容器(比如 Spring 容器)来管理,而不是在代码中手动创建和管理这些依赖关系。
可能有点抽象,比如我们手写Dao层与Service层的时候,Dao层里面的某个类需要被加入到Service层里的某个类的时候,我们使用手动构造方法进行初始化,然而这样的代码风格耦合性较高,所以我们要使用DI的设计模式
实现 DI(依赖注入) 主要有三种方式:字段注入、构造器注入 和 Setter 方法注入。这里推荐字段注入,通过注解进行自动装配。
注入分为两种,引用型注入和基本型注入。
@Autowired下面直接是需要注入的引用类
引用型注入:
代码示例:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDaoImpl userDaoImpl;
public UserDaoImpl getUserDao(UserDaoImpl userDaoImpl) {
return userDaoImpl;
}
}
基本型注入直接定义属性即可。
读取了配置文件@PropertySource(“classpath:jdbc.properties”)之后,
进行注入,这样的操作主要用于读取配置文件@Value("${firstdemo.url}")。
代码示例:
// 1.标注配置类
@Configuration
// 2.引入配置文件
@PropertySource("classpath:jdbc.properties")
// 3.扫描包
@ComponentScan(basePackages = {"com.SpringZhuJieDemo02"})
public class config01 {
// 4.将数据库配置文件进行读入 返回DataSource值,自动加入SpringIoc容器中
@Bean
public DruidDataSource dataSource(
@Value("${firstdemo.url}") String url,
@Value("${firstdemo.driver}") String driverClassName,
@Value("${firstdemo.username}") String username,
@Value("${firstdemo.password}") String password
) {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(url);
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DruidDataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
}
注意:参与自动装配的组件(需要装配、被装配)全部都必须在IoC容器中。
1.什么是组件的生命周期:
组件的生命周期是一个 Bean 从创建到销毁的整个过程。
2.SpringIoc如何管理组件的生命周期:
这里还是推荐注解方式进行管理:
@PostConstruct 指定初始化方法
@PreDestroy 指定销毁方法
3.当调用util的时候,就会调用初始化方法,只有当Ioc容器被正常销毁了才会执行销毁方法。
代码示例:
@Component("util")
public class Util {
@PostConstruct
public void init() {
System.out.println("Util init");
}
@PreDestroy
public void destroy() {
System.out.println("Util destroy");
}
@Test
public void test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("springconfigs.xml");
Util util = context.getBean("util", Util.class);
context.close();
}
}
1.什么是组件的作用域:
SpringIoc容器可以根据指定的bean组件对象进行反射创建多个Bean实例对象,基于你需求几个,就由Scope作用域指定。
2.如何实现组件的作用域:
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON) //单例
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) //多例
代码示例:
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON) //单例,默认值
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) //多例
public class aBean {
}
Spring AOP框架,基于AOP编程思维,封装动态代理技术,简化动态代理技术实现的框架,SpringAOP内部帮助我们实现动态代理。
AOP,面向切面编程,是一种编程范式。旨在通过分离横切关注点来提高代码的模块化,目的是将横切关注点(如日志、事务、权限等分散在多个模块中的公共功能)从核心业务逻辑中分离出来,通过“切面”模块化,避免代码重复和耦合。
剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为Aspect切面。所谓切面,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来的代码块。
在AOP中,我们可以将公共对核心没关联的操作提取出来,作为一个横切面,将这些横切封装起来,从而降低了代码的耦合度
4.常见的AOP应用场景:
1. 日志记录:在系统中记录日志是非常重要的,可以使用AOP来实现日志记录的功能,可以在方法执行前、执行后或异常抛出时记录日志。
2. 事务处理:在数据库操作中使用事务可以保证数据的一致性,可以使用AOP来实现事务处理的功能,可以在方法开始前开启事务,在方法执行完毕后提交或回滚事务。
3. 安全控制:在系统中包含某些需要安全控制的操作,如登录、修改密码、授权等,可以使用AOP来实现安全控制的功能。
可以在方法执行前进行权限判断,如果用户没有权限,则抛出异常或转向到错误页面,以防止未经授权的访问。
4. 性能监控:在系统运行过程中,有时需要对某些方法的性能进行监控,以找到系统的瓶颈并进行优化。
可以使用AOP来实现性能监控的功能,可以在方法执行前记录时间戳,在方法执行完毕后计算方法执行时间并输出到日志中。
5. 异常处理:系统中可能出现各种异常情况,如空指针异常、数据库连接异常等,
可以使用AOP来实现异常处理的功能,在方法执行过程中,如果出现异常,则进行异常处理(如记录日志、发送邮件等)。
6. 缓存控制:在系统中有些数据可以缓存起来以提高访问速度,可以使用AOP来实现缓存控制的功能,
可以在方法执行前查询缓存中是否有数据,如果有则返回,否则执行方法并将方法返回值存入缓存中。
7. 动态代理:AOP的实现方式之一是通过动态代理,可以代理某个类的所有方法,用于实现各种功能。
就像一个万能代办公司,你别管他干了啥,他代理你去办了你的事,比你自己办轻松多了,而且他给你做了很多活。这里的SpringAOP就是用了动态代理机制进行实现AOP。
Spring利用动态代理机制实现的,Spring AOP 主要通过两种动态代理技术实现:
1.JDK 动态代理(基于接口):
默认选择,当目标类实现了至少一个接口时使用
运行时动态创建接口的实现类
2.CGLIB 代理(基于子类继承):
当目标类没有实现接口时使用
通过生成目标类的子类来实现代理
显而易见,面向接口编程是我们所追寻的。
分析一下,我们要切入一个程序,让核心功能(核心关注点)与非核心的通用的代码(横切关注点)分离开来,你要切谁(目标,被代理的目标),它的哪里(切入点),横切关注点的各个位置要干啥?(通知,也叫增强),切下来的切入点和通知构成的类(切面),连接点是程序运行中所有可能被切面拦截的点,代理是向目标对象应用通知之后创建的代理对象,织入指把通知应用到目标上,生成代理对象的过程。可以在编译时织入,也可以在运行时织入,Spring采用后者。
1.配置。@EnableAspectJAutoProxy 在配置类中启动允许自动动态代理。
代码示例:
@Configuration
@ComponentScan(basePackages = "com.aop01")
//作用等于 配置类上开启 Aspectj注解支持!
@EnableAspectJAutoProxy
public class config01 {
}
2.切面。@Aspect表示这个类是一个切面类。切面=通知+切入点
@Before 前置,在切入点前干啥。
@AfterReturning 后置,在切入点后面干啥,可以获得结果。 returning = "result"属性。
@AfterThrowing 异常,在切入点报错干啥,可以抛出异常。throwing = “throwException”
@After 最后,在切入点最后干啥,无论有无异常。
@Around 环绕,一个人代表上面所有。
代码示例:
// @Component注解保证这个切面类能够放入IOC容器
@Component
// @Aspect表示这个类是一个切面类
@Aspect
// 切面也是有优先级的使用
// @Order() 注解进行配置 越小的值优先级越高
@Order(1)
public class LogAspect {
// @Before注解:声明当前方法是前置通知方法
// value属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上
@Before(value = "com.aop01.pointcut.pointcut01.pointcut01()")
public void printLogBeforeCore(JoinPoint joinPoint) {
System.out.println("--------------------------------------");
System.out.println("[AOP前置通知] 方法开始了");
// 1.通过JoinPoint对象获取目标方法签名对象
Signature signature = joinPoint.getSignature();
System.out.println("方法的名称(包括具体包地址): " + signature.getDeclaringTypeName() + "." + signature.getName());
System.out.println("方法的名称: " + signature.getName());
System.out.println("modifiers: " + signature.getModifiers());
System.out.println("声明方法的接口: " + signature.getDeclaringTypeName());
// 2.通过JoinPoint对象获取外界调用目标方法时传入的实参列表
// 3.modifiers
// 在 AOP 中,常用于检查目标方法的访问权限或特性
// 在反射中,用于动态分析类成员
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
System.out.println("--------------------------------------");
}
@AfterReturning(
value = "com.aop01.pointcut.pointcut01.pointcut01()",
returning = "result"
)
public void printLogAfterSuccess(JoinPoint joinPoint, Object result) {
System.out.println("--------------------------------------");
System.out.println("[AOP返回通知] 方法成功返回了");
// result用来接收返回值
System.out.println("返回值是:" + result);
System.out.println("--------------------------------------");
}
@AfterThrowing(
value = "com.aop01.pointcut.pointcut01.pointcut01()",
throwing = "throwException"
)
public void printLogAfterException(JoinPoint joinPoint, Throwable throwException) {
System.out.println("--------------------------------------");
System.out.println("[AOP异常通知] 方法抛异常了");
System.out.println("异常类型是:" + throwException.getClass().getName());
System.out.println("--------------------------------------");
}
@After(value = "com.aop01.pointcut.pointcut01.pointcut01()")
public void printLogFinallyEnd() {
System.out.println("[AOP后置通知] 方法最终结束了");
}
}
4.切入点。通知里面的value值是切入点表达式,目的是指定要切入的方法。
@Pointcut(“execution(public int com…CalculatorImpl.*(int,int))”)
为了方便管理我们统一将切入点表达式提取出来便于管理,封装成一个类。
还有很多切入点表达式的细节,我本人用来总结就是,通配符用…双引号,最后要指定具体方法,也可以指定形参,视需求而定。但是一定要包含execution()的格式。
代码示例:
// 将切点表达式提取出来 便于引用
public class pointcut01 {
@Pointcut("execution(public int com..CalculatorImpl.*(int,int))")
public void pointcut01() {}
}
目标代码示例:
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
注意:我们使用的是JDK原生的动态代理,也就是说我们必须要实现至少一个接口,而且是实现AOP的是代理类哦,不是我们创建的类。
注意看:
代码示例:
@SpringJUnitConfig(value = {config01.class})
public class Test01 {
// @Qualifier(value = "userServiceImpl")
// private UserService userService; 为什么不能用private CalculatorImpl calculatorImpl;
// 使用@Autowired进行依赖注入时,最佳实践是针对接口类型(如Calculator)而不是具体实现类(如CalculatorImpl)进行注入
// 这是依赖倒置原则(Dependency Inversion Principle)的体现
// 且符合面向接口编程
// 适用于大多数情况,特别是使用了 Spring AOP 时 因为我们使用了SpringAOP框架 将Calculator增强了
// 默认情况下,Spring AOP 优先使用 JDK 动态代理(基于接口)
// 如果目标类(CalculatorImpl)实现了接口(Calculator),Spring 会创建 Calculator 接口的代理对象,而不是 CalculatorImpl 的代理对象
// 如果没Calculator没有实现接口,就执行cglib进行代理 就可以改成 private CalculatorImpl calculatorImpl;
@Autowired
private Calculator calculator;
@Test
public void test01(){
calculator.add(1,1);
calculator.mul(2,2);
}
}
注入的是接口,符合面向接口编程,如果你这个时候写的是接口的实现类,是会报错的哦,这是个非常经典的问题,因为我们使用的是代理类哦!!
SpringTX是SpringFramework提供的事务管理模块,它简化了对于数据库的事务的管理。支持声明式和编程式事务管理。
因为在进行对数据库DML操作的时候,有可能会发生错误,为了 方便管理这种情况,我们让SpringTX来帮助我们管理事务。
有两种方式,声明式事务与编程式事务,推荐使用声明式事务,即使用注解的方式进行实现。
1.装配事务管理器。
准许开启事务管理@EnableTransactionManagement并装配事务管理组件。
代码示例:
// 1.标注配置类
@Configuration
// 2.引入配置文件
@PropertySource("classpath:jdbc.properties")
// 3.扫描包
@ComponentScan(basePackages = {"com.SpringTXdemo01"})
// 4.准许开启事务管理
@EnableTransactionManagement
public class config01 {
// 4.将数据库配置文件进行读入 返回DataSource值,自动假如SpringIoc容器中
@Bean
public DruidDataSource dataSource(
@Value("${firstdemo.url}") String url,
@Value("${firstdemo.driver}") String driverClassName,
@Value("${firstdemo.username}") String username,
@Value("${firstdemo.password}") String password
) {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(url);
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DruidDataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 装配事务管理实现对象 为什么要传入数据源就是因为我们是基于连接池进行的 所以也要给管理事务一份
@Bean
public TransactionManager transactionTemplate(DruidDataSource druidDataSource){
return new DataSourceTransactionManager(druidDataSource);
}
}
2.标记事务。
@Transactional
readOnly = true把当前事务设置为只读 默认是false,可以写在类上。
timeout设置超时时间,单位为秒。
rollbackFor是设置事务回滚,默认值是运行时异常。
noRollbackFor是设置不回滚的异常。应该在rollbackFor异常之内。
isolation是设置事务的隔离级别。
propagation是设置事务的传播属性。比如Service层包含了Dao层的几个操作,将Service层设置事务,两个Dao层的操作就可以设置传播属性,默认是
REQUIRED,如果父方法有事务,就加入,如果没有就新建自己独立。
REQUIRES_NEW,不管父方法是否有事务,我都新建事务,都是独立的。
假如B和C是A的调用方法,其中A开启了事务,我使用了默认值,那么如果A或B有一个出了指定的运行时异常,那么A和B全部回滚。
事务隔离级别:
脏读:你看到别人正在打字但还没保存的内容(可能被撤回)
不可重复读:你两次刷新页面,发现同一行数据被改了
幻读:你查询时突然多出原本不存在的行(别人刚插入的)
隔离级别就是设定"你能看到别人哪些未提交或已提交的修改"的规则。
事务隔离级别参数:
1.读未提交(READ_UNCOMMITTED):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。
2.读已提交(READ_COMMITTED):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。
3.可重复读(REPEATABLE_RAED):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。
4.串行化(SERIALIZABLE):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。
代码示例:
@Repository
public class StudentDaoImpl implements StudentDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public List<StudentEntity> findAllStudents() {
String sql = "select * from students";
return jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(StudentEntity.class));
}
// new BeanPropertyRowMapper<>(StudentEntity.class) 是 Spring JDBC 的“自动化映射工具”,省去手动拼装对象的麻烦
// 本质是通过反射将数据库字段映射到 JavaBean 属性
// 适合快速开发,但需确保字段名和属性名匹配
@Override
// 开启事务 表示5000秒后抛出超时异常 回滚Exception的异常 除了FileNotFoundException的异常 隔离级别是读已提交
// 事务隔离级别是为了放置高并发的死锁与竞争
@Transactional(
timeout=5000,
rollbackFor=Exception.class,
noRollbackFor= FileNotFoundException.class,
isolation = Isolation.READ_COMMITTED
)
public Integer updateNameById(String name,Integer id) {
String sql = "update students set name = ? where id = ?";
return jdbcTemplate.update(sql,name,id);
}
// name和id用于替换前面的占位符
}
IoC容器管理所有Bean(包括AOP代理和事务管理器)
AOP代理包装目标对象,实现横切逻辑(如@Transactional的拦截)
TX模块通过AOP代理将事务逻辑织入目标方法