Spring
1、Spring框架
(1)Spring是轻量级的开源的javaEE框架
(2)Spring框架可以解决企业应用开发的复杂性
(3)Spring有两个核心部分:IOC和AOP
- IOC:控制反转,把创建对象的过程交给Spring进行管理
- Aop:面向切面,不修改源代码进行功能增强
(4)Spring特点
- 方便解耦,简化开发
- Aop编程支持
- 方便程序测试
- 方便和其他框架整合
- 方便进行事务操作
- 降低API开发难度
(5)Spring5
2、入门案例
(1)下载Spring5(5.2.6 https://repo.spring.io/release/org/springframework/spring/
)
3、IOC
3.1、IOC容器
3.1.1、IOC思想
反转控制
(1)获取资源耳朵传统方式
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
(2)反抓控制方式获取资源
反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率,这种行为也称为 被动形式。
(3)DI
DI:Dependency Injection,翻译过来就是 依赖注入
DI是IDC的另一种表述形式:即组件以一些预先定义的方式(例如:setter方法)接受来自于容器的资源注入,相当于IOC而言,这种表述更直接。
所以结论就是:IOC是一种反转控制的思想,而DI是对IOC的一种具体实现。
3.1.2、IOC容器在Spring中的实现
Spring的IOC容器就是IOC思想的一个落地的产品实现。IOC容器中管理的组件也叫做bean。在创建bean之前,首先需要创建IOC容器,Spring提供了IOC容器的两种实现方式:
(1)BeanFactory
这是IOC容器的基本实现,是Spring内部使用的接口,面向Spring本身,不提供给开发人员使用。
(2)ApplicationContext
BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。
实践3.1.2.1 HelloSpring
HelloSpring.java
package cn.springdemo;
/**
* 第一个Spring,输出"Hello,Spring!"。
*/
public class HelloSpring {
// 定义who属性,该属性的值将通过Spring框架进行设置
private String who = null;
private String content = null;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getWho() {
return who;
}
public void setWho(String who) {
this.who = who;
}
/**
* 定义打印方法,输出一句完整的问候。
*/
public void print() {
System.out.println("Hello," + this.getWho() + "!");
}
public void tell() {
// TODO Auto-generated method stub
System.out.println(this.getWho()+"说"+this.getContent());
}
}
applicationContext.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-3.2.xsd">
<!--
bean:配置一个bean对象,将对象交给IOC容器管理
属性:
id:bean的唯一标识,不能重复
class:设置bean对象所对应的类型
-->
<!-- 通过bean元素声明需要Spring创建的实例。该实例的类型通过class属性指定,并通过id属性为该实例指定一个名称,以便在程序中使用 -->
<bean id="helloSpring" class="cn.springdemo.HelloSpring">
<!-- property元素用来为实例的属性赋值,此处实际是调用setWho()方法实现赋值操作 -->
<property name="who">
<!-- 此处将字符串"Spring"赋值给who属性 -->
<value>Spring</value>
</property>
</bean>
<bean id="zhangGa" class="cn.springdemo.HelloSpring">
<!-- property元素用来为实例的属性赋值,此处实际是调用setWho()方法实现赋值操作 -->
<property name="who">
<!-- 此处将字符串"Spring"赋值给who属性 -->
<value>我是张嘎</value>
</property>
<property name="content">
<value>三天不打小鬼子,手抖痒痒我是孙冠楠 </value>
</property>
</bean>
<bean id = "rod" class="cn.springdemo.HelloSpring">
<property name="who"><value>Rod</value></property>
<property name="content"><value>世界上有10种人,认识二进制的和不认识二进制的</value></property>
</bean>
</beans>
HelloSpringTest.java
package test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.springdemo.HelloSpring;
public class HelloSpringTest {
@Test
public void helloSpring() {
// 通过ClassPathXmlApplicationContext实例化Spring的上下文
// FileSystemXmlAppplicationContext无法在其他电脑运行
// 获取IOC容器
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
// 获取IOC容器中的bean
// ,通过ApplicationContext的getBean()方法,根据id来获取bean的实例
HelloSpring helloSpring = (HelloSpring) context.getBean("helloSpring");
// 执行print()方法
helloSpring.print();
}
@Test
public void tell() {
// 通过ClassPathXmlApplicationContext实例化Spring的上下文
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
// 通过ApplicationContext的getBean()方法,根据id来获取bean的实例
HelloSpring zhangga = (HelloSpring) context.getBean("zhangGa");
// 执行print()方法
zhangga.tell();
}
@Test
public void tellRod() {
// 通过ClassPathXmlApplicationContext实例化Spring的上下文
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
// 通过ApplicationContext的getBean()方法,根据id来获取bean的实例
HelloSpring Rod = (HelloSpring) context.getBean("rod");
// 执行print()方法
Rod.tell();
}
}
反射中通常使用无参构造。没有有参构造的时候无参构造是默认的,如果存在有参构造就必须加上一个无参构造。
获取Bean的三种方式
-
根据bean的id获取,是object类型
-
根据bean的类型获取(常用)
注意:根据获取bean时,要求IOC容器中有且只有一个类型匹配的bean
若没有任何一个匹配的bean,此时抛出异常:NoSuchDefinitionException
若有多个类型匹配的bean,此时抛出异常:NoUniqueDefinitionException
-
根据bean的id和类型获取
Test文件中
1.HelloSpring helloSpring = (HelloSpring) context.getBean("helloSpring");
2.HelloSpring helloSpring = (HelloSpring) context.getBean(HelloSpring.class);
3.HelloSpring helloSpring = (HelloSpring) context.getBean("helloSpring",HelloSpring.class);
扩展:
1、如果组件类实现了接口,根据接口类可以获取bean吗?
答:可以,前提是bean唯一
Person person = ioc.getBean(Person.class);
2、如果一个接口有多个实现类,这些实现类都配置了bean,根据接口类型可以获取bean吗?
答:不可以,因为bean不唯一
结论:
根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:{对象instanceof指定的类型}的返回结果,只要返回的是true就可以认定和类型匹配,能够获取到。
即通过bean的类型,bean所继承的类的类型、bean所实现的接口的类型都可以获取bean
实践3.1.2.2 依赖注入(常用的两种方式,第一种setter注入最常用)
为类中的属性进行赋值
方法一:setter注入
在xml配置文件中,property通过成员变量的set方法进行赋值
name属性设置需要赋值的属性名(和set方法有关,与成员变量关系不大)
value属性设置为属性所负的值
如下图所示:
测试:
public class PrinterTest {
@Test
public void printerTest() {
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
// 通过Printer bean的id来获取Printer实例
Printer printer = (Printer) context.getBean("printer",Printer.class);
String content = "几位轻量级容器的作者曾骄傲地对我说:这些容器非常有"
+ "用,因为它们实现了“控制反转”。这样的说辞让我深感迷惑:控"
+ "制反转是框架所共有的特征,如果仅仅因为使用了控制反转就认为"
+ "这些轻量级容器与众不同,就好像在说“我的轿车是与众不同的," + "因为它有4个轮子。”";
printer.print(content);
}
}
方法二:有参构造
name是设置参数名而不是属性名
标签使用,一个属性使用一个,然后自己会去对应类中的有参构造方法
<constructor-arg value=" ”>
然后测试类的getBean后面引号的内容换为value 的值
问题: 如果xml中的value的内容可以匹配多个类中的有参构造方法,可以增加一个name来进行指定
3.2、引入外部属性文件
4、基于注解管理bean
4.1、实验一:标记与扫描
和XML配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。
标记:本质上:所有的一切的操作都是java代码来实现的,XML和注解只是告诉框架中的JAVA代码如何执行。
扫描:Spring为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测,然后根据注解进行后续操作。
(1)标识组件的常用注解:
- @Component:将类标识为普通组件,通用
- @Controller:将类标识为控制层组件,控制器类
- @Service:将类标识为业务层组件,业务类
- @Repository:将类标识为持久组件,DAO类
将注解加到实现类上。通过查看源码我们得知,@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。
对于Spring使用IOC容器管理这些组件来说没有区别,所以这三个注解只是给开发人员看的,让我们便于分辨组件的使用。
注意: 虽然他们本质上是一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能够胡乱标记。
4、AOP
4.1、AOP概念及相关术语
AOP(Aspect Oriented Programming )(横向抽取机制)是一种设计思想,是软件设计领域中的面向切面编程,他是(oop)(纵向继承机制)面向对象编程的一种补充和完善,它可以通过预编译方式和运行期动态代理方式实现在 在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
3.2、相关术语
3.2.1、横切关注点
从每个方法中抽取出来的同一类非核心业务,在一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
4.2.2、通知(增强)
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
- 前置通知:在被代理的目标方法 前执行;
- 返回通知:在被代理的目标方法 成功结束后才执行(寿终正寝);
- 异常通知:在被代理的目标方法 异常结束后执行(死于非命);
- 后置通知:在被代理的目标方法 最终结束后执行(盖棺定论);
- 环绕通知:使用try…catch…finally结构围绕 整个被代理的目标方法,包括上面死忠通知对应的所有位置。一个环绕相当于上面四个
4.2.3、切面
封装通知方法的类
4.2.4、目标
被代理的目标对象。
4.2.5、代理(AOP帮助创建)
向目标对象应用通知之后创建的代理对象。
4.2.6、连接点
这也是一个纯逻辑概念,不是语法定义的。为了从抽的地方套进去
把方法排成一排,每一个横切位置看出x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。
4.2.7、切入点(从代码层面套)
定位连接点的方式。
每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。
如果把连接点看作数据库中的记录,那么切入点就是查询记录的SQL语句。
Spring的AOP技术可以通过切入点定位到特定的连接点。
切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。
4.3、作用
- 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性
- 嗲吗增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。
4.4、使用注解实现AOP
- 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口,因为这个技术要求 代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
- cglib:通过 继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
- AspectJ:本质上是静态代理, 将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
4.4.1、准备工作
(1)添加依赖
在IOC所需依赖基础上再加入下面依赖即可:
<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
(2)准备代理的目标资源
接口:
public interface Calculator{
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
实现类:
@Component
public class CalculatorPureImpl implements Calculator{
@Override
public int add(int i,int j){
int result = i+j;
System.out.println("方法内部 result="+result);
return result;
}
......
}
(3)创建切面类并配置