笔记参考于柏码知识库
loC容器基础
简单使用
新建Maven项目, 导入Spring框架依赖:
1 2 3 4 5
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.13</version> </dependency>
|
在在resource中创建一个Spring配置文件,命名为test.xml:
1 2 3 4 5 6 7
| <?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 https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
|
主方法获取loC容器:
1 2 3 4
| public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("text"); }
|
想loC容器中注册JavaBean:
1 2 3 4 5
| public class Student { String name; int age; }
|
配置文件中添加(name或id不可重复):
1
| <bean name="student" class="com.test.bean.Student"/>
|
通过loC容器获取:
1 2 3 4 5
| public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test.xml"); Student student = (Student) context.getBean("student"); System.out.println(student); }
|
默认情况下,loC容器进行管理的JavaBean是单例模式,在一开始Bean就会被创建。
可以添加scope属性修改作用域。prototype(原型模式)来使得其每次都会创建一个新的对象,在获取时才被创建。
1
| <bean name="student" class="com.test.bean.Student" scope="prototype"/>
|
生命周期
还可以配置初始化方法以及销毁方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Student { String name; int age;
private void init(){ System.out.println("我是初始化方法!"); }
private void destroy(){ System.out.println("我是销毁方法!"); }
public Student(){ System.out.println("我被构造了!"); } }
|
1
| <bean name="student" class="com.test.bean.Student" init-method="init" destroy-method="destroy"/>
|
1 2 3 4 5 6
| public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test.xml"); Student student = (Student) context.getBean("student"); System.out.println(student); context.close(); }
|
还可以使用depend-on属性指定Bean的加载顺序。
依赖注入
可以使用property标签预定属性,对应属性必须存在set方法:
1 2 3
| <bean name="student" class="com.test.bean.Student"> <property name="name" value="小明"/> </bean>
|
对于对象,也将其对应的类型注册为bean即可,使用ref属性引用。
1 2 3 4 5
| <bean name="card" class="com.test.bean.Card"/> <bean name="student" class="com.test.bean.Student"> <property name="name" value="小明"/> <property name="card" ref="card"/> </bean>
|
集合使用list-value标签编写:
1 2 3 4 5 6 7 8 9
| <bean name="student" class="com.test.bean.Student"> <property name="list"> <list> <value type="double">100.0</value> <value type="double">95.0</value> <value type="double">92.5</value> </list> </property> </bean>
|
Map类型使用map-entry标签:
1 2 3 4 5 6 7 8 9
| <bean name="student" class="com.test.bean.Student"> <property name="map"> <map> <entry key="语文" value="100.0"/> <entry key="数学" value="80.0"/> <entry key="英语" value="92.5"/> </map> </property> </bean>
|
自动装配
还可以自动装配:
1 2
| <bean name="card" class="com.test.bean.Card"/> <bean name="student" class="com.test.bean.Student" autowire="byType"/>
|
使用有参构造方法:
1 2 3 4
| <bean name="student" class="com.test.bean.Student"> <constructor-arg name="name" value="小明"/> <constructor-arg index="1" value="18"/> </bean>
|
使用注解
主要配置文件使用@Configuration注解
1 2 3 4
| @Configuration public class MainConfiguration { }
|
配置Bean使用@Bean注解。
1 2 3 4 5 6 7 8
| @Configuration public class MainConfiguration {
@Bean("student") public Student student(){ return new Student(); } }
|
可以在@Bean中配置初始化方法、摧毁方法、自动装配等:
1 2 3 4
| @Bean(name = "", initMethod = "", destroyMethod = "", autowireCandidate = false) public Student student(){ return new Student(); }
|
对于依赖注入的Bean:
1 2 3 4 5 6 7 8 9 10 11 12
| @Configuration public class MainConfiguration { @Bean public Teacher teacher(){ return new Teacher(); }
@Bean public Student student(Teacher teacher){ return new Student(teacher); } }
|
也可以到Bean中对应类使用自动装配:
1 2 3 4
| public class Student { @Autowired private Teacher teacher; }
|
若有多个相同类型的Bean,可以配合@Qualifier进行匹配:
1 2 3 4 5
| public class Student { @Autowired @Qualifier("a") private Teacher teacher; }
|
官方更推荐使用@Resource注解进行注入,区别为:
- @Resource默认ByName如果找不到则ByType,可以添加到set方法、字段上。
- @Autowired默认是byType,只会根据类型寻找,可以添加在构造方法、set方法、字段、方法参数上。
使用@Resource需要额外导入包:
1 2 3 4 5
| <dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <version>2.1.1</version> </dependency>
|
还有@PostConstruct和@PreDestroy,它们效果和init-method和destroy-method是一样的:
1 2 3 4 5 6 7 8 9
| @PostConstruct public void init(){ System.out.println("我是初始化方法"); }
@PreDestroy public void destroy(){ System.out.println("我是销毁方法"); }
|
对于自己编写的类,可以使用@Component注解自动扫描并注册:
1 2 3 4
| @Component("lbwnb") public class Student {
}
|
1 2 3 4 5
| @Configuration @ComponentScan("com.test.bean") public class MainConfiguration {
}
|
对于这种使用@Component注册的Bean,如果其构造方法不是默认无参构造,那么默认会对其每一个参数都进行自动注入。
面向切面AOP
SpringAOP
可以使用AOP在方法执行前后进行一些额外操作,而不修改方法源码。
首先导入依赖:
1 2 3 4 5
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.13</version> </dependency>
|
定义切入方法:
1 2 3 4 5 6 7 8 9 10 11 12
| public class AopTest {
public void after(){ System.out.println("我是执行之后"); }
public void before(){ System.out.println("我是执行之前"); } }
|
注册bean:
1 2
| <bean name="student" class="com.test.bean.Student"/> <bean name="aopTest" class="com.test.aop.AopTest"/>
|
引入aop标签:
1 2 3 4 5 6
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
|
添加AOP配置:
1 2 3 4 5 6 7
| <aop:config> <aop:pointcut id="test" expression="execution(* com.test.bean.Student.test(String))"/> <aop:aspect ref="aopTest"> <aop:before method="before" pointcut-ref="test"/> <aop:after-returning method="after" pointcut-ref="test"/> </aop:aspect> </aop:config>
|
接口AOP
通过Advice实现AOP。
方法前操作继承MethodBeforeAdvice实现:
1 2 3 4 5 6
| public class AopTest implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("通过Advice实现AOP"); } }
|
其中args代表的是方法执行前得到的实参列表,还有target表示执行此方法的实例对象。
AOP配置文件:
1 2 3 4
| <aop:config> <aop:pointcut id="stu" expression="execution(* com.test.bean.Student.say(String))"/> <aop:advisor advice-ref="before" pointcut-ref="stu"/> </aop:config>
|
添加方法执行后:
1 2 3 4 5 6 7 8 9 10 11
| public class AopTest implements MethodBeforeAdvice, AfterReturningAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("我是方法执行之前!"); }
@Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("我是方法执行之后!"); } }
|
使用MethodInterceptor(同样也是Advice的子接口)进行更加环绕那样的自定义的增强:
1 2 3 4 5 6 7
| public class StudentAOP implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object value = invocation.proceed(); return value+"增强"; } }
|
注解AOP
添加@EnableAspectJAutoProxy注解,开启AOP注解支持:
1 2 3 4 5
| @EnableAspectJAutoProxy @ComponentScan("org.example.entity") @Configuration public class MainConfiguration { }
|
注册Bean:
1 2 3 4 5 6
| @Component public class Student { public void study(){ System.out.println("我是学习方法!"); } }
|
定义AOP增强操作的类,添加@Aspect注解和@Component将其注册为Bean, 定义方法并在方法前添加注解:
1 2 3 4 5 6 7 8
| @Aspect @Component public class StudentAOP { @Before("execution(* org.example.entity.Student.study())") public void before(){ System.out.println("我是之前执行的内容!"); } }
|
添加JoinPoint参数获取切入点信息:
1 2 3 4 5
| @Before("execution(* org.example.entity.Student.study())") public void before(JoinPoint point){ System.out.println("参数列表:"+ Arrays.toString(point.getArgs())); System.out.println("我是之前执行的内容!"); }
|
使用命名绑定模式,可以快速得到原方法的参数:
1 2 3 4 5 6 7
| @Before(value = "execution(* org.example.entity.Student.study(..)) && args(str)", argNames = "str")
public void before(String str){ System.out.println(str); System.out.println("我是之前执行的内容!"); }
|
还有@AfterReturning、@AfterThrowing等注解。
环绕注解:
1 2 3 4 5 6 7
| @Around("execution(* com.test.bean.Student.test(..))") public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("方法执行之前!"); Object val = point.proceed(); System.out.println("方法执行之后!"); return val; }
|
Spring高级特性
Bean Aware
在Bean加载前获取相关信息。
任务调度
异步任务
首先开启异步任务支持,添加@EnableAsync注解。
1 2 3 4 5
| @EnableAsync @Configuration @ComponentScan("com.test.bean") public class MainConfiguration { }
|
在需要异步执行的方法上添加@Async注解,当此方法被调用时,会异步执行,也就是新开一个线程执行,而不是在当前线程执行。添加此注解要求方法的返回值只能是void或是Future类型才可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Component public class Student { public void syncTest() throws InterruptedException { System.out.println(Thread.currentThread().getName()+"我是同步执行的方法,开始..."); Thread.sleep(3000); System.out.println("我是同步执行的方法,结束!"); }
@Async public void asyncTest() throws InterruptedException { System.out.println(Thread.currentThread().getName()+"我是异步执行的方法,开始..."); Thread.sleep(3000); System.out.println("我是异步执行的方法,结束!"); } }
|
定时任务
在配置类上添加@EnableScheduling注解:
1 2 3 4 5
| @EnableScheduling @Configuration @ComponentScan("com.test.bean") public class MainConfiguration { }
|
添加@Scheduled注解:
1 2 3 4
| @Scheduled(fixedRate = 2000) public void task(){ System.out.println("我是定时任务!"+new Date()); }
|
@Scheduled中有很多参数,需要指定’cron’, ‘fixedDelay(String)’, or ‘fixedRate(String)’的其中一个:
- ixedDelay:在上一次定时任务执行完之后,间隔多久继续执行。
- fixedRate:无论上一次定时任务有没有执行完成,两次任务之间的时间间隔。
- cron:还可以使用cron表达式来指定任务计划。
监听器
创建监听器:
1 2 3 4 5 6 7
| @Component public class TestListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println(event.getApplicationContext()); } }
|
自定义监听事件:
1 2 3 4 5
| public class TestEvent extends ApplicationEvent { public TestEvent(Object source) { super(source); } }
|
SpringEL表达式
SpEL 是一种强大,简洁的装配 Bean 的方式,它可以通过运行期间执行的表达式将值装配到我们的属性或构造函数当中,更可以调用 JDK 中提供的静态常量,获取外部 Properties 文件中的的配置。
外部属性注入
在resource目录下创建以test.properties结尾的配置文件:
使用@PropertySource引入:
1 2 3 4 5 6
| @Configuration @ComponentScan("com.test.bean") @PropertySource("classpath:test.properties") public class MainConfiguration{ }
|
使用@Value注解注入:
1 2 3 4 5 6 7 8 9
| @Component public class Student { @Value("${test.name}") private String name;
public void hello(){ System.out.println("我的名字是:"+name); } }
|
也可以注入方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Component public class Student { private final String name;
public Student(@Value("${test.name}") String name){ this.name = name; }
public void hello(){ System.out.println("我的名字是:"+name); } }
|
也可以注入常量值:
1 2 3 4
| private final String name; public Student(@Value("10") String name){ this.name = name; }
|
SpEL简单使用
创建SpEL表达式,此处得到一个简单的字符串:
1 2 3
| ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello World'"); System.out.println(exp.getValue());
|
还可以调用字符串方法:
1 2
| Expression exp = parser.parseExpression("'Hello World'.toUpperCase()"); System.out.println(exp.getValue());
|
可以像访问属性一样使用Getter方法:
1 2 3
| Expression exp = parser.parseExpression("'Hello World'.bytes"); System.out.println(exp.getValue());
|
还可以多级调用:
1 2
| Expression exp = parser.parseExpression("'Hello World'.bytes.length"); System.out.println(exp.getValue());
|
可以支持根据特定表达式,从给定对象中获取属性出来:
1 2 3 4 5 6 7 8 9 10 11
| @Component public class Student { private final String name; public Student(@Value("${test.name}") String name){ this.name = name; }
public String getName() { return name; } }
|
1 2 3 4
| Student student = context.getBean(Student.class); ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("name"); System.out.println(exp.getValue(student));
|
还可以进一步处理对象属性,或设置属性值:
1
| Expression exp = parser.parseExpression("name.bytes.length");
|
1 2
| Expression exp = parser.parseExpression("name"); exp.setValue(student, "刻师傅");
|
还可以进行一些计算。还有一种特殊语法来使用需要导入才能使用的类:
1 2 3
| Expression exp = parser.parseExpression("T(java.lang.Math).random()");
System.out.println(exp.getValue());
|
集合操作
对于类中的集合类:
1 2 3 4 5
| @Component public class Student { public Map<String, String> map = Map.of("test", "你干嘛"); public List<String> list = List.of("AAA", "BBB", "CCC"); }
|
使用SpEL快速取出集合中的元素:
1 2 3 4
| Expression exp = parser.parseExpression("map['test']"); System.out.println(exp.getValue(student)); Expression exp = parser.parseExpression("list[2]"); System.out.println(exp.getValue(student));
|
快速创建集合:
1 2 3 4 5 6 7 8 9 10
| Expression exp = parser.parseExpression("{5, 2, 1, 4, 6, 7, 0, 3, 9, 8}"); List value = (List) exp.getValue(); value.forEach(System.out::println);
Expression exp = parser.parseExpression("{{1, 2}, {3, 4}}");
Expression exp = parser.parseExpression("{name: '小明', info: {address: '北京市朝阳区', tel: 10086}}"); System.out.println(exp.getValue());
|
根据条件获取集合元素:
1 2 3 4 5 6
| @Component public class Student { public List<Clazz> list = List.of(new Clazz("高等数学", 4));
public record Clazz(String name, int score){ } }
|
1 2 3 4 5 6
| Expression exp = parser.parseExpression("list.?[name == '高等数学']"); System.out.println(exp.getValue(student));
Expression exp = parser.parseExpression("list.?[score > 3]"); System.out.println(exp.getValue(student));
|
针对某个属性创建对应的投影集合:
1 2
| Expression exp = parser.parseExpression("list.![name]"); System.out.println(exp.getValue(student));
|
安全导航运算符
针对空指针
1 2
| Expression exp = parser.parseExpression("name?.toUpperCase()"); System.out.println(exp.getValue(student));
|
与注解和xml文件配合
1 2 3
| <bean id="numberGuess" class="org.spring.samples.NumberGuess"> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> </bean>
|
1 2 3 4
| public class FieldValueTestBean { @Value("#{ systemProperties['user.region'] }") private String defaultLocale; }
|
数据库框架整合
关于数据源
采用池化技术来避免反复与数据库新建连接降低性能。也是Mybatis默认配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?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> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${驱动类(含包名)}"/> <property name="url" value="${数据库连接URL}"/> <property name="username" value="${用户名}"/> <property name="password" value="${密码}"/> </dataSource> </environment> </environments> </configuration>
|
其中dataSource标签属性有三个选项:
- UNPOOLED 不使用连接池的数据源
- POOLED 使用连接池的数据源
- JNDI 使用JNDI实现的数据源
整合Mybatis框架
使用@MapperScan来去除Mapper配置文件。
在配置文件中创建数据源Bean去除Mybatis配置文件。
在配置文件中创建SqlSessionFactoryBean类来代替手写的SqlSession分配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Configuration @ComponentScan("org.example.entity") @MapperScan("org.example.mapper") public class MainConfiguration { @Bean public DataSource dataSource(){ return new PooledDataSource("com.mysql.cj.jdbc.Driver", "jdbc:mysql://localhost:3306/study", "root", "123456"); }
@Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){ SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean; } }
|
使用:
1 2 3 4 5
| public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(MainConfiguration.class); TestMapper mapper = context.getBean(TestMapper.class); System.out.println(mapper.getStudent()); }
|
HikariCP连接池
导入依赖:
1 2 3 4 5
| <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> </dependency>
|
数据源的实现:
1 2 3 4 5 6 7 8 9
| @Bean public DataSource dataSource() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/study"); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUsername("root"); dataSource.setPassword("123456"); return dataSource; }
|
Mybatis事务管理
隔离级别
- 读未提交:可能脏读、不可重复读(虚读)、幻读
- 读已提交:不可重复读、幻读
- 可重复读:幻读
- 串行化:数据没问题,但效率低。
其中脏读是读取到旧数据,虚读是两次读取前后不一致,幻读是表记录的数量读取不一致。
Spring声明式事务管理
在配置类添加@EnableTransactionManagement注解:
1 2 3 4 5 6 7 8 9 10 11
| @Configuration @ComponentScan("org.example") @MapperScan("org.example.mapper") @EnableTransactionManagement public class MainConfiguration {
@Bean public TransactionManager transactionManager(DataSource dataSource){ return new DataSourceTransactionManager(dataSource); }
|
在方法上添加@Transactional注解,即可表示此方法执行的是一个事务操作,在调用此方法时,Spring会通过AOP机制为其进行增强,一旦发现异常,事务会自动回滚。
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Component public class TestServiceImpl implements TestService{
@Resource TestMapper mapper;
@Transactional public void test() { mapper.insertStudent(); if(true) throw new RuntimeException("我是测试异常!"); mapper.insertStudent(); } }
|
集成JUnit测试
Spring提供了一个Test模块,会自动集成Junit进行测试,导入以下依赖:
1 2 3 4 5 6 7 8 9 10 11
| <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.9.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>6.0.10</version> </dependency>
|
在测试类上添加注解:
1 2 3 4 5 6 7 8 9 10 11 12
| @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = TestConfiguration.class) public class TestMain {
@Autowired TestService service; @Test public void test(){ service.test(); } }
|