目录
Spring框架概述
可以解决对象创建以及对象之间依赖关系的一种框架。
且可以和其他框架一起使用:Spring与Struts, Spring与hibernate(起到整合/粘合作用的一个框架)
Spring 架构
1) Spring Core Spring的核心功能: IOC容器, 解决对象创建及依赖关系
2) Spring AOP 切面编程
3) Spring DAO Spring 对jdbc操作的支持 【JdbcTemplate模板工具类】
4) Spring ORM Spring对orm的支持:
--> 既可以与hibernate整合,【session】
--> 也可以使用spring的对hibernate操作的封装
5) Spring Web Spring对web模块的支持:建立在上下文模块之上
--> 可以与struts整合,让struts的action创建交给spring
--> spring mvc模式
6) Spring EE Spring 对javaEE其他模块的支持
组件/框架设计
侵入式设计
引入了框架,对现有的类的结构有影响;即需要实现或继承某些特定类。
例如: Struts框架
非侵入式设计
引入了框架,对现有的类结构没有影响。
例如:Hibernate框架 / Spring框架
1.控制反转 IOC( Inversion on Control ):把对象的创建交给外部容器完成,这个就做控制反转
控制反转是一种设计思想,不是技术 . 控制了对象对外部资源的获取
2.依赖注入 DI (dependency injection):处理对象的依赖关系
控制反转, 解决对象创建的问题 【对象创建交给别人】
依赖注入:在创建完对象后, 对象的关系的处理就是依赖注入 【通过set方法依赖注入】
传统的项目: A类依赖B类, 就会自己去创建B类对象,
spring IOC把对象的创建都交给容器, B对象由IOC容器创建,然后DI依赖注入给A类
3.AOP 切面
面向切面编程。简单来说来可以理解为把很多重复代码抽出来形成的类。
切面举例:事务、日志、权限;
IOC/DI概述
使用场景:
A对象依赖B对象,B对象依赖C对象,C对象依赖D对象
要创建A对象就得new B,要new B就得new C,要new C就得new D......
- 问题1:依赖对象要多次创建
使用反射 单例模式 或者 工厂模式 统一创建对象,统一管理 (控制反转)
- 问题2:每个对象内部都要创建它的依赖对象,依赖关系复杂,
不用内部自己创建依赖对象,从外部传入依赖对象(依赖注入)
spring核心内容。作用: 创建对象 & 处理对象的依赖关系
原理: 把对象的信息写在配置文件, 读取配置文件,然后用反射创建对象
IOC 容器:
BeanFactory 容器
流程: 开发一个javabean, 然后配置bean的参数, 就可以在类中通过spring获取bean的对象,调用bean的方法
配置bean元数据三种方法:
- 基于 XML 的配置文件
- 基于注解的配置
- @Component @ComponentScan
- @Bean
- @Import
- 基于 Java 的配置
FactoryBean, 创建bean的工厂
一. 基于xml配置:
创建一个xml配置文件,
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="......" xmlns:xsi="......" xsi:schemaLocation="......">
<!-- 一个简单的bean定义 -->
<bean id="bean的别名" class="javabean文件的包名.类名">
<!-- bean的配置 -->
</bean>
<!-- 设置了lazy init的bean定义 -->
<bean id="..." class="..." lazy-init="true">
<!-- bean的配置 -->
</bean>
<!-- 生命周期 -->
<!-- 具有初始化方法的bean定义 -->
<bean id="..." class="..." init-method="方法名">
<!-- bean的配置 -->
</bean>
<!-- 具有销毁方法的bean定义 -->
<bean id="..." class="..." destroy-method="方法名">
<!-- bean的配置 -->
</bean>
<!-- 作用域 -->
<!-- 单例作用域的bean定义 -->
<bean id="..." class="..." scope="singleton">
<!-- bean的配置 -->
</bean>
<!-- 原型作用域的bean定义 -->
<bean id="..." class="..." scope="prototype">
<!-- bean的配置 -->
</bean>
<!-- 继承 父定义的配置数据 -->
<bean id="..." class="..." parent="父bean">
<!-- bean的配置 -->
</bean>
</beans>
属性 | 描述 |
---|---|
class | 必输,指定用来创建 bean 的 java类。 |
id / name | 别名, 指定唯一的 bean 标识符。id是唯一的,, name可以不唯一 |
scope | 指定由特定的 bean 定义创建的对象的作用域, |
constructor-arg | 注入依赖关系,通过构造函数注入 |
properties | 注入依赖关系,通过setxx方法注入 |
autowiring mode | 注入依赖关系,自动装配 |
lazy-initialization mode | 懒汉单例模式, 延迟初始化 bean , IoC 容器在它第一次被请求时而不是在启动时去创建bean 实例。 |
initialization 方法 | 在 bean 的所有必需的属性被容器设置之后,调用回调方法。 |
destruction 方法 | 当包含该 bean 的容器被销毁时,使用回调方法。 |
作用域 | 描述 |
---|---|
singleton | 单例模式: 在spring IoC容器仅存在一个Bean实例,默认值 |
prototype | 原型模式: 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean() |
request | WEB应用: 每次HTTP请求都会创建一个新的Bean |
session | WEB应用: 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean |
global-session | WEB应用: 一般用于Portlet应用环境 |
在方法中读取配置文件,获取bean
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("xml配置文件");
A a = (A) context.getBean("bean的id");
}
}
工厂类对象spring管理
// 创建工厂类ObjectFactory ,作用创建对象
public class ObjectFactory {
// 实例方法创建对象
public User getInstance() {
return new User(100,"工厂:调用实例方法");
}
// 静态方法创建对象
public static User getStaticInstance() {
return new User(101,"工厂:调用静态方法");
}
}
<!-- 工厂类的xml配置 -->
<!-- 先创建工厂 -->
<bean id="factory" class="包名.ObjectFactory"></bean>
<!-- 再创建user对象 -->
<!-- # 3.1 工厂类,实例方法 -->
<bean id="user3" factory-bean="factory" factory-method="getInstance"></bean>
<!-- # 3.2 工厂类: 静态方法 -->
<bean id="user4" class="包名.ObjectFactory" factory-method="getStaticInstance"></bean>
DI 依赖注入:
给对象的属性赋值
在xml文件中配置 各个bean的依赖关系, 然后在java代码中用ClassPathXmlApplicationContext直接获取对象
依赖注入3/4种方式: 其实原理都是通过 赋值方法 或 属性反射 赋值
- 通过构造函数传参 (依赖关系无变化的场景,尽量采用)
- 通过set方法给属性传参 (简单直观,大量使用)
- 属性反射 field.set()
- 静态工厂方法
- 实例工厂方法
例子: 有3个类, 用spring处理他们的依赖关系
- 主函数:需要创建A类对象
- A类:依赖B类对象
- B类:普通类,无其他依赖关系
1. 构造函数注入
A类 通过构造函数获取 B类对象 与其他参数
/*-----------------------------------------------------------------------*/
public class A {
private B b;
private int year;
private String name;
//A类构造函数 获取B对象及year和name参数
public A(B b, int year, String name) {
this.b = b;
this.year = year;
this.name = name;
}
public void b() {
b.Method();
}
}
基于构造函数注入的xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="..." xmlns:xsi="..." xsi:schemaLocation="...">
<!-- a bean 传参数 -->
<bean id="a" class="A类路径">
<constructor-arg index="0" ref="b"/>
<constructor-arg index="1" type="int" value="2019"/>
<constructor-arg index="2" type="java.lang.String" value="Allen"/>
</bean>
<!-- b bean 无参数 -->
<bean id="b" class="B类路径">
</bean>
</beans>
把一个引用传递给一个对象要使用标签的 ref 属性,而如果你要直接传递一个值使用 value 属性。
2. set方法注入
A类 通过get,set方法获取 B类对象 与其他参数
/*-----------------------------------------------------------------------*/
public class A {
private B b;
private int year;
private String name;
public void setB(B b) {
this.b = b;
}
public SpellChecker getB() {
return b;
}
......year,name,set(),get()方法
public void b() {
b.Method();
}
}
于set函数注入的xml配置
只改〈constructor-arg〉标签就行
<bean id="a" class="A类路径">
<property name="b" ref="b"/>
<property name="year" value="2019"/>
<property name="name" value="Allen"/>
</bean>
<bean id="b" class="B类路径">
</bean>
基于构造函数注入和基于set函数注入配置文件 唯一的区别就是 用的是〈bean〉标签中的〈constructor-arg〉元素还是〈property〉元素
3 其他写法
P名称空间
如果有许多set方法, 那么在 XML 配置文件中使用 p-namespace 是非常方便
<bean id="a" class="A类路径"
p:b-ref="b"
p:name="Allen"
p:year="2019"/>
</bean>
内部bean的注入
使用内部bean,相当于局部变量 就不用单独创建一个B类的bean标签了
<bean id="a" class="A类路径">
<property name="b">
<bean id="b" class="B类路径"/>
</property>
</bean>
注入集合
public class JavaCollection {
List list;
Set set;
Map map;
Properties prop;
set...
get...
......
}
<bean id="javaCollection" class="JavaCollection类路径">
<property name="list">
<list>
<value>1</value>
<ref bean="beanName"/>
<property name="email" value=""/> 空字符串
<property name="email"><null/></property> NULL值
</list>
</property>
<property name="set">
<set>
<value>1</value>
<ref bean="beanName"/>
</set>
</property>
<property name="map">
<map>
<entry key="1" value="1"/>
<entry key="2" value-ref="beanName"/>
</map>
</property>
<property name="prop">
<props>
<prop key="one">1</prop>
<prop key="two">2</prop>
</props>
</property>
</bean>
4. 自动装配
Spring 容器可以在不使用<constructor-arg>
和<property>
元素的情况下自动装配相互协作的 bean 之间的关系
依赖查找:byType byName
Spring提供的自动装配主要是为了简化配置; 但是不利于后期的维护。(一般不推荐使用)
限制 | 描述 |
---|---|
重写的可能性 | 你可以使用总是重写自动装配的 <constructor-arg>和 <property> 设置来指定依赖关系。 |
原始数据类型 | 你不能自动装配所谓的简单类型包括基本类型,字符串和类。 |
混乱的本质 | 自动装配不如显式装配精确,所以如果可能的话尽可能使用显式装配。 |
使用<bean>
元素的 autowire 属性
模式 | 描述 |
---|---|
no | 这是默认的设置,它意味着没有自动装配,你应该使用显式的bean引用来连线。你不用为了连线做特殊的事 |
byName | 由属性名自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byName。然后尝试匹配,并且将它的属性与在配置文件中被定义为相同名称的 beans 的属性进行连接。 |
byType | 由属性数据类型自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byType。然后如果它的类型匹配配置文件中的一个确切的 bean 名称,它将尝试匹配和连接属性的类型。如果存在不止一个这样的 bean,则一个致命的异常将会被抛出。 |
constructor | 类似于 byType,但该类型适用于构造函数参数类型。如果在容器中没有一个构造函数参数类型的 bean,则一个致命错误将会发生。 |
autodetect | Spring首先尝试通过 constructor 使用自动装配来连接,如果它不执行,Spring 尝试通过 byType 来自动装配。 |
4.1)根据名称自动装配:【autowire="byName"】(自动去IOC容器中找与属性名同名的引用的对象,并自动注入)
将A的属性与配置文件中定义为相同名称的 beans 进行匹配和连接。如果找到匹配项,它将注入这些 beans,否则,它将抛出异常
<bean id="a" class="A类对象" autowire="byName">
<!-- 不用写b类的bean了,根据名字自动装配 -->
<property name="year" value="2019"/>
<property name="name" value="Allen"/>
</bean>
也可以定义到全局beans中【default-autowire="byName"】,就不用在每个bean节点中配置了
4.2)根据类型自动装配:【autowire="byType"】和属性名没关系,但必须确保改类型在IOC容器中只有一个对象;否则报错。
<bean id="a" class="A类地址" autowire="byType">
<!-- 不用写b类的bean了,根据TYPE自动装配 -->
<property name="year" value="2019"/>
<property name="name" value="Allen"/>
</bean>
4.3)构造函数自动装配:【autowire="constructor"】与byType非常相似, 把它构造函数的参数与配置文件中 beans 名称中的一个进行匹配和连线。如果找到匹配项它会注入这些 bean,否则它会抛出异常。
<bean id="a" class="A类地址" autowire="constructor">
<!-- 不用写b类的bean了,根据构造函数去自动装配 -->
<property name="year" value="2019"/>
<property name="name" value="Allen"/>
</bean>
Java代码使用
主方法 通过spring获取A对象,用A对象调用B类方法
/*-----------------------------------------------------------------------*/
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("xml配置文件");
A a = (A) context.getBean("a");
a.b();
}
}
B类
/*-----------------------------------------------------------------------*/
public class B{
public B(){
//B类构造函数
}
public void Method() {
//B类方法
}
}
二. 使用注解实现
使用注解,可以简化配置,使用相关类方法或字段声明的注解,将 bean 配置移动到组件类本身。
使用注解步骤:
5.1)注解连线在默认情况下在 Spring 容器中不打开 . 在xml配置中:引入context名称空间;开启注解扫描;
<beans ......>
<!-- 需要先配置Xml注册Bean, 让已经注册的bean开始工作 -->
<context:annotation-config/>
<!-- 不需要注册其, 作用是告诉Spring,bean都放在这个包下, 优先级大于annotation-config -->
<context:component-scan base-package="包名(扫描此包名下的所有类)"></context:component-scan>
</beans>
5.2)在java文件中使用注解通过注解的方式:可把对象加入ioc容器,或从ioc容器中取出对象注入。
@Required 用于 bean 属性的 setter 方法,
@Autowired 可以应用到 bean 属性的 setter 方法,非 setter 方法,构造函数和属性。
@Qualifier 通过指定确切的将被连线的 bean,
@Autowired 和 @Qualifier 注解可以用来删除混乱。
基于java相关的注解:
@Component 指定把一个类加入IOC容器,表明此类是bean的来源
@Bean 将带有 @Bean 注解的方法名称作为 bean 的 ID, 创建并返回实际的 bean
@import 从另一个配置类中加载 @Bean 定义
@Resource 从IOC容器中找出属性注入
案例开发步骤:
spring各个版本中:
在3.0以下的版本,源码有spring中相关的所有包【spring功能 + 依赖包】
在3.0以上的版本,源码中只有spring的核心功能包【没有依赖包】(如果要用依赖包,需要单独下载!)
例:创建javabean:User(id,name)
1) 导入源码, jar文件:
spring-framework-3.2.5.RELEASE
commons-logging-1.1.3.jar 日志
spring-beans-3.2.5.RELEASE.jar bean节点
spring-context-3.2.5.RELEASE.jar spring上下文节点
spring-core-3.2.5.RELEASE.jar spring核心功能
spring-expression-3.2.5.RELEASE.jar spring表达式相关表
注:以上是必须引入的5个jar文件,在项目中可以用用户库管理!
2) 创建核心xml配置文件:
Spring配置文件:name.xml
通过bean的方式把对象加入IOC
name.xml
<beans xmlns=...>
<!-- IOC容器的配置: 要创建的所有的对象都配置在这里 -->
<bean id="user" class="javabean文件的包名.类名" init-method="init_user" destroy-method="destroy_user" scope="singleton" lazy-init="false">
<property name="name" value="张三" />
</bean>
</beans>
3)调用API
// 1. 通过工厂类得到IOC容器创建的对象
public void testIOC() throws Exception {
// 不自己创建对象
// 把对象的创建交给spring的IOC容器
Resource resource = new ClassPathResource("配置文件路径/name.xml");
// 创建容器对象(Bean的工厂), IOC容器 = 工厂类 + name.xml
BeanFactory factory = new XmlBeanFactory(resource);
// 得到容器创建的对象
User user = (User) factory.getBean("user");
}
// *2. (推荐)直接得到IOC容器对象
public void testAc() throws Exception {
// 得到IOC容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("配置文件路径/name.xml");
// 根据id属性从容器中获取bean
User user = (User) ac.getBean("user");
//对象的name值为 "张三"
System.out.println(user.getName());
// 销毁容器对象
ac.destroy();
}
AOP编程
Spring AOP 模块提供拦截器来拦截一个应用程序,当执行一个方法时,你可以在方法执行之前或之后添加额外的功能。
在Spring的面向切面编程AOP思想里面,把功能分为核心业务功能,和周边功能。
核心业务:比如登陆,增加数据,删除数据都叫核心业务
周边功能【切面】:比如性能统计,日志,事务管理等等
AOP( aspect object programming)面向切面编程
核心业务功能和切面功能分别独立进行开发, 然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP。
切入点:执行目标对象方法,动态植入切面代码。
可以通过切入点表达式,指定拦截哪些类的哪些方法; 给指定的类在运行的时候植入切面类代码。
通知 | 描述 |
---|---|
前置通知 | 在一个方法执行之前,执行通知。 |
后置通知 | 在一个方法执行之后,不考虑其结果,执行通知。 |
返回后通知 | 在一个方法执行之后,只有在方法成功完成时,才能执行通知。 |
抛出异常后通知 | 在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。 |
环绕通知 | 在建议方法调用之前和之后,执行通知。 |
先引入aop相关jar文件 (aspectj aop优秀组件)
spring-aop-3.2.5.RELEASE.jar 【spring3.2源码】
aopalliance.jar 【spring2.5源码/lib/aopalliance】
aspectjweaver.jar 【spring2.5源码/lib/aspectj】或【aspectj-1.8.2\lib】
aspectjrt.jar 【spring2.5源码/lib/aspectj】或【aspectj-1.8.2\lib】
注意:用到spring2.5版本的jar文件,如果用jdk1.7可能会有问题。需要升级aspectj组件,即使用aspectj-1.8.2版本中提供jar文件提供。
XML方式实现AOP编程:
1).核心业务功能 ProductService
public class ProductService {
public int doSomeService(){
System.out.println("---执行核心业务功能");
return 1;
}
}
2).日志切面 LoggerAspect
public class Logging {
//在选定的方法执行之前
public void beforeAdvice(){
System.out.println("方法执行前");
}
//在选定的方法执行之后
public void afterAdvice(){
System.out.println("方法执行后");
}
//当任何方法返回时
public void afterReturningAdvice(Object retVal){
System.out.println("返回值:" + retVal.toString() );
}
//出现异常
public void AfterThrowingAdvice(IllegalArgumentException ex){
System.out.println("出现异常: " + ex.toString());
}
//环绕通知 相当于 前+后
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕执行前:" + joinPoint.getSignature().getName());
//AOP环绕通知,用来执行核心代码
Object object = joinPoint.proceed();
System.out.println("环绕执行后:" + joinPoint.getSignature().getName());
return object;
}
}
3). 配置文件 applicationContext.xml
<beans xmlns=...>
<!-- 业务bean -->
<bean id="service" class="业务类路径"/>
<!-- 切面aspect -->
<bean id="loggerAspect" class="日志类路径"/>
<!-- aop:config把业务对象与辅助功能编织在一起 -->
<aop:config>
<aop:aspect id="log" ref="loggerAspect">
<!-- 声明一个切点, 表示对满足以com包开头任意类 任意方法 任意参数 进行切面操作 -->
<aop:pointcut id="serviceCutpoint" expression="execution(* com.*.*(..))"/>
<!-- before定义 切点执行前 调用log切面的方法 -->
<aop:before pointcut-ref="serviceCutpoint" method="beforeAdvice"/>
<!-- after定义 切点执行后 调用方法-->
<aop:after pointcut-ref="serviceCutpoint" method="afterAdvice"/>
<!-- after-returning定义 切点有返回 调用方法-->
<aop:after-returning pointcut-ref="serviceCutpoint"
returning="retVal"
method="afterReturningAdvice"/>
<!-- after-throwing定义 切点异常 调用方法-->
<aop:after-throwing pointcut-ref="serviceCutpoint"
throwing="ex"
method="AfterThrowingAdvice"/>
<!-- around定义 环绕通知 前+后 -->
<aop:around pointcut-ref="serviceCutpoint" method="doAround"/>
</aop:aspect>
</aop:config>
</beans>
执行顺序是 @Around→@Before→@After→@Around→@AfterRunning (如果有异常→@AfterThrowing)
测试:
public class TestSpring {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"xml配置文件路径"});
ProductService s = (ProductService) context.getBean("service");
s.doSomeService();
}
}
控制台打印:
环绕执行前::doSomeService
方法执行前
---执行核心业务功能
方法执行后
环绕执行后:doSomeService
返回值:1
注解方式实现AOP编程
1)注解配置业务类
使用@Component("s") 注解ProductService 类
package com.service;
import org.springframework.stereotype.Component;
@Component("s")
public class ProductService {
public void doSomeService(){
System.out.println("核心业务功能");
}
}
2)注解配置切面
@Aspect 表示这是一个切面
@Component 表示这是一个bean,由Spring进行管理
@Around(value = "execution(* com.service.ProductService.*(..))") 表示对com.service.ProductService 这个类中的所有方法进行切面操作
package com.aspect;
import ······;
@Aspect
@Component
public class LoggerAspect {
@Around(value = "execution(* com.service.ProductService.*(..))")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("start 切面:" + joinPoint.getSignature().getName());
Object object = joinPoint.proceed();
System.out.println("end 切面:" + joinPoint.getSignature().getName());
return object;
}
}
3)applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans ······>
<context:component-scan base-package="com.aspect"/>
<context:component-scan base-package="com.service"/>
<!-- 扫描包com.aspect和com.service,定位业务类和切面类 -->
<aop:aspectj-autoproxy/>
<!-- 找到被注解了的切面类,进行切面配置 -->
</beans>
4)测试
public class TestSpring {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { "applicationContext.xml" });
ProductService s = (ProductService) context.getBean("s");
s.doSomeService();
}
}
控制台打印:
start 切面:doSomeService
核心业务功能
end 切面:doSomeService
注解详解
@Aspect 指定一个类为切面类
@Pointcut("execution(* cn.itcast.e_aop_anno.*.*(..))") 指定切入点表达式
@Before("pointCut_()") 前置通知: 目标方法之前执行
@After("pointCut_()") 后置通知:目标方法之后执行(始终执行)
@AfterReturning("pointCut_()") 返回后通知: 执行方法结束前执行(异常不执行)
@AfterThrowing("pointCut_()") 异常通知: 出现异常时候执行
@Around("pointCut_()") 环绕通知: 环绕目标方法执行
切入点表达式