Spring简介
使用Spring可以使用简单的JavaBean实现以前EJB实现的功能, Spring是一个IOC和AOP容器框架
- Spring是非侵入式, 开发应用过程中可以不依赖Spring的API
- 依赖注入
- 面向切面变成
- 容器
- 框架
- 一站式: IOC和AOP的基础上可以整合企业开源框架和第三类库
public class HelloWorld {
private String name;
public void setName(String name) {
this.name = name;
}
public void hello() {
System.out.println("hello: " + name);
}
}
public class Main {
public static void main(String[] args) {
// 创建HelloWorld 的一个对象
HelloWorld helloWorld = new HelloWorld();
// 为 name 属性赋值
helloWorld.setName("kong");
// 调用hello 方法
helloWorld.hello();
}
}
复制代码
一个简单的helloWorld程序,其中第一步创建对象 和 第二步为属性赋值可以交给Spring框架来完成
// 1. 创建Spring IOC 容器对象,容器对象会调用构造器并调用HelloWorld类中的setName方法
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 从IOC容器中获取Bean实例
HelloWorld helloWorld = (HelloWorld) ctx.getBean("helloWorld");
// 3. 调用 hello 方法
helloWorld.hello();
复制代码
<bean id="helloWorld" class="com.atguigu.spring.beans.HelloWorld">
<property name="name" value="Spring"></property>
</bean>
复制代码
Spring中的IOC & DI概述
IOC是翻转资源获取的方向, 传统资源查找方式是组件向容器发起请求查找资源, 容器适时的返回资源, 应用了IOC之后,是容器主动将资源推送给它所管理的组件,组件要做的是选择合适的方式来接收资源
DI: 组件以一些预先定义好的方式接受来自容器的资源注入
// 传统方式
A a = getA();
B b = getB();
b.setA(a)
复制代码
// 使用IOC实现
B b = getB();
复制代码
IOC前生
- 分离接口与实现
- 报表生成器: 生成PDF 或 HTML不同类型的报表
- 采用工厂设计模式
- 使用反转控制 Service
##Spring配置Bean Spring属性配置细节 如何配置Bean
-
配置方式: 基于XML文件的格式
<bean id="helloWorld" class="com.atguigu.spring.beans.HelloWorld"> <property name="name" value="Spring"></property> </bean> class: 用于创建 bean的全类名,通过反射方式在IOC中创建bean, 要求bean中必须要有无参数的构造器 id: 用于获取容器中的bean,id值唯一 复制代码
-
Bean的配置方式: 通过全类名(反射)/通过工厂方法(静态工厂方法 & 实例工厂方法)、FactoryBean
-
IOC容器BeanFactory & ApplicationContext概述
- ApplicationContext 代表IOC容器 两种类型的IOC容器实现
- BeanFactory: IOC容器的基本实现
- ApplicationContext: 提供了更多的高级特性,是BeanFactory的子接口
- BeanFactory是Spring框架的基础设施,面向Spring本身, ApplicationContext面向框架的使用者, 应用场合多直接使用ApplicationContext而非底层的BeanFactory
- ApplicationContext 代表IOC容器 两种类型的IOC容器实现
-
依赖注入的方式:
- 属性注入(实际中最常用):
- 通过setter方法注入Bean的属性值或依赖的对象
- 属性注入使用元素,使用name属性制定Bean的属性名称,value属性(或子节点指定属性值)
- 构造器注入:
- 通过构造方法注入Bean的属性值或依赖的对象,保证了Bean实例在实例化后就可以使用
- 构造器注入在
<constructor-arg>
元素里声明属性,没有name属性 - 混合使用
index
type
来指定参数的位置和参数的类型以区分重载的构造器
// 使用value属性值类配置 <constructor-arg value="Audi" index="2"></constructor-arg> // 使用value子节点来配置 <constructor-arg type="int"> <value>250</value> </constructor-arg> 复制代码
- 参数列表 参数类型
- 属性注入(实际中最常用):
-
字面值: 可以用字符串表示的值,可以通过元素标签或value属性进行注入,基本数据类型及其封装类、String等类型都可以采用字面值注入的方式
-
使用property的ref属性建立bean之间的引用关系
- 使用元素建立引用
- 使用内部Bean(不能被外部Bean引用,只能在内部使用): 在属性或构造器中包含Bean的声明,这样的Bean被称为内部Bean
-
使用集合的基本标签进行集合的配置,例如
list
,array
,`` -
配置Properties属性值
-
配置独立的集合bean,以供多个bean进行引用,使用
util
命名空间
<util:list id="cars">
<ref bean="car"/>
<ref bean="car2"/>
</util:list>
复制代码
使用p
命名空间来为bean的属性赋值 <bean id="person" class="com.atguigu.spring.beans.Person" p:age="30" p:name="Queen" p:cars-ref='cars'></bean>
5.Srping 自动装配
什么是自动装配: 在<bean>
的autowire属性里制定自动装配的模式
- byType(根据类型自动装配): 若出现多个类型相同的Bean,Spring无法判定,所以无法进行自动装配
- byName(根据名称自动装配): 必须将目标Bean的名称和属性名设置的完全相同
在Person类当中定义了setter setter里面有名字的信息,
自动装配的缺点:
- 必须所有的属性都使用自动装撇
- 使用了装配方式只能是byName 或 byType,不够灵活
- 一般情况下很少使用自动装配功能,明确清晰的配置文档更好
6.Spring Bean之间的关系
继承关系: 配置上的继承关系 bean配置的重用 使用<parent>
关键字
依赖关系: 某些bean要在前面创建 depends-on
关键字 比如要求在配置Person时,必须要有一个关联的car! 换句话说person这个bean依赖于Car这个bean
7.Spring Bean的作用域
使用scope
来设定作用域 默认情况下是单例模式,相同类型的bean都是同样的 prototype: 原型 容器初始化时不创建bean实例,而在每次请求时都创建一个新的Bean实例,并返回
8.Spring使用外部属性文件
比如需要用到系统部署的细节信息(比如文件路径),这些信息需要和Spring文件分离
使用PropertyPlaceholderConfigurer的BeanFactory 后置处理器,该处理器允许用户将Bean配置的部分内容外移动到属性文件中,可以在Bean配置文件中使用 ${var}
变量来从外部属性文件中加载属性,并使用这些属性来替换变量
9. Spring SpEL语句
可以使用SpEL语句进行扩展性的操作:
- 引用类的静态属性
- 使用SpEL 语句来应用其他的Bean、其他Bean的属性、
- 使用运算符
10.Spring 管理Bean的声明周期
Spring允许在Bean声明周期的特定点执行定制的任务 SpringIOC 容器对Bean的声明周期进行管理的过程:
- 通过构造器或工厂方法来创建Bean实例
- 为Bean的属性设置值和对其它Bean的引用
- 调用Bean的初始化方法
- 使用Bean
- 当容器关闭时,调用Bean的销毁方法
在Bean的声明中设置init-method
和destroy-method
属性,为Bean指定 初始化和销毁方法
在类里面先将init方法(名字可以自定义,保证对应就行)定义好,
<bean id="car" class="..." init-method="init" destroy-method="destroy">
复制代码
ctx.close()
: 关闭IOC容器
创建Bean后置处理器 Bean后置处理器允许在调用初始化方法前后对Bean进行额外的处理
通过工厂方法配置Bean的
静态工厂方法: 直接调用某一个类的静态方法就可以返回Bean的实例
class
: 指向静态工厂方法的全类名factory-method
关键字: 指向静态工厂方法的名字constructor-arg
: 如果工厂方法需要传入参数,则使用constructor-arg来配置参数
<bean id="car1" class="..." factory-method="getCar">
<constructor-arg value="audi"></constructor-arg>
</bean>
复制代码
实例工厂方法:: 先要创建工厂本身,再通过调用工厂
factory-bean
: 指向实例工厂方法的beanfactory-method
关键字定义工厂bean的方法
<bean id="carFactory" class="..."></bean>
<bean id="car2" factory-bean="" factory-method="..">
复制代码
11. 使用Spring的FactoryBean配置Bean
FactoryBean 是Spring提供的一个接口
- 自定义的FactoryBean需要实现FactoryBean
class
: 指向FactoryBean的全雷鸣property
: 配置FactoryBean的属性 实际返回的实例是FactoryBean的getObject() 返回的实例 便于有时候配置Bean的时候用到Spring当中的其它Bean- getObject()
- getObjectType()
- isSingleton()
13. 基于注解配置Bean
在classpath中扫描组件
@Component
: 基本注解,标识一个Spring管理的组件@Respository
: 标识持久层组件@Service
: 标识服务层(业务层)@Controller
: 表现层组件 Spring默认命名策略: 使用非限定类名时,第一个字母小写, 也可以在注解中通过value属性值标识组件的名称
需要在Spring配置文件中声明<context:component-scan>
来限定扫描哪些包
指定一个需要扫描的基类包,Spring容器会扫描基类包以及其子包中的所有类,多个包可以通过逗号分隔 context:exclude-filter
: 排除指定组件 context:include-filter
: 包含表达式组件
14. 基于注解配置Bean2
16. Spring AOP基础
一个计算器接口,
需要完成两个额外的需求, 生成日志 + 进行验证
日志代码差不多 核心代码混乱,越来越多非业务需求,每个方法在处理 还要兼顾其他多个关注点
代码分散: 只是为了满足一个单一需求,不得不在多个模块(方法)里多次重复相同的日志代码,如果日志需求发生变化,必须修改所有模块.
AOP方式解决
动态代理解决 代理设计模式: 使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理
public class ArithmeticCalculatorLoggingProxy {
//要代理的对象
private ArithmeticCalculator target;
public ArithmeticCalculator getLoggingProxy() {
ArithmeticCalculator proxy = null;
// 代理对象由哪一个类加载器负责加载
ClassLoader loader = target.getClass().getClassLoader();
// 代理对象的类型,即其中有哪些方法
Class[] interfaces = new Class[]{ArithmeticCalculator.class};
// 当调用代理对象其中的方法时,需要执行的代码
InvocationHandler h = new InvocationHandler() {
/**
* @param: proxy: 正在返回的代理对象,一般情况下,invoke方法中不使用
* @param: method: 正在被调用的方法
* @args: 调用方法时,传入的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke...");
return 0;
}
};
proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h);
return proxy;
}
}
复制代码
动态是什么意思?体现在哪里?
横切关注点方法 在应用AOP时,需要定义公共功能,但可以明确定义功能在哪里,以何种方式应用,并且不必修改受影响的类.
AOP的好处:
- 每个事务逻辑位于一个位置,代码不分散,便于维护和升级
- 业务模块更简洁,只包含核心业务代码
计算器加减乘除的例子:
切面: 通知: 切面中的每一个方法就是通知 连接点: 程序执行的某个特定位置 一个具体的存在 切点: 看不见 通过切点定位到特定连接点
Spring 前置通知
在配置文件中加入AOP的命名空间 xmlns:aop="..."
基于注解的方式: 在配置文件中加入如下配置: 把横切关注点的代码抽象到切面的类中 切面首先是一个Java bean,需要加入@Component
注解 使用@Aspect
注解标注出切面,
- 提供5个注解来定义通知
@Before
: 方法执行前@After
: 方法执行后@AfterReturning
: 通知方法在目标方法返回后调用@AfterThrowing
: 通知方法在目标方法抛出异常后调用@Around
: 环绕
execution() *
可以表示任意修饰符及任意返回值
20. Spring切面的优先级
使用@Order
指定切面的优先级,值越小,优先级越高
21. 重用切点表达式
重新用execution 那一坨东西 定义一个方法,用于声明切入点表达式, 之后再用到该切点的地方就直接调用该方法即可声明对应切点
@Pointcut("...")
public void declareJointPointExpression(){}
复制代码
22. 基于配置文件声明切面
<aop:config>
<aop:aspect ref="audience">
<aop:before
pointcut="..." method="方法名"/>
<aop: after-returnning
pointcut="execution(...)"/>
</aop:aspect>
</aop:config>
复制代码
25. Spring事务准备
必须为不同的方法重写类似的样板代码,存在大量的重复部分
public void purchase(String isbn, String username) {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false);
...
conn.commit();
} catch (SQLException e) {
e.printStackTrace();
if (conn != null) {
try {
conn.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
throw new RuntimeException(e);
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
复制代码
编程式 声明式
Spring
配置事务管理器 启用事务注解 @Transactional