一名在读大学的学生,跟着黑马程序员学完了Spring框架,对其过程做了上万字的笔记,做笔记也是为了巩固已学的知识,希望这份笔记能够解决大家的一点疑惑。
目录
一、Spring
一、基本概述
Spring是一个IOC和AOP的容器框架、并且是开源的,是为了简化企业开发而产生的,使得开发变得更加优雅和简洁。
二、框架划分图
蓝色框表示独立具体的模块 黑色框表示需要的依赖
模块解释:
Test:Spring的单元测试模块
Core Container:核心容器模块
AOP+Aspects:面向切面编程模块
Instrumentation:提供了class instrumentation支持和类加载器的实现来在特定的应用服务器上使用,几乎不用
Messaging:包括一系列的用来映射消息到方法的注解,几乎不用
Data Access/Integration:数据的获取/整合模块,包括了JDBC,ORM,OXM,JMS和事务模块
Web:提供面向web整合特性
二、Spring IOC
一、核心概念
1、IOC(Inverison of Control) 控制反转 使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。
控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
反转 : 程序本身不创建对象 , 而变成被动的接收对象 。
2、Spring技术对IOC思想进行了实现
①Spring提供了一个容器,称为IOC容器,用来充当IOC思想的"外部" 。
②IOC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IOC容器中统称为Bean。
3、DI(Dependency Injection) 依赖注入是控制反转的一种方法 在容器中建立bean和bean之间的依赖关系的整个过程,称为依赖注入。
二、目标:充分解耦
1、使用IOC容器管理bean(IOC)
2、在IOC容器内将有依赖关系的bean进行关系绑定(DI)
3、最终效果:使用对象时不仅可以直接从IOC容器中获取,并且获取到的bean已经绑定所有的依赖关系。
三、快速入门
步骤1、在pom.xml中导入Spring坐标
2、定义Spring管理的类(接口)
3、创建Spring配置文件,配置对应类作为Spring管理的bean。定义的id属性同一个上下文不能重复
4、初始化IOC容器,通过容器获取bean
四、bean作用范围说明
为什么bean默认为单例?
①适合交给容器进行管理的bean:表现层、业务层、数据层、工具对象
②不适合交给容器进行管理的bean:封装实体的域对象
五、bean生命周期
1、bean生命周期:bean从创建到销毁的整体过程,以下两个关闭IOC容器的方法 ①ClassPathXmlApplicationContext.close();手工关闭 ②ClassPathXmlApplicationContext.registerShutdownHook();注册关闭钩子,JVM退出前关闭
2、bean生命周期控制:在bean创建后到销毁前做一些事情
<bean init-method="" destory-method="" /> 提供生命周期控制方法
实现InitializingBean、DisposableBean接口
生命周期:
1、初始化容器:①创建对象(内存分配)、②执行构造方法、③执行属性注入(set操作)、④执行bean初始化方法
2、使用bean:执行业务操作
3、关闭销毁容器
六、依赖自动装配
1、IOC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
2、自动装配方式(autowire=""):按类型(常用)、按名称、按构造方法、不启动自动装配
七、依赖自动装配特征
1、自动装配用于引用类型依赖注入,不能对简单类型进行操作
2、使用按类型分配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
3、使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐
4、自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
八、创建容器
Spring容器是Spring的核心,一切Spring bean都存储在Spring容器内,并由其通过IOC技术管理。Spring容器也就是一个bean工厂(BeanFactory)。应用中bean的实例化、获取、销毁等都是由这个bean工厂管理的。通过以下三种方式可以显示的初始化一个容器:
1、类路径加载配置文件:ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
2、文件路径加载配置文件:ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\applicationContext.xml");
3、加载多个配置文件:ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml","bean2.xml");
九、获取bean
1、使用bean名称获取:BeanDao beanDao = (BeanDao) ctx.getBean("beanDao");
2、使用bean名称获取并指定类型:BeanDao beanDao = ctx.getBean("beanDao",BeanDao.class);
3、使用bean类型获取:BeanDao beanDao = ctx.getBean(BeanDao.class);
通过:Resource resources = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(resources);初始化bean延迟加载,不会执行无参构造
或通过:ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");初始化bean会立即加载
十、容器相关接口
1、BeanFactory是IOC容器的顶层接口,初始化BeanFactory对象时,加载的bean延迟加载
2、ApplicationContext接口是Spring容器的核心接口,初始化bean会立即加载
3、ApplicationContext接口提供基本的bean操作相关方法,通过其他接口扩展其功能
4、ApplicationContext接口常用初始化类 :
①ClassPathXMLApplicationContext ②FileSystemXmlApplicationContext
十一、bean标签相关的属性
1、id="bookDao" bean的ID
2、neam="a b c" bean的别名
3、class="com.chf.dao.impl.bookDaoImpl" bean类型、静态工厂类、FactoryBean类
4、scope="singleton" 控制bean的实例数量
5、init-method="init" 生命周期的初始化方法
6、destory-method="destory" 生命周期销毁方法
7、autowire="byType" 自动装配类型
8、factory-method="getInstance" bean工厂方法,应用于静态工厂或实例工厂
9、factory-bean="com.chf.factory.BookDaoFactory" 实例工厂bean
10、lazy-init="true" 控制bean延迟加载
三、注解开发定义bean 纯注解开发
一、注解开发定义bean
Spring提供@Component注解的三个衍生注解
① @Controller:用于表现层bean定义
② @Service:用于业务层bean定义
③ @Repository:用于数据层bean定义 注解后面可以不写(""),默认id是类名首字母小写。
二、纯注解开发
1、Spring3.0开启了纯注解开发模式,使用Java类替代配置文件,开启了Spring快速开发赛道
@Configuration 用于设定当前类为配置类
@ComponentScan({"com.chf.service","com.chf.dao"}) 用于设定扫描路径,只能添加一次
public class SpringConfig{}
2、读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象 ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
三、bean作用范围与生命周期管理
1、在类上使用注解@Scope定义bean作用范围。
@Scope("singleton")单例;@Scope("prototype")非单例
2、在方法上添加注解@PostConstruct即是bean的初始化
3、在方法上添加注解@PreDestory即是bean的销毁时刻
四、注解开发依赖注入
1、在属性上添加注解@Autowired可以省略构造属性的set方法
2、使用@Qualifier注解开启指定名称装配bean。必须配合@Autowired注解使用
3、使用@Value实现简单类型注入:@Value("");@Value("${}")可以引用外部资源的内容但是需要在SpringConfig的类上使用注解@PropertySource("classpath:jdbc.properties"),不支持通配符"*"
五、第三方bean依赖注入
public class JdbcConfig {
//1.定义一个方法获得要管理的对象
@Value("com.mysql.cj.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/ssm")
private String url;
@Value("root")
private String userName;
@Value("root")
private String password;
//2.添加@Bean,表示当前方法的返回值是一个bean
//@Bean修饰的方法,形参根据类型自动装配
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
@Configuration
//@Import:导入配置信息
@Import({JdbcConfig.class})
public class SpringConfig {
}
2、引用类型依赖注入
注意:引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象
@Bean
public DataSource dataSource(BookService bookService){
System.out.println(bookService);
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
@Configuration
@ComponentScan("com.chf.service")
@Import({classpath:JdbcConfig.class})
public class SpringConfig {
}
六、Spring结合Mybatis
1、首先在pom.xml文件中添加以下配置
<!--Spring结合JDBC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!--Spring结合Mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
2、在resources目录下去写jdbc.properties的配置文件
jdbc.driver=com.mysql.cj.jbdc.driver
jdbc.url=jdbc:mysql://localhost:3306/ssm
jdbc.username=root
jdbc.password=root
3、在config目录下创建JdbcConfig类
public class JdbcConfig {
//1.定义一个方法获得要管理的对象
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(DataSource dataSources){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
4、将Mybatis中的SqlMapConfig核心配置文件进行了优化修改
public class MybatisConfig {
//定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setTypeAliasesPackage("com.chf.pojo");//代替的是注册实体类别名<typeAliases></typeAliases>中的<package>
sqlSessionFactoryBean.setDataSource(dataSource);//代替的是数据源配置中的 <environments>
return sqlSessionFactoryBean;
}
//定义bean,返回MapperScannerConfigurer对象
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();//代替的是注册mapper文件<mapper></mapper>
msc.setBasePackage("com.chf.dao");//代替的是批量注册<package>
return msc;
}
}
七、Spring结合JUnit
1、首先在pom.xml中添加以下配置
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
2、在Test-Java下创建测试类
//设置类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//设置类Spring环境对应的配置
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
//支持自动装配注入bean
@Autowired
private AccountService accountService;
@Test
public void testFindById(){
System.out.println(accountService.findById(1));
}
@Test
public void testFindAll(){
System.out.println(accountService.findAll());
}
}
四、Spring AOP
一、核心概念
AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构。
OOP(Object Oriented Programming)面向对象编程
作用:在不惊动原始设计的基础上为其进行功能增强。减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
Spring理念:无入侵式/无侵入式
核心原理:观察所调用的方法是否符合切入点表达式,如果符合,则使用代理执行增强方法。
①连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等。
在SpringAOP中,理解为方法的执行。
②切入点(Pointcut):匹配连接点的式子。
在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法:
一个具体方法:com.chf.dao包下的BookDao接口中的无形参无返回值的save方法。
匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口的任意方法,所有带有一个参数的方法。
③通知(Advice):在切入点处执行的操作,也就是共性功能。包含五种类型:前置、后置、环绕、返回后、抛出异常后通知。
在SpringAOP中,功能最终以方法的形式呈现。
④通知类:定义通知的类。
⑤切面(Aspect):描述通知与切入点的对应关系,只关注点模块化。
⑥目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的。
⑦代理(Proxy):核心本质。目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现。
二、AOP入门案例分析
1、导入坐标(pom.xml)
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
2、制作连接点方法(原始操作,Dao接口与实现类)
public interface BookDao {
void save();
void update();
}
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println(System.currentTimeMillis());
System.out.println("book dao save");
}
public void update(){
System.out.println("book dao update ...");
}
}
3、制作共性功能(通知类与通知):新建一个aop包新建MyAdvice的类
public class MyAdrice{
public void before(){
System.out.println(System.currentTimeMillis());
}
}
4、定义切入点表达式
说明:切入点定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑。
public class MyAdrice{
@Pointcut("execution(void com.chf.dao.BookDao.update())")
public class pt(){}
}
5、绑定切入点与通知关系(切面)
public class MyAdrice{
@Pointcut("execution(void com.chf.dao.BookDao.update())")
public class pt(){}
@Before("pt()")
public void before(){
System.out.println(System.currentTimeMillis());
}
}
6、定义通知类受Spring容器管理,并定义当前类为切面类
//1、先用注解@Component使此类变为可以被Spring控制的bean
@Component
//2、再用注解@Aspect告诉Spring此类为切面,需要读取其中的注解方法
@Aspect
public class MyAdrice{
@Pointcut("execution(void com.chf.dao.BookDao.update())")
public class pt(){}
@Before("pt()")
public void before(){
System.out.println(System.currentTimeMillis());
}
}
7、在配置类当中开启Spring对AOP注解驱动支持
@Configuration
@ComponentScan("com.chf")
//3、最后用注解@EnableAspectJAutoProxy为了执行切面的注解方法
@EnableAspectJAutoProxy
public class SpringConfig {
}
三、AOP切入点表达式
1、切入点:要进行增强的方法。
2、切入点表达式:要进行增强的方法的描述方式。
描述方式一:执行com.chf.dao包下的BookDao接口中的无参数update方法
execution(void com.chf.dao.BookDao.update())
描述方式二:执行com.chf.dao.impl包下的BookDaoImpl类中的无参数update方法
execution(void com.chf.dao.impl.BookDaoImpl.update())
3、切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
①动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
②访问修饰符:public、private等可以省略
③异常名:方法定义中抛出指定异常,可以省略
4、可以使用通配符描述切入点,快速描述
①*:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现。常用
②..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的手写。常用
execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法。
③+:专用于匹配子类类型
四、AOP通知类型
1、AOP同时描述的抽取的共性功能,根据共性功能抽取的位置不同,最终运行时要将其加入到合理的位置。
2、AOP通知共分为5种类型:
①前置通知:@Before
②后置通知:@After
③环绕通知(重点):@Around
这个方法会将想要切入的方法直接取代,只会输出两句话。
@Around("pt()")
public void around(){
System.out.println("around before advice");
System.out.println("around after advice");
}
需要再形参中添加ProceedingJoinPoint就可以对原先方法进行环绕。
对原始方法的调用可以不接收返回值,通知方法设置为void即可,如果接收返回值,必须设定为Object类型。
原始方法的返回值如果是void类型,通知方法的返回值类型可以设置为boid,也可以设置为Object。
由于无法预知原始方法运行后是否抛出异常,因此环绕通知方法必须抛出Throwable对象。
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("around before advice");
Object o = pjp.proceed();//表示对原始操作的调用
System.out.println("around after advice");
return o;
}
④返回后通知(了解):@AfterReturning
⑤抛出异常后通知(了解):@AfterThrowing。被此注解注解的类在抛出异常后才会运行。
五、环绕通知中获取签名对象及参数
//设置环绕通知,在原始操作的运行前后记录执行时间
@Around("ProjectAdvice.servicePt()")
public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
//获取执行的签名对象
Signature signature = pjp.getSignature();
//通过签名获取执行类型(接口名)
String className = signature.getDeclaringTypeName();
//通过前面获取执行操作名称(方法名)
String methodName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms");
}
六、AOP通知获取数据
1、获取切入点方法的参数
①JoinPoint:适用于前置、后置、返回后、抛出异常后通知
@Before("pt()")
public void before(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
}
②ProceedJoinPoint:适用于环绕通知
注:以下的Object[] getArgs()为获取连接点方法运行时的入参列表;
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
Object ret = pjp.proceed(args);
return ret;
}
2、获取切入点方法返回值
①返回后通知
@AfterReturning(value="pt()",returning="ret")
public void afterReturning(String ret){
System.out.println("afterReturning advice"+ret);
}
②环绕通知
@Around("pt()")
public Object arount(ProceedingJoinPoint pjp) throws Throwable{
Object ret = pjp.proceed();
return ret;
}
3、获取切入点方法运行异常信息
①抛出异常后通知
@AfterThrowing(value="pt()",throwing="t")
public void afterThrowing(Throwable t){
System.out.println("afterThrowing advice"+t);
}
②环绕通知
@Around("pt()")
public Object around(ProceedingJoinPoint){
Object ret = null;
try{
ret = pjp.proceed();
} catch(Throwable t){
t.printStackTrace();
}
return ret;
}
七、JDK动态代理和CGLIB动态代理的区别
JDK动态代理只提供接口的代理,不支持类的代理,该代理类是实现了目标类接口, 并且代理类会实现接口所有的方法增强代码。调用时通过代理类先去调用处理类进行增强,再通过反射的方式进行调用目标方法。从而实现AOP。如果代理类没有实现接口,那么Spring AOP会选择使用。
CGLIB来动态代理目标类。 CGLIB在运行时动态的生成目标类的一个子类。并且会重写父类所有的方法增强代码,调用时先通过代理类进行增强,再直接调用父类对应的方法进行调用目标方法。从而实现AOP。
简单来说就是:JDK动态代理需要被代理类必须实现接口,CGLIB不需要。