1. Spring概念
1.1 介绍
- Spring是一个轻量级的开源的JavaEE框架
- Spring用于解决企业应用开发的复杂性
- Spring两大核心:IOC(控制反转)和AOP(面向切面)
1.2 Spring入门案例
- 导入Spring的4个核心jar包:beans、core、context、expression
- 创建一个Java类,并创建Spring配置的xml文件对该类进行配置
- 编写测试代码:加载配置文件,获取配置文件中创建的对象
<?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">
<bean id="user" class="User"></bean>
</beans>
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = context.getBean("user", User.class);
user.add();
}
2. IOC容器
2.1 IOC介绍
- IOC把对象的创建和对象之间的调用过程,交给Spring管理
- 使用IOC的目的:降低耦合度
- IOC通过xml解析、工厂模式、反射来创建对象
- IOC容器底层就是对象工厂,通过map保存所有创建好的Bean,并提供外界获取功能
- IOC用于Bean管理,指Spring创建对象+Spring注入属性
2.2 IOC接口
- Spring提供了两种实现IOC容器的方式,对应两个接口:BeanFactory 和 ApplicationContext
- BeanFactory是IOC容器的基本实现,是Spring的内置接口,不提供给开发人员使用。在加载配置文件时不会创建对象,在获取对象时(getBean方法)才创建
- ApplicationContext是BeanFactory的子接口,提供了更多更强大的功能,用于开发使用。在加载配置文件时就会创建对象
- ApplicationContext有两个主要的实现类:ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext
2.3 基于xml的Bean管理
2.3.1 xml配置文件创建对象介绍
- 创建对象:在xml配置文件中使用<bean>标签实现对象的创建
- <bean>标签中的属性:① id:唯一标识;② class:类的全路径
- 使用xml创建对象时,默认执行无参构造方法
- 创建好Java对象好,需要给该对象注入属性,又称为依赖注入(DI)
2.3.2 依赖注入的几种方法
- 方式一:使用set()方法注入属性。在Java类中编写属性对应的set()方法,并在xml配置文件中使用<property>标签进行配置,该标签是<bean>的子标签
<bean id="user" class="User">
<property name="age" value="22"></property>
<property name="name" value="防伪"></property>
</bean>
- 方式二:使用有参构造器注入属性。在Java类中编写有参构造器,并在xml配置文件中使用<constructor-arg>标签进行配置,该标签是<bean>的子标签
<bean id="user" class="User">
<constructor-arg name="age" value="22"></constructor-arg>
<constructor-arg name="name" value="防伪"></constructor-arg>
</bean>
- 方式三:p名称空间注入。用于简化set()方法注入,在xml文件的头部<beans>标签中添加p名称空间,在<bean>标签中进行属性注入
2.3.3 xml注入其他类型的属性
- 注入空值:
<property name="age"> <null/> </property>
- 注入特殊符号:
<property name="name"> <value><![CDATA[<<防伪>>]]]></value> </property>
- 注入外部bean:在xml中配置外部类对象对应的<bean>,将该对象注入到其他<bean>中
<bean id="book" class="Book"></bean>
<bean id="user" class="User">
<property name="book" ref="book"></property>
</bean>
- 注入内部bean:
<property name="book"> <bean id="book" class="Book"></bean> </property>
- 级联赋值:注入外部bean的操作即为级联赋值。除了在被调用bean中直接配置属性值,还可以在调用bean中通过“id.属性”的方式配置被调用bean的属性值,此时被调用bean对应的Java类中应有属性的get()方法
- 注入集合:除了直接在<property>标签中配置集合属性,还可以在xml中引入util标签库后,将集合提取出来后调用
<property name="arr">
<array>
<value>java</value>
<value>c++</value>
</array>
</property>
<property name="list" ref="myList"></property>
<property name="map">
<map>
<entry key="aa" value="AA"></entry>
<entry key="bb" value="BB"></entry>
</map>
</property>
<property name="set">
<set>
<value>mysql</value>
<value>redis</value>
</set>
</property>
<util:list id="myList">
<value>spring</value>
<value>autumn</value>
</util:list>
2.3.4 工厂Bean
- Spring有两种类型的bean,普通bean和工厂bean。普通bean在配置文件中定义的类型就是返回类型,工厂bean在配置文件中定义的类型可以和返回类型不同
- 在xml中配置静态工厂Bean时,class属性指定静态工厂的全类名,通过“factory-method”属性指定工厂类中创建实例的方法,在该Bean的子标签<constructor-arg>中配置方法的传入参数
- 在xml中配置实例工厂Bean时,class属性指定要创建对象的全类名,通过“factory-bean”属性指定工厂对象对应的Bean,通过“factory-method”属性指定工厂类中创建实例的方法
- 若一个类实现了FactoryBean接口,Spring自动将该类识别为工厂Bean,在xml文件中配置时不需要通过属性指定为工厂Bean,实现接口中的getObject()方法定义要返回的对象
public class MyBean implements FactoryBean<Book> {
//定义要返回的bean
public Book getObject() throws Exception {
Book book = new Book();
book.setName("Java");
return book;
}
}
2.3.5 Bean的作用域
- 在Spring中,可以设置创建的bean是单实例还是多实例,默认为单实例
- 将<bean>标签中的scope属性设置为“prototype”后为多实例(默认为“singleton”单实例)
- 当scope值为“singleton”时,加载xml配置文件时就会创建对象;若设置为“prototype”时,在调用getBean()方法时才创建对象
2.3.6 Bean的生命周期
对于一般的bean生命周期有5步,而对于实现了BeanPostProcessor接口的bean,若将该bean在xml中进行配置,所有在同一个xml配置文件中的bean均会加入后置处理器,生命周期变为7步
- 通过构造器创建bean实例
- 为bean的属性设置值
- 【若加入bean的后置处理器】把bean实例传递给postProcessBeforeInitialization方法
- 调用bean的初始化方法(在<bean>标签中用“init-method”属性指定该bean的初始化方法)
- 【若加入bean的后置处理器】把bean实例传递给postProcessAfterInitialization方法
- 使用bean
- 调用close()方法关闭容器,会调动bean的销毁方法(在<bean>标签中用“destroy-method”属性指定该bean的销毁方法)
2.3.7 自动装配
- 在<bean>标签中手动设置属性值称为手动装配,若根据bean的属性名和属性类型自动将匹配的属性值进行注入,称为自动装配
- 自动装配只针对对象类型的属性,且该对象要在xml中进行过配置
- 在<bean>标签中设置autowire属性的值可更改自动装配的类型,“byName”根据属性名注入,“byType”根据属性类型注入
2.3.8 引入外部属性文件
- 创建properties格式的外部属性文件
- 在xml配置文件中引入context名称空间,通过<context>标签引入外部属性文件
- 在<bean>标签中使用表达式“${}”读取外部属性文件
<context:property-placeholder location="test.properties"/>
<bean id="book" class="Book">
<property name="name" value="${key1}"></property>
</bean>
2.3.9 SpEL(Spring表达式语言)
在xml文件中进行配置时,可以通过“#{}”使用Spring的EL表达式,支持:使用字面量、引用其他bean、引用其他bean的某个属性值、调用非静态方法、调用静态方法、使用运算符
2.4 基于注解的Bean管理
2.4.1 注解介绍
- 注解是代码的特殊标记,格式:@注解名称(属性名=属性值,属性名=属性值)
- 注解可以作用在类、方法、属性上
- 使用注解的目的在于简化xml配置
2.4.2 创建对象的注解
以下四个注解功能一样,都可以用来创建bean实例,为了清晰使用一般用在固定层
- @Component
- @Service:一般用在Service层
- @Controller:一般用在web层(使用Spring整合JavaWeb时,Servlet由Tomcat创建,不能再使用注解交给ioc容器创建,同时Servlet中获取Service对象时也不能使用注解自动注入,需要从ioc容器中获取,Spring提供了名为ContextLoaderListener的监听器,通过该监听器可以在项目启动和关闭时创建和销毁ioc容器)
- @Repository:一般用在DAO层
2.4.3 使用注解创建对象的步骤
- 引入AOP依赖
- 开启组件扫描,在xml配置文件中使用<context:component-scan>标签进行扫描配置,“base-package”指定要扫描的目录
- 组件扫描的配置:use-default-filters=“false”:表示不使用默认filter,在子标签<context:include-filter>中设置要扫描的内容
<context:component-scan base-package="serive,dao" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
- 创建类,在类上添加注解。注解中的value等同于使用xml配置中的id,若省略,其值等于类名首字母小写
@Component(value = "userService")
public class UserService {
2.4.4 注入属性的注解
- @AutoWired:根据属性类型自动装配(标注在方法上时会为每个形参自动注入)
- @Qualifier:根据属性名称自动装配(可标注在形参前)
- @Resource:根据属性类型与名称自动装配(非Spring官方注解)
- @Value:注入普通类型属性
2.4.5 使用注解注入属性的步骤
- 在Service类和Dao类上添加创建对象的注解(注解添加在实现类上,非接口上)
- 在Service类中添加Dao类型的属性,在该属性上添加注入属性的注解(不需要set方法)
- @Qualifier要和@AutoWired一起使用,在指定类型的前提下再指定名称
- 泛型依赖注入:注入一个组件时,它的泛型也是参考标准,Spring可以使用带泛型的父类类型来确定子类的类型
@Service
public class UserService {
@Value(value = "abc")
private String name;
@Autowired
@Qualifier(value = "userDaoImpl")
private UserDao userDao;
}
2.4.6 纯注解开发(一般用于SpringBoot)
- 上述注解开发中,在xml配置文件中只进行了开启组件扫描的配置,若将该配置放置与一个配置类中,即可实现纯注解开发
- 在一个Java类上加入@Configuration注解可将其作为Spring配置类,替代xml配置文件
- 在配置类上加入@ComponentScan注解指定要扫描的包
@Configuration
@ComponentScan(basePackages = {"dao","service"})
public class SpringConfig {
}
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
3. AOP
3.1 AOP介绍
3.1.1 AOP用途与原理
- 利用AOP可以对业务逻辑的各个部分进行隔离,使得各部分之间的耦合度降低,提高程序的可重用性
- AOP通过不修改源代码的方式,在主干功能中添加新功能
- AOP底层使用动态代理实现。有接口的情况下使用JDK动态代理,没有接口的情况下使用CGLIB动态代理
- JDK动态代理:代理类会实现与被代理类相同的接口,若被代理类没有实现接口就无法使用JDK动态代理
- CGLIB动态代理:代理类会实现被代理类所有的方法
3.1.2 JDK动态代理底层代码
- JDK动态代理使用Proxy类中的方法创建代理对象
- Proxy中的newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法返回指定接口的代理类的实例,将方法调用分派给指定的调用处理程序
- newProxyInstance方法中的3个参数分别表示:类加载器、增强方法所在类实现的接口、代理对象增强后的方法
public class JDKProxy {
public static void main(String[] args) {
Class[] interfaces = {UserDao.class};
UserDaoImpl userDao = new UserDaoImpl();
UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
System.out.println(dao.add(1, 2));
}
}
class UserDaoProxy implements InvocationHandler {
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
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;
}
}
3.1.3 AOP术语
- 连接点:类中可以被增强的方法
- 切入点:类中实际上被增强的方法
- 通知(增强):实际上被增强的部分。通知有5种类型:前置通知(@Before)、后置通知(@AfterReturning)、环绕通知(@Around)、异常通知(@AfterThrowing)、最终通知/返回通知(@After)
- 切面:将通知应用到切入点的过程
3.2 AspectJ
3.2.1 AOP准备工作
- Spring框架一般基于AspectJ实现AOP操作
- AspectJ不是Spring组成部分,是独立的AOP框架
- 基于AspectJ实现AOP操作有两种方式:基于xml配置文件、基于注解(常用)
- 使用AOP需要导入的jar包有:aop、aspects、cglib、aspectjweaver、aopalliance(后3个包保证在被代理类没有实现接口时也能使用AOP)
- 切入点表达式语法结构:execution( [权限修饰符] [返回类型] [全类名] . [方法名] ([参数列表]) )
3.2.2 AspectJ注解
- 创建一个待增强类,类中定义方法
- 创建增强类,该类中创建的不同方法代表不同通知类型
- 在Spring配置文件或配置类中,开启注解扫描
- 使用注解创建待增强类和增强类的对象
- 在增强类上添加注解@Aspect
- 在Spring配置文件或配置类中开启生成代理对象
- 配置不同类型的通知:在增强类中的方法上添加通知类型注解,使用切入点表达式配置
- 对相同的切入点可用@Pointcut注解进行抽取
- 若一个待增强方法有多个增强类,可在增强类上添加“@Order(数字)”注解指定执行顺序,数字越小优先级越高
使用配置文件开启生成代理对象:<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
使用配置类开启生成代理对象:@EnableAspectJAutoProxy
@Component
@Aspect
@Order(1)
public class UserProxy {
@Pointcut(value = "execution(* aop.annotation.User.add(..))")
public void point() {
}
@Before(value = "execution(* aop.annotation.User.add(..))")
public void before() {
System.out.println("before!");
}
@AfterReturning(value = "point()")
public void after() {
System.out.println("after!");
}
}
3.2.3 AspectJ配置文件
在xml配置文件中配置切入点:
<aop:config>
<aop:pointcut id="p" expression="execution(* ioc.Book.setName(..))"/>
<aop:aspect ref="user">
<aop:before method="add" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
3.2.4 AOP细节
- 若被代理类实现了接口,要通过ioc.getBean(接口)获取bean;若被代理类没有实现接口,通过ioc.getBean(被代理类)获取bean。因为加入了AOP之后获取到的bean都是代理类对象
- 切入点表达式中支持通配符: ∗ * ∗匹配一个或多个字符、匹配任意一个参数; . . .. ..匹配任意多个参数、匹配任意多层路径
- 通知方法的执行顺序:@Before→@After→@AfterReturning或@AfterThrowing
- 在通知方法中加入JoinPoint类型的形参,可获取切入方法的详细信息,如方法参数、方法名等
- 通知注解的returning参数可指定该通知方法的一个形参用来接收切入方法的返回值
- 通知注解的throwing参数可指定该通知方法的一个形参用来接收切入方法的异常
4. JdbcTemplate
4.1 JdbcTemplate概述与准备工作
- Spring框架对JDBC进行了封装,使用JdbcTemplate方便实现对数据库操作
- 使用JdbcTemplate需要引入的jar包:spring-jdbc、spring-tx、spring-orm
- 在xml配置文件中配置数据库连接池
- 配置JdbcTemplate对象,注入DataSource(在JdbcTemplate源码中有DataSource属性的set方法)
- 创建Service类和Dao类,在Service类中注入Dao对象,Dao类中注入JdbcTemplate对象
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql:///users"/>
<property name="username" value="root"/>
<property name="password" value="MyNewPass"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
4.2 JdbcTemplate操作数据库
- 调用JdbcTemplate对象中的update(String sql, @Nullable Object… args)方法执行增删改操作
- 调用JdbcTemplate对象中的queryForObject(String sql, Class<T> requiredType)方法查询返回某个值
- 调用JdbcTemplate对象中的queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object… args)方法查询返回某个对象
- 调用JdbcTemplate对象中的query(String sql, RowMapper rowMapper)方法查询返回某个集合
- 调用JdbcTemplate对象中的batchUpdate(String sql, List<Object[]> batchArgs)方法执行批量增删改操作,该方法的第二个参数是一个List集合,集合中的每个元素是一个Object数组,每个数组代表每次执行SQL语句的传入对象
String sql = "insert into t_user(`username`,`status`) values(?,?)";
jdbcTemplate.update(sql, user.getUsername(), user.getStatus());
String sql = "select count(*) from t_user";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
String sql = "select id,username,status from t_user where id=?";
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), id);
String sql = "select * from t_user";
List<User> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class));
String sql = "insert into t_user value(?,?,?)";
int[] ints = jdbcTemplate.batchUpdate(sql, list);
5. 事务管理
5.1 Spring事务管理介绍
- 事务一般添加到Service层(业务逻辑层)中
- 在Spring中进行事务管理操作有两种方式:① 编程式事务管理;② 声明式事务管理(使用)
- 声明式事务管理有两种方式实现:基于注解方式(使用)和基于xml配置文件方式
- 声明式事务管理底层使用AOP原理
- Spring中提供了一个PlatformTransactionManager接口作为事务管理器,该接口针对不同的框架提供不同的实现类
5.2 注解方式实现声明式事务管理
5.2.1 操作步骤
- 在xml配置文件中配置事务管理器
- 在xml配置文件中引入名称空间tx,开启事务注解
- 在Service类或Service类的方法上添加事务注解“@Transactional”
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
5.2.2 @Transactional注解上可以进行的参数配置
- propagation:事务传播行为。Spring定义了7种传播行为,默认为REQUIRED
- isolation:事务隔离级别。Mysql数据库默认使用REPEATABLE READ
- timeout:超时时间(单位:秒)。事务需要在一定时间内提交,若未提交进行回滚。默认为-1,不回滚
- readOnly:是否只读(只能进行查询操作)。默认为false
- rollbackFor:回滚(设置出现哪些异常进行事务回滚)。运行时异常默认回滚,编译时异常默认不回滚
- noRollbackFor:不回滚(设置出现哪些异常不进行事务回滚)
6. Spring5新特性
6.1 新特性介绍
- 整个Spring5框架的代码基于Java8,运行时兼容JDK9,并将许多不建议使用的类和方法在代码库中删除
- Spring5自带了通用的日志框架,移除了log4j,使用log4j2
- Spring5核心容器支持@Nullable注解,该注解可以用在方法、属性、参数上,表示可以为空
- Spring5核心容器支持函数式风格GenericApplicationContext
- Spring5支持整合JUnit5单元测试框架
- 引入了新的spring-webflux模块,一个基于reactive的spring-webmvc,完全的异步非阻塞,旨在使用enent-loop执行模型和传统的线程池模型
6.2 使用log4j2日志框架
- 导入log4j-api、log4j-core、log4j-slf4j-impl、slf4j-api的依赖
- 创建一个名为“log4j2.xml”的配置文件,该配置文件中内容一般固定
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="sel4jTest" level="trace">
<AppenderRef ref="Console"/>
</Logger>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
6.3 使用函数式风格向Spring容器中注入对象
GenericApplicationContext context = new GenericApplicationContext();
context.refresh();
context.registerBean("user",User.class, () -> new User());
User user = (User) context.getBean("user");
System.out.println(user);
6.4 整合JUnit
原始的单元测试需要手动创建IOC容器的对象,且无法通过注解自动装配。使用Spring整合后的单元测试会自动创建IOC容器的对象,可以自动装配。
- 导入spring-test依赖
整合JUnit4
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:jdbc.xml")
public class Junit4Test {
整合JUnit5
@SpringJUnitConfig(locations = "classpath:jdbc.xml")
public class Junit5Test {
@Autowired
private UserService userService;
@Test
public void test1(){
User user = userService.findUser(1);
System.out.println(user);
}
}