IoC 容器
Spring
为咱们提供了一个容器,用于治理所有的 JavaBean 组件,这是 Spring 框架最外围的概念
举个例子来阐明 IoC 容器到底是啥:
假如当初有一个 MemberService
和BookService
,当初它俩都须要操作数据库,用传统形式天然是在每个 Service 中都创立数据源实例,比方:
public class MemberService {private HikariConfig config = new HikariConfig();
private DataSource dataSource = new HikariDataSource(config);
}
public class BookService {private HikariConfig config = new HikariConfig();
private DataSource dataSource = new HikariDataSource(config);
}
在每个 Service 中都须要反复创立这些对象,随着 Service
越来越多,难道咱们要一个个手动创立进去吗?齐全能够共享同一个DataSource
,那 IoC 就是用来解决这些问题的,在 Ioc 模式下控制权产生了反转,所有的组件都由容器负责,而不是咱们本人手动创立,最简略的形式就是通过 XML 文件来实现:
<beans>
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="password" />
<property name="maximumPoolSize" value="10" />
<property name="autoCommit" value="true" />
</bean>
<bean id="bookService" class="BookService">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="memberService" class="MemberService">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
在这个文件中创立了三个 JavaBean 组件,能够发现两个 Service
共享同一个数据源ref="dataSource"
,创立好 Bean 之后就须要应用了 Bean 了
依赖注入形式:
注入在 IoC 容器中治理的 Bean,可通过 set()
或构造方法实现
public class BookService {
private DataSource dataSource;
public setDataSource(DataSource dataSource) {this.dataSource = dataSource;}
}
创立 Spring 我的项目
通过 maven 创立即可,需引入 spring-context
依赖
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itranswarp.learnjava</groupId>
<artifactId>spring-ioc-appcontext</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<java.version>11</java.version>
<spring.version>5.2.3.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</project>
一个特定的 application.xml
文件,就是之前组装 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
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
最初得通知容器为咱们创立并拆卸好所有得 Bean,在主启动类中增加以下代码
public static void main(String[] args) {
// 在 resources 目录下加载配置文件,并实现 Bean 的创立
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
// 获取 Bean
MemberService member = context.getBean(MemberService.class);
// 调用办法
member.login("username","password");
}
应用注解配置
其实齐全能够不必 XML 文件配置 Bean,应用注解配置更加简略
间接在类上增加 @Component
注解:
@Component
public class MailService {}
@Component
public class UserService {
@Autowired
private MailService mailService;
}
@Component
就等于在容器中定义了一个 Bean,默认名为首字母小写,@Autowired
就等于应用 set()
进行依赖注入
因为没有了配置文件,所以主启动类中的加载形式也有了变动
@Configuration
@ComponentScan
public class AppConfig {public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
}
}
@Configuration
示意它是一个配置类,等于 application.xml
,@ComponentScan
用于扫描以后类以及所在子包所有标注了 @Component
的类并将其创立,肯定要严格依照这个包构造来创立类
Scope
Spring 容器创立的 Bean 默认都是单例的,所以说通过 context.getBean()
获取的 Bean 都是同一个实例,咱们也能够让它每次都返回一个新的实例,把这种 Bean 称为原型,在类上上面的注解即可
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
注入 List
通过定义接口和实现类,将所有该类型的 Bean 都注入到一个 List 中
public interface Validator {}
@Component
public class NameValidator implements Validator {}
@Component
public class PasswordValidator implements Validator {}
@Component
public class Validators {
@Autowired
List<Validator> validators;
}
Validator
接口有两个实现类,在 Validators
中定义好了汇合的泛型,通过 @Autowired
就可将所有 Validator
类型的 Bean 注入到一个 List 中
第三方 Bean
用于一个 Bean 不在咱们的包治理之内
@Configuration
@ComponentScan
public class AppConfig {
// 创立一个 Bean:
@Bean
ZoneId createZoneId() {return ZoneId.of("Z");
}
}
@Bean
只调用一次,它返回的 Bean 也是单例的
初始化和销毁
用于一个 Bean 在被注入后进行初始化操作以及容器敞开时进行销毁操作,需引入一个特定依赖
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
@Component
public class MailService {
@PostConstruct
public void init() {System.out.println("init");
}
@PreDestroy
public void shutdown() {System.out.println("shutdown");
}
}
在 MailService
被注入后就会执行init()
,在容器敞开时执行shutdown()
,需调用close()
Resource
Spring 提供了一个 org.springframework.core.io.Resource
用于读取配置文件
@Value("classpath:/logo.txt")
private Resource resource;
还有更简略的形式,应用注解:
@PropertySource("app.properties") // 示意读取 classpath 的 app.properties
public class AppConfig {@Value("${app.zone:Z}")
String zoneId;
}
${app.zone:Z}
示意如果 Key 不存在就用默认值 Z
条件拆卸
当满足特定条件创立 Bean,应用@Conditional
,看一个简略例子:
@ConditionalOnProperty(name="app.smtp", havingValue="true")
public class MailService {}
如果配置文件中有 app.smtp
并且值为 true 才创立
AOP
面向切面编程,能够将罕用的比方日志,事务等从每个业务办法中抽离进去,实质其实是一个动静代理
引入 AOP 依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
@Aspect
@Component
public class LoggingAspect {
// 在执行 UserService 的每个办法前执行:
@Before("execution(public * com.zouyc.learn.service.UserService.*(..))")
public void doAccessCheck() {System.err.println("[Before] do access check...");
}
// 在执行 MailService 的每个办法前后执行:
@Around("execution(public * com.zouyc.learn.service.MailService.*(..))")
public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {System.err.println("[Around] start" + pjp.getSignature());
Object retVal = pjp.proceed();
System.err.println("[Around] done" + pjp.getSignature());
return retVal;
}
}
通过注解加特定的语法实现在办法执行前后做些事件,pjp.proceed()
执行 MailService
的办法,最初还需在配置类上开启@EnableAspectJAutoProxy
能够看到 AspectJ
的语法是非常复杂的,怎么更简洁呢?应用纯注解
自定义一个注解
@Target(METHOD)
@Retention(RUNTIME)
public @interface MetricTime {String value();
}
在须要被监控的办法上增加注解
@MetricTime("register")
public void register() {System.out.println("registration success");
}
定义 Aspect
类
@Aspect
@Component
public class MetricAspect {@Around("@annotation(metricTime)")
public Object metric(ProceedingJoinPoint joinPoint, MetricTime metricTime) throws Throwable {return joinPoint.proceed();
}
}
@Around("@annotation(metricTime)")
找到标注了 @MetricTime
的办法,需注意办法参数上的 metricTime
和@annotation(metricTime)
必须一样
AOP 避坑:
始终应用 get()
办法拜访,而不间接拜访字段
public String sendMail() {
ZoneId zoneId = userService.zoneId;
System.out.println(zoneId); // null
}
上述代码会报空指针异样,为什么?
起因在于成员变量的初始化,失常来说构造方法第一行总是调用super()
,然而 Spring 通过 CGLIB 动态创建的代理类并未调用super()
,因而从父类继承的成员变量以及本身的成员变量都没有初始化,如何解决?
public String sendMail() {
// 不要间接拜访 UserService 的字段:
ZoneId zoneId = userService.getZoneId();}
为什么调用 getZoneId()
就能解决呢?因为代理类会覆写 getZoneId()
,并将其交给原始实例,这样变量就失去了初始化,就不会报空指针异样了,最初一点,如果你的类有可能被代理,就不要编写public final
办法,因为无奈被覆写
拜访数据库
Spring 提供了一个 JdbcTemplate
让咱们操作 JDBC,所以咱们只需实例化一个JdbcTemplate
根本应用办法:
创立配置文件jdbc.properties
,这里应用的是 HSQLDB,它能够以内存模式运行,适宜测试
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.5.0</version>
</dependency>
# 数据库文件名为 testdb:
jdbc.url=jdbc:hsqldb:file:testdb
# Hsqldb 默认的用户名是 sa,口令是空字符串:
jdbc.username=sa
jdbc.password=
读取数据库配置文件并创立 DataSource
和JdbcTemplate
@PropertySource("classpath:jdbc.properties")
public class AppConfig {@Value("${jdbc.url}")
private String jdbcUrl;
@Value("${jdbc.username}")
private String jdbcUsername;
@Value("${jdbc.password}")
private String jdbcPassword;
@Bean
DataSource createDataSource() {HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(jdbcUrl);
hikariConfig.setUsername(jdbcUsername);
hikariConfig.setPassword(jdbcPassword);
hikariConfig.addDataSourceProperty("autoCommit", "true");
hikariConfig.addDataSourceProperty("connectionTimeout", "5");
hikariConfig.addDataSourceProperty("idleTimeout", "60");
return new HikariDataSource(hikariConfig);
}
@Bean
JdbcTemplate createJdbcTemplate(@Autowired DataSource dataSource) {return new JdbcTemplate(dataSource);
}
}
因为是在 HSQLDB 内存模式下工作的,所以咱们还须要建设对应的表
@Autowired
JdbcTemplate jdbcTemplate;
@PostConstruct
public void init() {
jdbcTemplate.update("CREATE TABLE IF NOT EXISTS users (" //
+ "id BIGINT IDENTITY NOT NULL PRIMARY KEY," //
+ "email VARCHAR(100) NOT NULL," //
+ "password VARCHAR(100) NOT NULL," //
+ "name VARCHAR(100) NOT NULL," //
+ "UNIQUE (email))");
}
在其余 Service 中注入 JdbcTemplate
即可
JdbcTemplate
的用法:
T execute(ConnectionCallback<T> action)
:应用 Jdbc 的Connection
T execute(String sql, PreparedStatementCallback<T> action)
:应用 Jdbc 的PreparedStatement
T queryForObject(String sql, @Nullable Object[] args, RowMapper<T> rowMapper)
:RowMapper 将 ResultSet 映射成一个 JavaBean 并返回,返回一行记录
List<T> query(String sql, @Nullable Object[] args, RowMapper<T> rowMapper)
:返回多行记录
INSERT
操作:
因为 INSERT
波及到主键自增,所以它比拟非凡
public User register(String email, String password, String name) {
// 创立一个 KeyHolder:
KeyHolder holder = new GeneratedKeyHolder();
if (1 != jdbcTemplate.update(
// 参数 1:PreparedStatementCreator
(conn) -> {
// 创立 PreparedStatement 时,必须指定 RETURN_GENERATED_KEYS:
var ps = conn.prepareStatement("INSERT INTO users(email,password,name) VALUES(?,?,?)",
Statement.RETURN_GENERATED_KEYS);
ps.setObject(1, email);
ps.setObject(2, password);
ps.setObject(3, name);
return ps;
},
// 参数 2:KeyHolder
holder)
) {throw new RuntimeException("Insert failed.");
}
// 从 KeyHolder 中获取返回的自增值:
return new User(holder.getKey().longValue(), email, password, name);
}
申明式事务
Spring 提供了一个 PlatformTransactionManager
来示意事务管理器,事务由 TransactionStatus
示意
PlatformTransactionManager transactionManager = new DataSourceTransactionManager();
TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionDefinition());
应用申明式事务:
@EnableTransactionManagement
public class AppConfig {
@Bean
PlatformTransactionManager createTxManager(@Autowired DataSource dataSource) {return new DataSourceTransactionManager(dataSource);
}
}
在须要应用的办法上增加@Transactional
,或者在类上增加(示意类中所有 public 办法都反对事务),事务的原理仍是 AOP 代理,所以开启事务之后就不用增加EnableAspectJAutoProxy
,判断事务回滚,只需抛出RuntimeException
,如果要针对某个异样回滚,就在注解上定义进去:
@Transactional(rollbackFor = {RuntimeException.class, IOException.class})
事务的流传:
默认级别为REQUIRED
,看代码:
@Transactional
public User register(String email, String password, String name) {
// 插入用户记录:
User user = jdbcTemplate.insert("...");
// 减少 100 积分:
bonusService.addBonus(user.id, 100);
}
能够看到上述代码中 register()
开启了事务,然而在办法中又调用了另一个 bonusService
,bonusService
没必要再创立新事务,事务的默认流传级别 REQUIRED
示意,如果以后有事务就主动退出以后事务,如果没有才创立新事务,所以 register()
办法的开始和完结就是整个事务的范畴,当然除了 REQUIRED
还有其余流传级别,这里就不一一赘述了
DAO 层
DAO 即 Data Access Object 的缩写,就是专门用来和数据库打交道的层级,负责解决各种业务逻辑
Spring 提供了 JdbcDaoSupport
简化数据库操作,它的外围就是持有一个JdbcTemplate
public abstract class JdbcDaoSupport extends DaoSupport {
@Nullable
private JdbcTemplate jdbcTemplate;
/**
* Set the JdbcTemplate for this DAO explicitly,
* as an alternative to specifying a DataSource.
*/
public final void setJdbcTemplate(@Nullable JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
initTemplateConfig();}
/**
* Return the JdbcTemplate for this DAO,
* pre-initialized with the DataSource or set explicitly.
*/
@Nullable
public final JdbcTemplate getJdbcTemplate() {return this.jdbcTemplate;}
}
能够看到 JdbcDaoSupport
并没有主动注入JdbcTemplate
,所以得本人注入
编写一个 AbstractDao
用于注入JdbcTemplate
public abstract class AbstractDao extends JdbcDaoSupport {
@Autowired
private JdbcTemplate jdbcTemplate;
@PostConstruct
public void init() {super.setJdbcTemplate(jdbcTemplate);
}
}
这样继承了 AbstractDao
的子类就能够间接调用 getJdbcTemplate()
获取JdbcTemplate
咱们还能够把更多的罕用办法都写到 AbstractDao
中,这里就须要用到泛型
public abstract class AbstractDao<T> extends JdbcDaoSupport {
private String table;
private Class<T> entityClass;
private RowMapper<T> rowMapper;
@Autowired
private JdbcTemplate jdbcTemplate;
@PostConstruct
public void init() {super.setJdbcTemplate(jdbcTemplate);
}
public AbstractDao() {this.entityClass = getParameterizedType();
this.table = this.entityClass.getSimpleName().toLowerCase() + "s";
this.rowMapper = new BeanPropertyRowMapper<>(entityClass);
}
public Class<T> getParameterizedType() {Type type = this.getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {ParameterizedType t = (ParameterizedType) type;
Type[] types = t.getActualTypeArguments();
Type firstType = types[0];
return (Class<T>) firstType;
}
return null;
}
public T getById(long id) { }
public List<T> getAll(int pageIndex) { }
public void deleteById(long id) { }
public Class<T> getEntityClass() {return entityClass;}
public String getTable() {return table;}
}
集成 MyBatis
MyBatis 是一款半自动 ORM 框架,只负责把 ResultSet 映射成 JavaBean,SQL 仍需本人编写
要应用它先要引入依赖:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
应用 MyBatis 的外围就是创立SqlSessionFactory
@Bean
SqlSessionFactoryBean createSqlSessionFactoryBean(@Autowired DataSource dataSource) {var sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
它能够间接应用申明式事务,创立事务管理和应用 JDBC 是一样的
定义 Mapper 接口,编写 SQL 语句:
public interface UserMapper {@Select("SELECT * FROM users WHERE id = #{id}")
User getById(@Param("id") long id);
}
扫描并创立 Mapper 的实现类:
@MapperScan("com.zouyc.learn.mapper")
public class AppConfig {}
在业务逻辑中间接注入 UserMapper
即可
XML 配置:为了更加灵便的编写 SQL 语句,就须要应用 XML 文件来做到,详情查看官网文档