IOC 控制反转
- 程序将类的创建交给第三方容器来管理,即第三方容器能创建类的实例化对象,而不需要程序员new对象
- ioc容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在ioc容器中称为Bean
- maven坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
bean的注入
- 通过bean.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">
<!--
1. 引入spring依赖
2. 创建applicationContext.xml文件
3. 配置bean
4. <bean id="唯一标识" class="实现类"/>
-->
<bean id="bookDao" class="com.hly.dao.impl.BookDaoImpl"></bean>
<bean id="bookService" class="com.hly.service.impl.BookServiceImpl"></bean>
</beans>
- 获取bean
public static void main(String[] args) {
// 获取IOC容器
ApplicationContext ctx =new ClassPathXmlApplicationContext("applicationContext.xml");
// 通过id获取bean对象
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
BookService bookService = (BookService)ctx.getBean("bookService");
bookService.service();
}
工厂模式注入bean
DI依赖注入
- bean与bean之间有关联关系时,需要采用依赖注入
set注入
- 提供set方法
public class BookServiceImpl implements BookService {
private BookDao bookDao;
@Override
public void service() {
System.out.println("book service ....");
bookDao.save();
}
// 提供set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
- xml中bean中书写property标签
<bean id="bookService" class="com.hly.service.impl.BookServiceImpl">
<!--
配置bean与bean之间的依赖
谁需要谁,就在是谁里配
<property(属性标签) name="bean中的属性名" ref="需要哪个bean的Id"></property>
-->
<property name="bookDao" ref="bookDao"></property>
</bean>
- 底层实现机制 * 通过property标签获取name(属性名) * 通过反射获取setName()方法 * 通过property标签ref获取Bean的id * 通过反射机制调用set方法完成注入
- 注意 1. name关联的是setXxx()方法,而不是属性名 2. bean的id必须唯一
构造注入
- 提供有参构造方法
- xml中bean中写constructor-arg标签
- 三种注入方式
- 可以通过下标
- 可以通过参数名
- 可以通过类型判断
set注入案例
- 注入Bean
- 外部Bean
- 内部Bean
- 注入简单类型
- 注入集合
- 注入property
- 注入null/""
- 根据p命名空间注入
- set注入
- 根据c命名空间注入
- 构造注入
- 根据utils命名空间注入
- 集合类型
自动装配
- 基于XML的自动装配
- 通过类型(byType)
- bean唯一
- 通过名称(byName)
- 注意:
- 都需要提供set、get方法
- 通过类型(byType)
- 基于注解的自动装配
BeanFactory与FactoryBean的区别
- FactoryBean是辅助Spring容器实例化其他Bean的一个Bean对象
- BeanFactory是Spring容器创建Bean的工厂
Bean的生命周期
一个bean从创建到死亡的过程
Bean生命周期的5步
- 创建Bean
- Bean属性的赋值
- 初始化Bean init-method
- 使用Bean
- 销毁Bean destroy-method
Bean生命周期的7步
可以在初始化前后调用某些方法,需要某个Bean实现BeanPostProcessor接口,重写两个方法。在xml中注册该Bean,会对所有bean对象生效
- 创建Bean
- Bean属性的赋值
- 执行"Bean后处理器"的postProcessBeforeInitialization方法
- 初始化Bean
- 执行"Bean后处理器"的postProcessAfterInitialization方法
- 使用Bean
- 销毁Bean
Bean生命周期的十步
在某些特定时间点检查该Bean是否实现了特定的某些接口,若实现了则执行这些接口中的某些重写方法,从而更好的管理bean
- 创建Bean
- Bean属性的赋值
- 检查Bean是否实现某些带Aware后缀接口
- BeanClassLoaderAware, BeanNameAware, BeanFactoryAware
- 可以获取Bean的类加载器,Bean工厂,Bean的名字等操作
- 执行Bean后处理器的postProcessBeforeInitialization方法
- 检查Bean是否实现了InitializingBean接口
- 初始化Bean
- 执行"Bean后处理器"的postProcessAfterInitialization方法
- 使用Bean
- 检查Bean是否实现了DisposableBean接口
- 销毁Bean
Spring整合Mybatis
基于XML版
导入对应的依赖坐标
- mybatis配置文件: 用于配置mybatis其他配置,例如分页插件
<?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>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>
</configuration>
- jdbc.properties文件
jdbc.driver=com.mysql.cj.jdbc.Driver
# mysql8需要添加时区等
jdbc.url=jdbc:mysql://localhost:3306/book_shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=123456
jdbc.acquireIncrement=5
jdbc.initialPoolSize=5
jdbc.minPoolSize=3
jdbc.maxPoolSize=10
- spring配置文件 配置SqlSessionFactoryBean、Mapper接口动态生成代理类、dataSource等
<!-- 加载properties目录下的所有properties文件-->
<context:property-placeholder location="classpath:properties/*.properties" file-encoding="utf-8"/>
<!--使用c3p0连接池-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 设置连接池参数-->
<!-- 一次同时获取的连接数 -->
<property name="acquireIncrement" value="${jdbc.acquireIncrement}"/>
<!-- 初始化连接数 -->
<property name="initialPoolSize" value="${jdbc.initialPoolSize}"/>
<!-- 最小连接数 -->
<property name="minPoolSize" value="${jdbc.minPoolSize}"/>
<!--最大连接数-->
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
</bean>
<!-- 配置mybatis-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--数据库-->
<property name="dataSource" ref="dataSource"/>
<!-- 配置mybatis配置文件 -->
<property name="configLocation" value="classpath:mybatis.xml"/>
<!-- 配置实体类别名 -->
<property name="typeAliasesPackage" value="com.hly.book.entity"/>
<!-- 配置映射Mapper配置文件-->
<property name="mapperLocations" value="classpath*:mapper/*"/>
</bean>
<!--配置mapper扫描器,让spring生成代理类,并交给spring管理-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- bean工厂的名字 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--mapper接口所在的包-->
<property name="basePackage" value="com.hly.book.mapper"/>
</bean>
<!--事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启注解式事务-->
<tx:annotation-driven/>
基于注解
- 添加依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.10</version>
</dependency>
<!--mybatis jar包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!--spring jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!--spring mybatis jar包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
2.创建SpringConfig类对象,spring的配置类
@Configuration
@ComponentScan("com.hly")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
3.创建数据源对象
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.user}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
- 创建Mybatis配置对象
public class MybatisConfig {
//使用SqlSessionFactoryBean类创建SqlSessionFactory对象Bean
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
// 设置数据源
ssfb.setDataSource(dataSource);
// 设置实体类映射包
ssfb.setTypeAliasesPackage("com.hly.domain");
return ssfb;
}
//MapperScannerConfigurer类创建Mapper映射文件对象Bea
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
//mapper映射包
MapperScannerConfigurer msc = new MapperScannerConfigurer();
//设置mapper映射接口所在的包
msc.setBasePackage("com.hly.dao");
return msc;
}
}
SpringAOP
AOP的概念
-
概念
- AOP(Aspect Oriented Programamming) 面向切面编程,是一种编程范式
-
作用
- 在不惊动原始数据设计的基础上为方法进行功能增强
-
核心概念
-
代理(Proxy)
- SpringAOP的核心使用代理模式实现的
-
连接点(JoinPoint)
- SpringAOP中,理解为任意方法的执行
-
切入点(Pointcut)
- 匹配连接点的式子,具有共性功能的方法描述
-
通知(Advise)
- 若干个方法的共性功能,最终体现为一个方法
-
切面(Aspect)
- 描述通知与切入点的对应关系
-
目标对象(Target)
- 被代理的原始对象称为目标对象
-
AOP的入门案例
- 第一步:创建一个接口和实现类
-
public interface BookDao { void save(); void update(); } @Repository @Scope("singleton") public class BookDaoImpl implements BookDao{ @Override public void save() { System.out.println("book dao ........"); } public void update(){ System.out.println("book update ........"); } @PreDestroy public void destroy() throws Exception { }B @PostConstruct public void afterPropertiesSet() throws Exception { } } - 第二步:创建一个通知类并写一个公有的通知方法
-
public class MyAdvise { public void advise() { System.out.println("This is my advise"); } } - 第三步:创建一个切入点,使用注解标识连接点的匹配规则
-
//切入点是无返回值、无参数、无内容的私有方法 @Pointcut("execution(void com.hly.*.*Dao.*(..))") private void point(){} - 第四步:在通知方法上添加注解标识通知类型
- @xxx("切入点的方法名()")
-
public class MyAdvise { //切入点是无返回值无内容的私有方法 @Pointcut("execution(void com.hly.*.*Dao.*(..))") private void point(){} //定义该方法是执行前 //@Before("point()") public void advise() { System.out.println("This is my advise"); } } - 第五步:给通知类添加注解,标识这个类是一个通知类
-
// 为了让Spring检测到是一个Bean @Component // Aspect使其不是一个普通的Bean,而是一个切面 @Aspect // 定义一个通知类,包含通知方法 public class MyAdvise { .... } - 第六步:在Spring配置类上添加注解
-
@Configuration @ComponentScan("com.hly") // 告诉Spring使用了注解式开发aop @EnableAspectJAutoProxy public class SpringConfig { }
AOP工作流程
Spring容器启动
读取所有切面配置的切入点(使用到的才装载)
初始化Bean
0. 匹配失败,创建对象
0. 匹配成功,创建原始对象(目标对象)的代理对象
获取Bean执行方法
- 获取Bean,调用方法执行,完成操作
- 获取的Bean是代理对象时,执行代理模式中的增强 方法,完成操作
AOP的切入点表达式
-
切入点表达式标准格式:动作关键字(访问修饰符(可以省略) 返回值 包名.类/接口名.方法名(参数)异常名(可省))
- execution(* com.hly.. * Dao.*(..))
-
切入点表达式描述通配符
- 作用:用于快速描述,范围描述
- *:匹配任意符号
- .. : 匹配多个连续的任意符号(常用于匹配包名)
- +:匹配子类的类型
-
切入点表达式书写技巧
- 按标准规范开发
- 查询操作的返回值建议使用*匹配
- 减少使用..的形式描述包
- 对接口进行描述,使用*表示模块名,例如BookDao的匹配描述为 *dao
- 方法名书写保留动词,例如get* getById 匹配为getBy*
- 参数根据实际情况灵活调整
AOP的通知类型
-
@Before()
- 连接点执行前
-
@After()
- 连接点执行后
-
@Around()
-
环绕执行
-
注意
- 需要使用形参,调用连接点,否则会覆盖连接点方法
-
@Around("point()") public void advise1(ProceedingJoinPoint pjp) throws Throwable { pjp.proceed(); } - 环绕通知返回值设置为Object类型
- 环绕通知可以对原始方法调用过程出现的异常进行处理
-
-
@AfterReturning()
- 方法正常执行完后执行
-
@AfterThrowing()
- 方法抛出异常后执行
AOP案例:打印方法的执行时间
@Around("point()")
public void advise1(ProceedingJoinPoint pjp) throws Throwable {
long l = System.currentTimeMillis();
Signature signature = pjp.getSignature();
String declaringTypeName = signature.getDeclaringTypeName();
String name = declaringTypeName+"."+signature.getName();
for (int i=0;i<10000;i++){
pjp.proceed();
}
long l1 = System.currentTimeMillis();
System.out.println(name+"执行效率:"+(l1-l)+"ms");
}
AOP获取数据
-
获取切入点方法的参数
- JoinPoint 适用于前置、后置、返回值、抛出异常后通知,设置为方法的第一个形参
-
@Around("MyAdvise.point()") public void advise2(JoinPoint jp){ Object[] args = jp.getArgs(); System.out.println(Arrays.toString(args)); }- ProceedJointPoint:适用于环绕通知
-
@Around("point()") public void advise1(ProceedingJoinPoint pjp) throws Throwable { pjp.proceed(); Object[] args = pjp.getArgs(); System.out.println(Arrays.toString(args)); } -
获取切入点返回值
- 返回后通知
-
@AfterReturning(value = "MyAdvise.point()",returning = "ret") public void advise2(JoinPoint jp,Object ret){ }- 环绕通知
-
@Around("point()") public void advise1(ProceedingJoinPoint pjp) throws Throwable { Object ret = pjp.proceed(); } -
获取切入点方法运行异常信息
- 抛出异常后通知
-
@AfterThrowing(value = "MyAdvise.point()",throwing= "t") public void advise3(JoinPoint jp,Throwable t){ }- 环绕通知
-
@Around("MyAdvise.point()") public void advise3(ProceedingJoinPoint pjp){ try { Object proceed = pjp.proceed(); } catch (Throwable e) { e.printStackTrace(); } }
案例:给参数去掉空格后执行
@Around("MyAdvise.point()")
public void advise3(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
for (int i = 0; i < args.length; i++) {
if (args[i].equals(String.class)){
args[i] = args[i].toString().trim();
}
}
Object proceed = pjp.proceed(args);
return;
}