一。Spring简介
1.Spring是一个轻量级的javaEE解决方案,是众多优秀设计模式的整合
Spring 框架中使用了了工厂(factory) 代理(proxy) 模板(template)三种模式
我们可以认为Spring的核心就是工厂
2.工厂设计模式
作用:工厂设计模式的主要作用就是生产对象(创建对象)
好处:解耦和
耦合:代码之间的强关联性体现在 代码的修改,相互影响
工厂设计模式的应用:建立一个工厂类,由工厂来负责对象的创建 在工厂中利用反射获取类的对象
注意:工厂全部都是重量级资源:占内存多 功能强 一个应用只需创建一个工厂并确保线程安全
3.Spring工厂的开发步骤
① 创建类
② 配置Spring的配置文件applicationContext.xml中写
<bean id="userDAO" class="userDAO的权限定名"></bean>
③ 使用步骤(在java类中)
//创建工厂
ApplicationConText ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
//获得对象
Account acc = (Account)ctx.getBean("userDAO");
4.Spring工厂创建对象的原理
① 通过读取sqring的配置文件 获取权限定名 在通过反射调用无参构造方法创建类的对象
② FactoryBean
作用:在spring工厂中用于创建复杂的对象
【1】简单对象
可以直接通过构造方法创建的对象
【2】复杂对象(接口类型)
不能通过 new 运算符直接创建对象
[1]FactoryBean的开发步骤:实现FactoryBean接口 实现内部规定的方法
public Object getObject(){
///作用:用于书写创建赋值对象的代码,并把创建的赋值对象作为方法的返回值
}
public Class getObjectType(){
///返回创建的复杂对象的Class对象
}
public boolean isSingleton(){
//控制复杂对象的创建次数
//返回true意味着Spring工厂只会创建一个复杂对象
//返回false意味着每一次调用Spring工厂都会创建一个新的复杂对象
//根据所要创建的对象特性来确定返回true或false
}
【3】在FactoryBean应用过程中,可以通过 &id 的形式获得Class类型的对象
例:
ApplicationContext ctx = new ClassPathXmlApplicationContext("/application.properties");
ConnectionFactoryBean cfb = (ConnectionFactoryBean)ctx.getBean("&conn");
③ 控制简单对象的创建次数
例:<bean id="xx" class="xx" scope="xx">
当scope="singleton" 那么这个简单对象只会被创建一次
当scope="prototype" 那么这个简单对象每一次都会创建一个新的对象
默认值为:singleton
【1】Spring为什么会控制对象的创建次数
[1]对象创建会拖延程序的运行效率
[2]对象创建过多会占用过多的有限内存资源
所以Spring控制对象的创建次数可以提高程序的运行效率,减少内存的不必要浪费
【2】哪些对象需要创建多次 哪些对象只需要创建一次
[1]只创建一次的对象
例:SqlSessionFactory XXXDAO XXXService
[2]需要创建多次的对象
Connection ShopingCart Action(struts2的)
在bean标签中添加一个scope属性
④ 工厂创建对象的生命周期(单例类型的对象)
【1】对象什么时候被工厂创建
工厂创建时,对象被创建
【2】对象什么时候被工厂销毁
工厂被关闭的时候,对象被销毁
【3】生命周期方法
[1]对象中任意定义一个初始化方法,Spring会在这个对象创建之后,自动调用初始化方法中的功能
在实体类中定义一个方法后 在配置文件中的bean标签中 添加一个属性 init-method="方法名"
这样Spring会在对象被创建后立即执行这个方法
[2]对象中任意定义一个销毁方法,Spring会在这个对象销毁之前,自动调用销毁方法中的功能
在实体类中定义一个方法后 在配置文件中的bean标签中 添加一个属性 destroy-method="方法名"
这样Spring会在对象被销毁之前调用这个方法
⑤ 配置信息参数化
作用:把Spring配置文件中,哪些需要经常修改的字符串,从Spring配置文件中转移到小的配置文件中
好处:利于后续的维护
【1】开发步骤
[1]准备小配置文件 :jdbc.properties
[2]小配置文件与Spring的配置文件整合
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd"
>
导入context包指定小配置文件路径
<context:property-placeholder location="classpath:/jdbc.properties"/>
在Connection的value标签中用展位符${driver}这种形式通过键获取小配置文件中的值
注意:Spring的小配置文件中的用户名不能使用username
【2】自定义类型转换器(SpringMVC)
【3】BeanPostProccesor(后置处理Bean)(AOP底层实现)
5.注入(Injection)
定义:通过Spring的配置文件为对象的成员变量赋值;
① 为实体类中的成员变量提供get/set方法
② 在bean标签中添加两个标签
例:<bean id="usr" class="com.user">/*调用无参构造方法创建类的对象*/
<property name="id">/*调用前set方法为指定属性赋值*/
<value>1</value>
</property>
<property name="name">/*指定要赋值的属性*/
<value>wangyongqi</value>/*指定属性*/
</property>
</bean>
6.set注入详解
① JDK类型成员变量
【1】8种基本类型+包装类+String
利用property标签中的value标签使用set方法赋值
<property name="name">/*指定要赋值的属性名*/
<value>wangyongqi</value>/*指定属性值*/
</property>
【2】数组类型或List集合的成员变量
利用property标签中的list标签表示数组 在list标签内部使用value标签代表数组中的一个元素
<property name="name">/*指定要赋值的属性名*/
<list>
<value>wangyongqi1</value>/*指定属性值*/
<value>wangyongqi2</value>/*指定属性值*/
</list>
</property>
【3】Set集合
在property标签中使用set标签表示set集合 根据具体的使用情况在set中嵌套相应的标签来赋值
<property name="name">/*指定要赋值的属性名*/
<set>
<value>wangyongqi1</value>/*指定属性值*/
<value>wangyongqi2</value>/*指定属性值*/
</set>
</property>
【4】Map集合
在property中添加一个map标签 在map中添加entry标签代表其中的一个键值对
【5】Properties
特殊的Map:key和value都是String类型 默认有11个键值对
使用props来表示properties集合 其中写prop标签表示其中的一个键值对
例:<props>
<prop key="键名">值</prop>
</props>
② 用户自建(自定义)成员变量
<bean id="userDAO" class="com.yongqi.wang.UserDAOImpl"></bean>
<bean id="userService" class="com.yongqi.wang.UserServiceImpl">
<property name="userDAO">
<ref local="userDAO"/>/*引用当前配置文件中创建好的类来给属性赋值 参数为id*/
</property>
</bean>
string的配置文件中配置的配置顺序没有任何关系
③ 构造注入
【1】Spring通过构造方法完成对象成员变量的赋值
前提:提供有参的构造方法
<bean id="userService" class="com.baizhi.UserServiceImpl">
<constructor-arg>//一对标签代表一个构造参数,标签个数与参数个数一致,赋值顺序与构造方法参数顺序一致
xxx //值的外部标签与值的类型相关
</constructor-arg>
<constructor-arg>
xxxx
</constructor-arg>
</bean>
【2】构造方法可以重载
如果构造参数个数不同时,可以通过<constructor-arg>标签的个数来进行区分
如果参数的个数相同时,可以通过<constructor-arg type="int">通过type属性来指定参数类型
④ 自动注入
只适用于自建类型
例:<bean id="userDAO" class="xxx.UserDAOImpl"/>
<bean id="userService" class="xxx.UserServcieImpl" autowire="byName"></bean>
/*
当service中有一个自定义类型UserDAO时,spring会通过autowire自动通过
属性名查找配置文件中匹配的id同名类 来自动为成员变量完成赋值
*/
7.Spring中的IOC(反转控制、依赖注入)的概念
① IOC(Inverse of control)反转控制
【1】控制:对于成员变量赋值的控制权;
[1]在没有Spring框架时,由代码来负责成员变量的赋值 对于成员变量赋值的控制权由代码完成
[2]在Spring框架中,由配置文件负责对于成员变量的赋值,控制权转移到了配置文件中
【2】反转控制:把对于成员变量赋值的控制权从代码中转移到了配置文件中完成
【3】优点:解耦和
【4】原理:工厂设计模式
IOC的中文直译为反转控制,指的就是将成员变量的赋值由代码完成,改为在配置文件中完成来达到解耦合的作用,
Spring完成这种处理机制,是通过底层的工厂设计模式完成的
② DI(dependencyinjection)依赖注入
【1】注入:通过配置文件为成员变量赋值
【2】依赖:当一个类型需要使用另一个类型是,就意味着他依赖另一个类型,那么就可以把另一个类型作为成员变量,通过spring的配置文件,进行赋值;
8.AOP编程(Aspect Oriented Program)面向切面编程
【1】静态代理设计模式(Proxy)
一个项目最重要的就是Service层
[1]它的核心功能有:
(1)业务逻辑 (2)DAO层调用
[2]额外功能、附加功能(可有可无)
事务的控制。
日志: 用户重要操作的记录信息
性能检测等等。。。
[3]站在软件设计的角度:不应该在Service中添加附加功能,因为不好维护
站在Service调用者的角度中:应该加入额外功能,最少要在Service中添加控制事务
专有词汇:
原始对象(目标对象):只做核心功能的Service
原始方法:原始对象中的方法,只做核心功能
额外功能:控制事务、日志 等
代理对象 = 原始对象+额外功能+实现与原始对象相同接口
特点:每一个原始类,都要为它创建一个代理类
概念:通过代理类为原始类添加额外功能
优点:利于后续的维护
缺点: (1)造成项目的类过载,不利于项目管理
(2)代理类过多,造成额外功能的代码冗余,不利于项目维护
【2】Spring框架的动态代理
[1]什么是动态代理
以切面为基本单位的程序开发。
切面 = 切入点+额外功能
[2]概念:通过代理类为原始类添加额外功能,切入点决定了代理类中的额外功能添加的位置
[3]优点:利于后续的维护
[4]Spring动态代理的开发步骤
创建原始对象、额外功能、切入点、组装切面
(1)创建原始对象(只做核心功能)
(2)额外功能(Advice 接口)
我们一般不会直接使用Advice接口,而是使用其四个子接口
/*使用这个功能需要引入三个额外jar包 aopaliance.jar aspetcj.jar aspetcwave.jar */
MethodBeforeAdvice接口:当需要的额外功能要运行在原始方法之前执行,则实现这个接口
/**
*@param method代表原始对象中的方法 被切入的方法
*@param args 代表这个方法的参数 由于可能有多个参数所以为对象数组
*@param target 代表原始对象
*/
before(Method method, Object[] args, Object target);
继承AfterReturningAdvice类:当需要的额外功能要运行在原始方法之后执行,覆盖这个方法
/**
*@param returnValue 原始方法的返回值
*@param method 额外功能所增加给的那个原始方法
*@param args 原始方法参数
*@param target 代表原始对象
*/
afterReturning(Object returnValue, Method method, Object[] args, Object target);
MethodInterceptor//不是Spring框架定义的接口,是aopalliance 定义的接口
(环绕 arround)当需要的额外功能要运行在原始方法之前、之后都执行,则实现这个接口
/**
*@param invocation 代表原始方法
*@return Object 原始方法的返回值
*/
Object invoke(MethodInvocation invocation){
方法前运行的业务
invocation.proceed(); //调用这个方法代表原始方法运行
方法后运行的业务
}
ThrowsAdvice:当需要的额外功能要运行在原始方法抛出异常时执行,则实现这个接口
这个接口时一个标识性接口:接口没有定义方法 SerialBlob、ThrowsAdvice、Tag 都是标识性接口
需要按照指定格式书写这个接口中的方法
因为在这个实现接口类中的方法最终都会被反射作用
所以实现public void afterThrowing(Excepiton e){}这个方法
参数为 原始方法抛出的异常
通过实现这些接口来表达额外功能运行的位置
【3】配置切入点(PointCut)
决定了额外功能加入的位置//确定额外功能添加在哪一个方法中
例://表示为所有方法执行前全都加入这个额外功能
<bean id="befor" class="com.yongqi.wang.xxxx 额外功能类"/>
<aop:config>
<aop:pointcut id="pc" expression="execution(* *(..))"/>
<aop:advisor pointcut-ref="pc" advice-ref="before"/>
</aop:config>
(a)方法切入点(方法)
execution() 切入点函数 参数为切入点表达式
切入点表达式 * *(..)
第一个*表示修饰符与返回值 (不关心修饰符返回值)
第二个*表示方法名 (不关心方法名)
..表示参数表中的值 (不关心参数是什么)
可以理解为不关心 或 所有
例:login方法加入额外的功能 * login(..);
login(String name)方法 * login(String);
login(String name,String passwrod)方法 * login(String,String)
或者 * login(String,..)
注意:在匹配参数时,如果参数类型不再java.lang包中,则不需使用权限定名表示
例: * login(com.yongqi.wang.entity.User);
(b)类切入点
一级包下(只有一个包)
* *.UserServiceImpl.*(..)
多级包(在多个包下)
* *..UserSreviceImpl.*(..)
(c)包切入点(常用)
* com.yongqi.wang.*.*(..)//跟import语句一样只导入当前包下的类
* com..*.*(..)//导入com包下的所有子包下面的所有类
(d)其他方法切入
args(String,String) //简化版切入方法 不关心方法名 包名 类名 只要是两个String参数的方法都切入
包与类的信息 替换execution()方法
within(*..UserServcieImpl) //简化版切入方法 不关心这个类到底在哪个包中只关心类是谁
within(com.yongqi.wang..*) //简化版切入方法 不关心类有多少子包 为所有子包中的所有类中的所有方法都切入
@annotation() //把用户指定的注解作为切入点 有注解就切入 没有就不切入
///自定义一个注解类 在要切入的方法上面加上 @注解类名
在配置文件中例:expression="@annotation(注解类的权限定名)"
(e)切入点函数的逻辑运算
与 或 非 and or not
相同的切入点函数不能用and 只能用 or
【4】组装(切入点+额外功能)
如何获得动态代理类:
Spring会通过动态字节码技术,在程序运行的时候,自动创建动态代理类
代理类:
通过原始对象的ID值获得代理对象
代理对象 = ctx.getBean("原始对象id");
【5】Spring AOP 编程的实现原理
(1)动态代理类如何创建出来的
底层通过动态字节码技术,创建出来的代理类
代理:从新实现原始对象的接口,加入额外功能
【6】BeanPostProcessor后置处理Bean
为什么拿原始对象的ID会访问代理对象:这就是后置处理
作用:把工厂对象进行加工之后再交给调用者 实现这个接口 和接口中的两个方法//加工两次
*第一个方法代表工厂创建对象后初始化之前调用加工
*第二个方法代表工厂初始化对象被调用前调用加工
如果没有初始化方法那么实现一个方法就可以
例://模拟AOP 1.3后置处理
public class Bean{
private List<String> methodNames; // get/set
public boolean isContaintMethodName(String methodName){
for(String methodN:methodNames){
if(methodName.equals(methodN)){
return true;
}
}
return false;
}
public Object postProcessAfterInitialization(final Object bean,String arg1)throws BeansException{
InvocationHandler handler = new InvocationHandler(){
@Override
public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
Object ret = null;
if(isContaintMethodName(method.getName())){
System.out.println("...");
ret = method.invoke(bean,args);
}else{
ret = method.invoke(bean,args);
}
return ret;
}
};
}
}