spring部分
spring 系统架构
核心容器是spring中最核心的模块,其他模块都是依赖他运行的
核心概念
IOC:从程序直接new对象,变成了由外部提供对象。spring使用IOC容器充当外部,实现了IOC思想
- IOC最终目的充分解耦
IOC入门案例
- maven标准项目结构解释
Application接口是IOC容器的一个实现,为我们使用IOC容器提供了方便 - 1.在pom.xml中引入spring的jar包
<dependencies>
<dependency>
<groupId>org.springframework</groupId><!--隶属的组织-->
<artifactId>spring-context</artifactId><!--依赖项-->
<version>5.2.10.RELEASE</version><!--版本-->
</dependency>
</dependencies>
- 2.在配置文件中配置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">
<!--spring的配置文件-->
<!--BookDaoImpl-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"></bean>
<!--BookServiceImpl-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"></bean>
</beans>
- dao
package com.itheima.dao;
public interface BookDao {
public void save();
}
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
- 测试
public class Test1 {
public static void main(String[] args) {
//1.获取IOC容器(加载配置文件)
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
//2.获取Bean对象
BookDao bookDao= (BookDaoImpl)applicationContext.getBean("bookDao");
bookDao.save();
}
}
在加载配置文件的时候bean就已经被创建
DI(注入属性)入门案例
bean基础配置
bean的别名配置
id属性认为是bean的标识符(不写默认是类名首字母小写)(编号,且唯一)name属性是给bean起别名,可以有多个。不管是唯一的id还是多个别名都对应着同一个bean
bean的作用范围
- 总结
bean的实例化---构造方法实例化
- 默认是调用我们的无参构造方法实例化的
对于spring报错信息的阅读
- 给构造加上参数
- 此时没有找到需要的构造,所以报错
静态工厂实例化(注意工厂方法是静态的)
(不需要创建对象,直接调用静态方法即可)
使用静态工厂实例化bean,在一些早期的项目中使用的比较多
- 工厂类
package com.itheima.dao.impl;
public class BookDaoFactory {
public static BookDaoImpl getInstance() {
return new BookDaoImpl();
}
}
实例工厂创建bean
因为是实例工厂,所以我们必须先创建工厂的对象。
我们创建的工厂对象,好像仅仅是是为我们创建对象服务的,能不能优化一下简化这个程序
package com.itheima.dao;
public interface UserDao {
void serve();
}
package com.itheima.dao.impl;
import com.itheima.dao.UserDao;
public class UserDaoImpl implements UserDao {
@Override
public void serve() {
System.out.println("serve ...");
}
}
- factoryBean类
package com.itheima.dao.impl;
import com.itheima.dao.UserDao;
import org.springframework.beans.factory.FactoryBean;
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//创建对象
//替代工厂中创建对象的方法
@Override
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
@Override
public Class<?> getObjectType() {
return UserDao.class;
}
}
bean的生命周期
setter注入
分为注入引用类型和注入普通类型
构造器注入
- 写法1
- 重点
setter和构造器相比,setter注入更自由,可以不注入全部的属性
自动装配
按名称装配:属性名和bean的id名相同及匹配
以后一般使用按类型匹配
1.建议使用按名称装配
2.自动装配在底层使用的还是setter方法
3.手动装配和自动装配不用同时使用
集合注入
1,array和list标签可以混用
在以后的开发中集合的注入使用的很少
配置管理第三方bean
https://mvnrepository.com/这个网站可以查找maven资源对应的资源坐标
对于一个陌生的类的管理,需要我们去探索该类的组成结构。setter方法,构造方法的情况
- druid和c3po的管理
<!--德鲁伊连接池的创建-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="username" value="swt"></property>
<property name="password" value="123456"></property>
<property name="url" value="jdbc:mysql://database"></property>
</bean>
<!--配置c3p0数据库连接池-->
<bean id="a" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="swt"></property>
<property name="password" value="123456"></property>
<property name="jdbcUrl" value="jdbc:mysql://database"></property>
</bean>
加载Propertirs配置文件
一个现象:我们在加载过程中除了会加载properties文件中我们自己写的属性外,也会加载一些系统属性。而且这些系统属性的属性名可能和我们的属性名一样(如:就存在系统属性名username)。而且会优先使用系统属性
容器
beanFactory是容器的最顶层接口,也可以完成响应操作,但是有局限性
- 使用beanFactory
核心容器的总结
注解开发定义bean
纯注解开发
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration//表明是配置类
@ComponentScan(basePackages = "com.itheima")//扫描组件
public class SpringConfig {
}
使用注解管理bean的作用范围和bean的生命周期
uploading-image-48888.png
注解开发的依赖注入
注解注入引用类型
注意:注解注入普通类型
-1.加载配置文件
-2.使用注解给普通属性注入配置文件中的值
PropertySource注解的值也可以以数组的形式写多个,但是不能使用*
注解开发管理第三方的bean
- 以管理德鲁伊数据库连接池为例子
我们springconfig是spring的配置文件,而使用注解管理连接池应该写在jdbc专门的配置类中 - jdbc配置类
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
public class JdbcConfig {
@Bean//将返回的bean交给spring管理
public DataSource getDruidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setPassword("123456");
druidDataSource.setUsername("sss");
druidDataSource.setUrl("jdbc");
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
return druidDataSource;
}
}
- spring配置类(在spring配置类中导入jdbc配置类)
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.*;
@Configuration//表明是配置类
@ComponentScan(basePackages = "com.itheima")//扫描组件
@Import({JdbcConfig.class})//导入jdbc配置类(这种方式可以清楚的知道导入的是哪个配置类)
public class SpringConfig {
}
为第三方bean注入资源
package com.itheima.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.itheima.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
## 注解开发总结
![](/i/l/?n=23&i=blog/2942946/202312/2942946-20231204212308271-1614110233.png)
- 标红为使用频率高的
![](/i/l/?n=23&i=blog/2942946/202312/2942946-20231204212730060-1879977177.png)
public class JdbcConfig {
/*第三方bean普通类型的注入*/
@Value("sss")
private String userName;
@Value("123456")
private String password;
@Value("jdbc")
private String url;
@Value("com.mysql.jdbc.Driver")
private String driverClassName;
@Bean//将返回的bean交给spring管理
public DataSource getDruidDataSource(BookDao bookDao) {//假如第三方bean需要注入UserDao对象
System.out.println(bookDao);//成功注入了BookDao对象
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setPassword(userName);
druidDataSource.setUsername(password);
druidDataSource.setUrl(url);
druidDataSource.setDriverClassName(driverClassName);
return druidDataSource;
}
}
spring整合mybatis暂时省略
AOP
切入点理解为一个式子,这个式子可以匹配多个方法
AOP的入门案例
对应AOP的理解
AOP即面向切面编程,主要思想是代理模式的应用。虽然AOP使用的是动态代理,但是动态代理在本质上原理和和普通的代理相同,所以1.在理解AOP时使用简单代理思考即可
2.其中使用注解来实现AOP比较抽象,可以这样理解。切面表示的是通知方法和增强方法间的映射关系,他们之间是绑定的,这不就是在普通的代理模式中,在代理类中的需求方法中调用增强方法吗
3.我们的切入点方法指定为dao中的方法和dao实现类中的方法都可以,因为无论是指定接口中的方法还是实现类中的方法,后面都会调用实现类中的方法作为切入点方法,和增强方法一起完成方法的增强操作
AOP的工作流程
切入点表达式
这样写的话过于繁琐了
- 书写技巧
AOP通知类型
对于环绕通知的一个注意点
- dao
public interface BookDao {
public int save();
}
public int save() {
System.out.println("book dao save ...");
return 100;
}
- 环绕通知
//环绕通知方法
@Around("pointcut()")
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知前");
//调用方式方法
pjp.proceed();
System.out.println("环绕通知后");
}
这个应该很好理解,save方法本来应该返回一个int,返回被代理之后返回的是一个void,会原始方法不符合,我们应该在通知中将原始方法的返回值进行返回
案例业务层接口执行效率
/切面类
@Component//将增强类交给spring管理(制定了切入点方法和通知的对应关系)
@Aspect
public class MyAdvice {
//定义切入点
@Pointcut("execution(* com.itheima..save(..))")
public void pointcut(){}
//环绕通知方法
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//获取切入点签名
final Signature signature = pjp.getSignature();
final Class declaringType = signature.getDeclaringType();//获取类型
final String name = signature.getName();//获取切入点的方法名
final long start = System.currentTimeMillis();
//调用方式方法
for (int i = 0; i < 10000; i++) {
pjp.proceed();
}
final long end = System.currentTimeMillis();
System.out.println(declaringType+name+"万次执行的时间是"+(end-start)+"毫秒");
return 100;
}
}
AOP通知获取数据
我们现在已经处理切入点方法了,但是并不是所有的情况都需要处理,或者说不同的参数我们的处理方案不同。所有我们必须可以在通知中获取原始操作的数据
- 注意点1
ProceedingJoinPoint
接口是JoinPoint
接口的子接口
- 注意点2
- 获取切入点运行时异常
百度网盘密码数据兼容处理
这题的关键点在于我们需要获取原始方法中的密码对他进行处理(去除密码中的空格),所以我们必须使用环绕通知
- 切面类
@Around("pointcut()")
public void hh(ProceedingJoinPoint pjp) throws Throwable {
//1.获取原始方法的参数
final Object[] args = pjp.getArgs();
//对参数进行去除空格处理
for (int i = 0; i < args.length; i++) {
if(args[i] instanceof String){
args[i]=((String)args[i]).trim();
}
}
//调用原始方法
pjp.proceed(args);//将修改后的参数交给原始方法执行
}
- 测试
public class App2 {
public static void main(String[] args) {
//3.获取IoC容器
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
//4.获取Bean
BookDao bookDao = ctx.getBean(BookDao.class);
bookDao.openURL("hello ","123 ");
}
AOP总结
切入点表达式对接口描述会让我们的程序耦合度更低
环绕通知可以模拟出其他四种通知
spring事务简介
事务的作用:保障事务层操作的一致性
事务角色
spring事务属性
事务并不是遇到异常就回滚,也就是事务并不是所有异常都会回滚
我们的程序只有在遇到运行时异常和Error才会归滚,遇到非运行时异常不会进行回滚,如IOException.我们可以通过属性来设置回滚的异常
rollBackFor可以配置哪些异常进行回滚(一般是配置默认不回滚的异常)
我们需要在程序中加入一个业务进行日志记载
- 日志处理类
package com.aiheima.dao;
import org.apache.ibatis.annotations.Insert;
public interface LogDao {
@Insert("insert into log(id,info,crateDate) values(1,#{info},now())")
public void log(String info);
}
- service
package com.aiheima.service;
import com.aiheima.dao.LogDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class LogService {
@Autowired
private LogDao logDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String out,String in ,double money){
logDao.log("转账由"+out+"转入"+in+"金额:"+money);
}
}
但是当我们转账方法出现异常的后,这个日志方法并没有被执行,这个方法和其他的方法一起都进行了回滚。用户表和日志表都没有改变
- 事务传播行为
注意这个视频的事务的隔离级别没有将,这尚硅谷的视频里面有讲到事务的隔离级别
这就涉及到了事务的传播行为,上面这种情况的发生是因为此时日志的传播行为为默认,此时这个servcie中的转账方法开启了事务,他调用的方法也开启了事务,但是他们会加入到转账方法的事务中,所以他们会一起执行事务的回滚,而此时我们应该让日志的操作的事务独立出来,不受其他方法的影响,改变这个日志方法的事务传播行为就可以了
此时用户表的金额没有改变,但是日志表中有了记录