java深入探究12-框架之Spring
1.引入Spring
我们在搭建框架时常常会解决问题:对象创建,对象之间依赖关系如何处理,Spring就是来解决这类问题的:控制反转依赖注入
2.环境搭建
1)下载源码:其中3.0以下版本源码中有Spring相关所有包【核心包+依赖包】
3.0以上版本源码中只有spring核心包
2)导入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文件,在项目中可以用户库管理!
3)核心配置文件applicationContext.xml 和bean的属性描述
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> </beans>
bean属性描述
/** * 1) 对象创建: 单例/多例 * scope="singleton", 默认值, 即 默认是单例 【service/dao/工具类】 * scope="prototype", 多例; 【Action对象】 * * 2) 什么时候创建? * scope="prototype" 在用到对象的时候,才创建对象。 * scope="singleton" 在启动(容器初始化之前), 就已经创建了bean,且整个应用只有一个。 * 3)是否延迟创建 * lazy-init="false" 默认为false, 不延迟创建,即在启动时候就创建对象 * lazy-init="true" 延迟初始化, 在用到对象的时候才创建对象 * (只对单例有效) * 4) 创建对象之后,初始化/销毁 * init-method="init_user" 【对应对象的init_user方法,在对象创建爱之后执行 】 * destroy-method="destroy_user" 【在调用容器对象的destriy方法时候执行,(容器用实现类)】 */ @Test public void testIOC() throws Exception { // 得到IOC容器对象 【用实现类,因为要调用销毁的方法】 ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("cn/itcast/a_hello/applicationContext.xml"); System.out.println("-----容器创建-----"); // 从容器中获取bean User user1 = (User) ac.getBean("user"); User user2 = (User) ac.getBean("user"); System.out.println(user1); System.out.println(user2); // 销毁容器对象 ac.destroy(); }
4)获取IOC两种方式:
通过BeanFactory获得IOC容器
// 现在,把对象的创建交给spring的IOC容器 Resource resource = new ClassPathResource("cn/itcast/a_hello/applicationContext.xml"); // 创建容器对象(Bean的工厂), IOC容器 = 工厂类 + applicationContext.xml BeanFactory factory = new XmlBeanFactory(resource); // 得到容器创建的对象 User user = (User) factory.getBean("user");
直接获得IOC容器:ApplicationContext ac = new ClassPathXmlApplicationContext("cn/itcast/a_hello/applicationContext.xml");
//2. (方便)直接得到IOC容器对象 @Test public void testAc() throws Exception { // 得到IOC容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("cn/itcast/a_hello/applicationContext.xml"); // 从容器中获取bean User user = (User) ac.getBean("user"); System.out.println(user); }
3.专业术语
1)侵入式设计:引入框架对现有类结构产生影响struts框架;非侵入式框架:引入框架对现有类结构没有影响
2)控制反转(Inversion on Control IOC),依赖注入( dependency injection DI)
区别:IOC解决对象创建问题;DI:对象创建完后对对象属性赋值,对象间关系依赖的处理【通过set方法依赖注入】
3)AOP:面向切面编程:切面简单来说就是由很多重复代码组成,如:事务,日志,权限
4.Spring提供的一站式解决方法
1) Spring Core spring的核心功能: IOC容器, 解决对象创建及依赖关系
2) Spring Web Spring对web模块的支持。
1. 可以与struts整合,让struts的action创建交给spring
2. spring mvc模式
3) Spring DAO Spring 对jdbc操作的支持 【JdbcTemplate模板工具类】
4) Spring ORM spring对orm的支持:
1. 既可以与hibernate整合,【session】
2. 也可以使用spring的对hibernate操作的封装
5)Spring AOP 切面编程
6)SpringEE spring 对javaEE其他模块的支持
5.与applicationContext.xml配置文件有关的配置
1)配置创建对象的三种方式:
调用无参数构造器;带参数构造器constructor-arg;工厂创建对象factory-bean(静态方法创建对象,非静态方法创建对象)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- ###############对象创建############### --> <!-- 1. 默认无参数构造器 <bean id="user1" class="cn.itcast.b_create_obj.User"></bean> --> <!-- 2. 带参数构造器 --> <bean id="user2" class="cn.itcast.b_create_obj.User"> <constructor-arg index="0" type="int" value="100"></constructor-arg> <constructor-arg index="1" type="java.lang.String" value="Jack"></constructor-arg> </bean> <!-- 定义一个字符串,值是"Jack" ; String s = new String("jack")--> <bean id="str" class="java.lang.String"> <constructor-arg value="Jacks"></constructor-arg> </bean> <bean id="user3" class="cn.itcast.b_create_obj.User"> <constructor-arg index="0" type="int" value="100"></constructor-arg> <constructor-arg index="1" type="java.lang.String" ref="str"></constructor-arg> </bean> <!-- 3. 工厂类创建对象 --> <!-- # 3.1 工厂类,实例方法 --> <!-- 先创建工厂 --> <bean id="factory" class="cn.itcast.b_create_obj.ObjectFactory"></bean> <!-- 在创建user对象,用factory方的实例方法 --> <bean id="user4" factory-bean="factory" factory-method="getInstance"></bean> <!-- # 3.2 工厂类: 静态方法 --> <!-- class 指定的就是工厂类型 factory-method 一定是工厂里面的“静态方法” --> <bean id="user" class="cn.itcast.b_create_obj.ObjectFactory" factory-method="getStaticInstance"></bean> </beans>
2)给对象属性赋值(DI 依赖注入)
1.通过构造方法;2.通过set方式;3.p名称空间;4.自动装配;5.注解
1.通过set方式:bean中加入<property name="userDao" ref="userDao">
内部bean方式:
<!-- ##############内部bean############## --> <bean id="userAction" class="cn.itcast.c_property.UserAction"> <property name="userService"> <bean class="cn.itcast.c_property.UserService"> <property name="userDao"> <bean class="cn.itcast.c_property.UserDao"></bean> </property> </bean> </property> </bean>
2.p名称空间:p:userDao-ref=""代替了<property>set配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- ###############对象属性赋值############### --> <!-- 给对象属性注入值: # p 名称空间给对象的属性注入值 (spring3.0以上版本才支持) --> <bean id="userDao" class="cn.itcast.c_property.UserDao"></bean> <bean id="userService" class="cn.itcast.c_property.UserService" p:userDao-ref="userDao"></bean> <bean id="userAction" class="cn.itcast.c_property.UserAction" p:userService-ref="userService"></bean> <!-- 传统的注入: <bean id="user" class="cn.itcast.c_property.User" > <property name="name" value="xxx"></property> </bean> --> <!-- p名称空间优化后 --> <bean id="user" class="cn.itcast.c_property.User" p:name="Jack0001"></bean> </beans>
3.自动装配:autowire="byName";autowire="byType"可以在<bean>属性上设置也可以在全局配置在头部default-autowire="byName"> 根据名称自动装配(全局)
<!-- ###############自动装配############### --> 自动去IOC容器中找与属性名同名的引用的对象,并自动注入 <bean id="userDao" class="cn.itcast.d_auto.UserDao"></bean> <bean id="userService" class="cn.itcast.d_auto.UserService" autowire="byName"></bean> <!-- 根据“名称”自动装配: userAction注入的属性,会去ioc容器中自动查找与属性同名的对象 --> <bean id="userAction" class="cn.itcast.d_auto.UserAction" autowire="byName"></bean> 全局配置 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" default-autowire="byName"> 根据名称自动装配(全局) <!-- ###############自动装配############### --> <bean id="userDao" class="cn.itcast.d_auto.UserDao"></bean> <bean id="userService" class="cn.itcast.d_auto.UserService"></bean> <bean id="userAction" class="cn.itcast.d_auto.UserAction"></bean> </beans> 根据类型byType <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" default-autowire="byType"> <!-- ###############自动装配############### --> <bean id="userDao" class="cn.itcast.d_auto.UserDao"></bean> <bean id="userService" class="cn.itcast.d_auto.UserService"></bean> <!-- 如果根据类型自动装配: 必须确保IOC容器中只有一个该类型的对象 --> <bean id="userAction" class="cn.itcast.d_auto.UserAction"></bean> <!-- 报错: 因为上面已经有一个该类型的对象,且使用了根据类型自动装配 <bean id="userService_test" class="cn.itcast.d_auto.UserService" autowire="byType"></bean> --> </beans>
总结:pring提供的自动装配主要是为了简化配置; 但是不利于后期的维护
4.注解:
使用步骤:
1.先引入context名称空间
xmlns:context="http://www.springframework.org/schema/context"
2.开始扫描:
<context:component-scan base-package="cn.itcast.e_anno2"></context:component-scan>
3.使用注解:通过注解的方式,把对象加入ioc容器
@Component 指定把一个对象加入IOC容器
@Repository 作用同@Component; 在持久层使用
@Service 作用同@Component; 在业务逻辑层使用
@Controller 作用同@Component; 在控制层使用
@Resource 属性注入
例子:
bean.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" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 开启注解扫描 --> <context:component-scan base-package="cn.itcast.e_anno2"></context:component-scan> <bean id="userDao" class="cn.itcast.e_anno2.UserDao" scope="prototype"> </bean> </beans> Dao // 把当前对象加入ioc容器 //@Component("userDao") // 相当于bean.xml 【<bean id=userDao class=".." />】 //@Component // 加入ioc容器的UserDao对象的引用名称, 默认与类名一样, 且第一个字母小写 //@Repository // 在持久层可以选择用这个注解 public class UserDao { public UserDao(){ System.out.println("UserDao.UserDao()"); } public UserDao(int id){ System.out.println("UserDao.UserDao(int id)" + id); } public void save() { System.out.println("DB:保存用户!!!"); } } Service //@Component("userService") // userService加入ioc容器 //@Component @Service // 表示业务逻辑层的组件 public class UserService { // @Resource // 根据类型查找 【在容器中要确保该类型只有一个变量】 @Resource(name = "userDao") // 根据名称查找 private UserDao userDao; // 去容器中招UserDao类型的变量,找到后就赋值 public void save() { userDao.save(); } } Action //@Component("userAction") // 加入IOC容器 //@Component @Controller // 控制层的组件 public class UserAction { @Resource private UserService userService; public String execute() { userService.save(); return null; } }
6.Spring与Struts结合
关键点:struts的action创建交给spring
导入jar包
配置xml:Struts.xml;bean.xml;web.xml
struts.xml:struts路径与action映射配置
bean.xml:spring ioc 配置
web.xml:核心过滤器,初始化spring ioc容器
将Spring配置到web.xml中
<!-- 2. spring 配置 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/classes/bean-*.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
-----------------------------------------------------------
1.代理模式
1)理解:代理(Proxy)是一种设计模式,想在目标对象上实现功能扩展可用代理,是用户访问目标对象另一种访问方式
举例:明星《--经纪人<--用户:另一种方式访问对象给对象添加新功能
2)静态代理
1.原理:代理对象实现目标对象一样的接口也可以添加额外功能
2.优缺点:可以在不改变目标对象功能前提下,对目标对象功能扩展;但是可能会有很多代理的对象,且代理对象和目标对象都要维护
3.实现方式:与目标对象实现相同接口,在代理对象中添加自己的功能写上目标对象功能--------与装饰者设计模式类似
4.例子:保存用户(模拟)-》Dao(直接保存)-》DaoProxy(给保存添加事务处理)
IUserDao package 代理.a_static; public interface IUserDao { void save(); } UserDao package 代理.a_static; public class UserDao implements IUserDao{ @Override public void save() { System.out.println("用户保存对象"); } } UserDaoProxy package 代理.a_static; public class UserDaoProxy implements IUserDao{ private IUserDao target; public UserDaoProxy(IUserDao target){ this.target=target; } @Override public void save() { System.out.println("事务开始"); target.save(); System.out.println("事务结束"); } } 测试APP package 代理.a_static; public class App { public static void main(String[] args){ //目标对象 IUserDao target=new UserDao(); //代理对象 IUserDao proxy=new UserDaoProxy(target); proxy.save(); } }
3)动态代理
1.原理:代理对象不需要实现接口,而是动态的在内存中构建代理对象(需要我们提供代理对象/目标对象 实现接口的类型)
2.核心API:Proxy代理对象工具类
static Object newProxyInstance(
ClassLoader loader, 指定当前目标对象使用类加载器
Class<?>[] interfaces, 目标对象实现的接口的类型
InvocationHandler h 事件处理器
3.动态代理总结:代理对象不需要实现接口,但是目标对象必须实现接口,但是遇到目标对象没有实现接口就要用到新技术Cglib代理
4.例子:
IUserDao package 代理.b_dynamic; public interface IUserDao { void save(); } UserDao package 代理.b_dynamic; public class UserDao implements IUserDao{ @Override public void save() { System.out.println("-------用户已保存-------"); } } 代理对象动态创建,需要提供目标对象 package 代理.b_dynamic; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 给所有的到创建代理对象【动态代理】 * * 代理对象不需要实现接口,内存内部自己自动生成 * @author Administrator * */ public class ProxyFactory { private Object target; public ProxyFactory(Object target){ this.target=target; } //内存自动生成的 public Object getProxyInstance(){ return Proxy.newProxyInstance( //目标对象类加载器 target.getClass().getClassLoader(), //目标对象实现的接口类型 target.getClass().getInterfaces(), //核心事务处理器 new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("事务开始"); //执行目标对象方法 Object returnValue=method.invoke(target, args);//调用目标对象的方法 System.out.println("事务结束"); return returnValue; } } ); } } 测试App package 代理.b_dynamic; public class App { public static void main(String[] args){ //目的对象 IUserDao target=new UserDao(); //动态代理对象创建 IUserDao proxy=(IUserDao) new ProxyFactory(target).getProxyInstance(); //内存中动态代理对象就是$Proxy0 System.out.println(proxy.getClass()); //执行方法 proxy.save(); } }
4)Cglib代理:子类代理
1.原理:在内存中构建一个子类对象从而实现对目标对象功能扩展;
底层原理:通过小儿快的字节码处理框架ASM,来转换字节码生产新类
2.引入:普通jdk动态代理必须目标对象有接口实现,当遇到没有接口的就需要用CGLIB了
3.使用步骤:
1)引入cglib-jar,spring核心包中有了
2)开始动态在内存中构建子类
3)代理的类不能为final,否则报错;目标对象的方法如果为final/staic,那么就不会被拦截,不会执行目标对象额外的业务方法
总结:在Spring的AOP编程中,如果容器的目标对象有实现接口,用JDK代理,没有实现接口可以用cglib子类代理
4.例子:
实现MethodInterceptor接口实现逻辑代码-》写获得子代理对象getProxyInsance()->通过Enhancer工具类赋值父类,创建子类代理对象
UserDao package h_cglib; public class UserDao { public void save(){ System.out.println("User保存"); } } Proxy package h_cglib; import java.lang.reflect.Method; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; public class ProxyFactory implements MethodInterceptor{ private Object target; public ProxyFactory(Object target){ this.target=target; } //给目标创建代理自对象 public Object getProxyInsance(){ //1.工具类 Enhancer en=new Enhancer(); //2.设置父类 en.setSuperclass(target.getClass()); //3.设置回调函数 en.setCallback(this); //4.创建子类(代理对象) return en.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy arg3) throws Throwable { // TODO Auto-generated method stub System.out.println("开始事务。。。"); Object returnValue=method.invoke(target, args); System.out.println("提交事务。。。"); return returnValue; } } 测试类 package h_cglib; import g_dynamic.IUserDao; public class App { public static void main(String[] args) { //目标对象 UserDao target=new UserDao(); System.out.println(target.getClass()); //代理子对象 UserDao proxy=(UserDao) new ProxyFactory(target).getProxyInsance(); proxy.save(); } }
2.手动实现AOP编程【代码模式】面向切面编程
1)理解:切片编程,就是将重复代码拿出来放在一个切边类中,之后再和核心逻辑代码组合叫AOP编程
2)代码例子:都时抽象出重复代码放在AOP切片类中
IUserDao package e_AOP.myAOP2; public interface IUserDao { void save(); } UserDao package e_AOP.myAOP2; import org.springframework.stereotype.Component; @Component public class UserDao implements IUserDao{ @Override public void save() { System.out.println("User保存"); } } ProxyFactory package e_AOP.myAOP2; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyFactory { private static Object target; private static Aop aop; public static Object getProxyInstance(Object target_,Aop aop_){ target=target_; aop=aop_; return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { aop.begin(); Object returnValue=method.invoke(target, args); aop.end(); return returnValue; } }); } } Aop package e_AOP.myAOP2; import org.springframework.stereotype.Component; /** * 切片代码,与逻辑代码分离 * @author Administrator * */ @Component public class Aop { public void begin(){ System.out.println("开始事务。。。"); } public void end(){ System.out.println("结束事务。。。"); } } App package e_AOP.myAOP2; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { ApplicationContext ac=new ClassPathXmlApplicationContext("e_AOP/myAOP2/bean.xml"); @Test public void test(){ IUserDao proxy=(IUserDao) ac.getBean("userDao_proxy"); proxy.save(); } } bean.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" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 开启注解扫描 --> <context:component-scan base-package="e_AOP.myAOP2.ProxyFactory" ></context:component-scan> <!-- 调用工厂类静态方法放回UserDao 代理后的对象 --> <bean id="userDao_proxy" class="e_AOP.myAOP2.ProxyFactory" factory-method="getProxyInstance"> <constructor-arg index="0" ref="userDao"></constructor-arg> <constructor-arg index="1" ref="aop"></constructor-arg> </bean> </beans>
3.AOP编程
1)关键字
AOP: aspect object programming 面向切面编程,关注点代码与业务代码分离!
关注点:重复代码
切面:关注点组成的类
切入点:执行目标对象方法,动态植入切面代码
可以通过切入点表达式,指定拦截哪些类的哪些方法,给指定的类在运行时候植入切面类代码
2)步骤
1.引入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】
2.bean.xml中引入aop名称空间
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
3.开始aop注解
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 开启注解扫描 --> <context:component-scan base-package="cn.itcast.e_aop_anno"></context:component-scan> <!-- 开启aop注解方式 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
4.使用注解
切入点表达式:返回类型+包名+访问的类+类的方法(* cn.itcast.e_aop_anno.*.*(..))
)
@Aspect 指定一个类为切面类
@Pointcut("execution(* cn.itcast.e_aop_anno.*.*(..))") 指定切入点表达式
@Before("pointCut_()") 前置通知: 目标方法之前执行
@After("pointCut_()") 后置通知:目标方法之后执行(始终执行)
@AfterReturning("pointCut_()") 返回后通知: 执行方法结束前执行(异常不执行)
@AfterThrowing("pointCut_()") 异常通知: 出现异常时候执行
@Around("pointCut_()") 环绕通知: 环绕目标方法执行
5.例子:
package e_AOP.myAOPanno1; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Component @Aspect //指定当前面为切面类 public class Aop { //指定切入点表单式:拦截表达式,返回类型+包+哪一类+类方法(有参数可以写参数没有..) @Pointcut("execution(* e_AOP.myAOPanno1.*.*(..))") public void pointCut_(){ } //前置通知:在执行目标方法之前执行 @Before("pointCut_()") public void begin(){ System.out.println("开始事务/异常"); } //后置通知:在执行目标方法之后执行 @After("pointCut_()") public void after(){ System.out.println("提交事务/关闭"); } //返回后通知:在调用目标方法之后执行 @AfterReturning("pointCut_()") public void afterRunning(){ System.out.println("afterRunning()"); } //异常执行 @AfterThrowing("pointCut_()") public void afterThrowing(){ System.out.println("afterThrowing()"); } //环绕执行 @Around("pointCut_()") public void around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("环绕前..."); pjp.proceed();//执行目标方法 System.out.println("环绕后..."); } }
测试类
package e_AOP.myAOPanno1; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { ApplicationContext ac = new ClassPathXmlApplicationContext("e_AOP/myAOPanno1/bean.xml"); // 目标对象有实现接口,spring会自动选择“JDK代理” @Test public void testApp() { IUserDao userDao = (IUserDao) ac.getBean("userDao"); System.out.println(userDao.getClass());//$Proxy001 userDao.save(); } //目标对象没有实现接口,spring会用“cglib代理” @Test public void testCglib(){ OrderDao orderDao=(OrderDao) ac.getBean("orderDao"); System.out.println(orderDao.getClass()); orderDao.save(); } }
全部配置在bean.xml中
<!-- 配置OrderDao --> <bean id="userDao" class="e_AOP.myAOXml.UserDao"></bean> <!-- 配置UserDao --> <bean id="orderDao" class="e_AOP.myAOXml.OrderDao"></bean> <!-- 配置Aop --> <bean id="aop" class="e_AOP.myAOXml.Aop"></bean> <!-- Aop配置--> <aop:config> <!-- 定义一个切入点表达式:拦截哪些方法 --> <aop:pointcut expression="execution(* e_AOP.myAOXml.*.*(..))" id="pt"/> <!-- 切面 --> <aop:aspect ref="aop"> <!-- 环绕通知 --> <aop:around method="around" pointcut-ref="pt"/> <!-- 前置通知: 在目标方法调用前执行 --> <aop:before method="begin" pointcut-ref="pt"/> <!-- 后置通知: --> <aop:after method="after" pointcut-ref="pt"/> <!-- 返回后通知 --> <aop:after-returning method="afterReturning" pointcut-ref="pt"/> <!-- 异常通知 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pt"/> </aop:aspect> </aop:config>
切面类的所有配置
<!-- 切面类 -->
<bean id="aop" class="cn.itcast.g_pointcut.Aop"></bean>
<!-- Aop配置 -->
<aop:config>
<!-- 定义一个切入点表达式: 拦截哪些方法 -->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.*.*(..))" id="pt"/>-->
<!-- 【拦截所有public方法】 -->
<!--<aop:pointcut expression="execution(public * *(..))" id="pt"/>-->
<!-- 【拦截所有save开头的方法 】 -->
<!--<aop:pointcut expression="execution(* save*(..))" id="pt"/>-->
<!-- 【拦截指定类的指定方法, 拦截时候一定要定位到方法】 -->
<!--<aop:pointcut expression="execution(public * cn.itcast.g_pointcut.OrderDao.save(..))" id="pt"/>-->
<!-- 【拦截指定类的所有方法】 -->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.*(..))" id="pt"/>-->
<!-- 【拦截指定包,以及其自包下所有类的所有方法】 -->
<!--<aop:pointcut expression="execution(* cn..*.*(..))" id="pt"/>-->
<!-- 【多个表达式】 -->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) || execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) or execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<!-- 下面2个且关系的,没有意义 -->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) && execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) and execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<!-- 【取非值】 -->
<!--<aop:pointcut expression="!execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
<aop:pointcut expression=" not execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>
<!-- 切面 -->
<aop:aspect ref="aop">
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
4.Spring DAO
类似于DbUtil
1)配置xml导入JdbcTemplate对象
<!-- 1. 数据源对象: C3P0连接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql:///hib_demo"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> <property name="initialPoolSize" value="3"></property> <property name="maxPoolSize" value="10"></property> <property name="maxStatements" value="100"></property> <property name="acquireIncrement" value="2"></property> </bean> <!-- 2. 创建JdbcTemplate对象 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- dao 实例 --> <bean id="userDao" class="cn.itcast.h_jdbc.UserDao"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean>
2)使用这个JdbcTemplate对象
private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public void save() { String sql = "insert into t_dept(deptName) values('test');"; jdbcTemplate.update(sql); } public Dept findById(int id) { String sql = "select * from t_dept where deptId=?"; List<Dept> list = jdbcTemplate.query(sql,new MyResult(), id); return (list!=null && list.size()>0) ? list.get(0) : null; } public List<Dept> getAll() { String sql = "select * from t_dept"; List<Dept> list = jdbcTemplate.query(sql, new MyResult()); return list; } class MyResult implements RowMapper<Dept>{ // 如何封装一行记录 @Override public Dept mapRow(ResultSet rs, int index) throws SQLException { Dept dept = new Dept(); dept.setDeptId(rs.getInt("deptId")); dept.setDeptName(rs.getString("deptName")); return dept; } }
5.Spring事务管理
1).事务管理分类
1.编程式事务管理:是jdbc中conn.setAutoCommite(false)
Hibernate中是session.beginTransaction()
细粒度的事务控制
2.声明式事务管理:只需在配置文件中配置,实现了对事务控制最大解耦
Jdbc:DataSourceTransactionManager
Hibernate技术:HibernateTransactionManager
粗粒度事务控制:只能给整个方法应用事务,因为aop拦截的是方法
2)配置事务管理
1.引入spring-aop相关4个jar文件;2.引入aop名称空间3.引入tx名称空间
2.spring声明式事务管理配置:
1)配置事务管理器类2)配置事务增强如何管理事务3)AOP配置拦截哪些方法应用上面的增强
<!-- #############5. Spring声明式事务管理配置############### --> <!-- 5.1 配置事务管理器类 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 5.2 配置事务增强(如果管理事务?) --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="find*" read-only="true"/> <tx:method name="*" read-only="false"/> </tx:attributes> </tx:advice> <!-- 5.3 Aop配置: 拦截哪些方法(切入点表表达式) + 应用上面的事务增强配置 --> <aop:config> <aop:pointcut expression="execution(* cn.itcast.a_tx.DeptService.*())" id="pt"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/> </aop:config>
3)完整例子
1. DeptDao.java public class DeptDao { // 容器注入JdbcTemplate对象 private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public void save(Dept dept){ String sql = "insert into t_dept (deptName) values(?)"; jdbcTemplate.update(sql,dept.getDeptName()); } } 2. DeptService public class DeptService { // 容器注入dao对象 private DeptDao deptDao; public void setDeptDao(DeptDao deptDao) { this.deptDao = deptDao; } /* * 事务控制? */ public void save(Dept dept){ // 第一次调用 deptDao.save(dept); int i = 1/0; // 异常: 整个Service.save()执行成功的要回滚 // 第二次调用 deptDao.save(dept); } } 3. App 测试类 @Test public void testApp() throws Exception { //容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("cn/itcast/a_tx/bean.xml"); // 模拟数据 Dept dept = new Dept(); dept.setDeptName("测试: 开发部"); DeptService deptService = (DeptService) ac.getBean("deptService"); deptService.save(dept); } 4. bean.xml (Spring声明式事务管理配置) <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 1. 数据源对象: C3P0连接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql:///hib_demo"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> <property name="initialPoolSize" value="3"></property> <property name="maxPoolSize" value="10"></property> <property name="maxStatements" value="100"></property> <property name="acquireIncrement" value="2"></property> </bean> <!-- 2. JdbcTemplate工具类实例 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 3. dao实例 --> <bean id="deptDao" class="cn.itcast.a_tx.DeptDao"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <!-- 4. service实例 --> <bean id="deptService" class="cn.itcast.a_tx.DeptService"> <property name="deptDao" ref="deptDao"></property> </bean> <!-- #############5. Spring声明式事务管理配置############### --> <!-- 5.1 配置事务管理器类 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 5.2 配置事务增强(如果管理事务?) --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="find*" read-only="true"/> <tx:method name="*" read-only="false"/> </tx:attributes> </tx:advice> <!-- 5.3 Aop配置: 拦截哪些方法(切入点表表达式) + 应用上面的事务增强配置 --> <aop:config> <aop:pointcut expression="execution(* cn.itcast.a_tx.DeptService.*())" id="pt"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/> </aop:config> </beans>
3.注解方式实现事务管理
注解声明讲解:
事物传播行为介绍: @Transactional(propagation=Propagation.REQUIRED) 如果有事务, 那么加入事务, 没有的话新建一个(默认情况下) @Transactional(propagation=Propagation.NOT_SUPPORTED) 容器不为这个方法开启事务 @Transactional(propagation=Propagation.REQUIRES_NEW) 不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务 @Transactional(propagation=Propagation.MANDATORY) 必须在一个已有的事务中执行,否则抛出异常 @Transactional(propagation=Propagation.NEVER) 必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反) @Transactional(propagation=Propagation.SUPPORTS) 如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务. 事物超时设置: @Transactional(timeout=30) //默认是30秒 事务隔离级别: @Transactional(isolation = Isolation.READ_UNCOMMITTED) 读取未提交数据(会出现脏读, 不可重复读) 基本不使用 @Transactional(isolation = Isolation.READ_COMMITTED) 读取已提交数据(会出现不可重复读和幻读) @Transactional(isolation = Isolation.REPEATABLE_READ) 可重复读(会出现幻读) @Transactional(isolation = Isolation.SERIALIZABLE) 串行化 MYSQL: 默认为REPEATABLE_READ级别 SQLSERVER: 默认为READ_COMMITTED 脏读 : 一个事务读取到另一事务未提交的更新数据 不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说, 后续读取可以读到另一事务已提交的更新数据. 相反, "可重复读"在同一事务中多次 读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据 幻读 : 一个事务读到另一个事务已提交的insert数据 @Transactional注解中常用参数说明 参 数 名 称 功 能 描 述 readOnly 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true) rollbackFor 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如: 指定单一异常类:@Transactional(rollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) 续表) 参 数 名 称 功 能 描 述 rollbackForClassName 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如: 指定单一异常类名称:@Transactional(rollbackForClassName="RuntimeException") 指定多个异常类名称:@Transactional(rollbackForClassName={"RuntimeException","Exception"}) noRollbackFor 该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如: 指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) noRollbackForClassName 该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如: 指定单一异常类名称:@Transactional(noRollbackForClassName="RuntimeException") 指定多个异常类名称: @Transactional(noRollbackForClassName={"RuntimeException","Exception"}) propagation 该属性用于设置事务的传播行为,具体取值可参考表6-7。 例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) isolation 该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置 timeout 该属性用于设置事务的超时秒数,默认值为-1表示永不超时 注意的几点: 1 @Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能. 2用 spring 事务管理器,由spring来负责数据库的打开,提交,回滚.默认遇到运行期例外(throw new RuntimeException("注释");)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception("注释");)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚 要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .如果让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class) 如下: @Transactional(rollbackFor=Exception.class) //指定回滚,遇到异常Exception时回滚 public void methodName() { throw new Exception("注释"); } @Transactional(noRollbackFor=Exception.class)//指定不回滚,遇到运行期例外(throw new RuntimeException("注释");)会回滚 public ItimDaoImpl getItemDaoImpl() { throw new RuntimeException("注释"); } 3、@Transactional 注解应该只被应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。 4、@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。然而,请注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据,能够被可以识别 @Transactional 注解和上述的配置适当的具有事务行为的beans所使用。上面的例子中,其实正是 <tx:annotation-driven/>元素的出现 开启 了事务行为。 5、Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因 此,请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解。
配置文件关于事务管理部分的改为
<!-- 事务管理器类 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 开启注解扫描 --> <context:component-scan base-package="cn.itcast.b_anno"></context:component-scan> <!-- 注解方式实现事务: 指定注解方式实现事务 --> <tx:annotation-driven transaction-manager="txManager"/>
调用事务的方法使用注解的用法
@Transactional( readOnly=false, timeout=-1, isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED ) public void save(Dept dept){ //第一次调用 deptDao.save(dept); int i = 1/0; // 异常: 整个Service.save()执行成功的要回滚 // 第二次调用 deptDao.save(dept); }
4.事务属性
@Transactional( readOnly = false, // 读写事务 timeout = -1, // 事务的超时时间不限制 noRollbackFor = ArithmeticException.class, // 遇到数学异常不回滚 isolation = Isolation.DEFAULT, // 事务的隔离级别,数据库的默认 propagation = Propagation.REQUIRED // 事务的传播行为 ) public void save(Dept dept){ deptDao.save(dept); int i = 1/0; deptDao.save(dept); } 事务传播行为: Propagation.REQUIRED 指定当前的方法必须在事务的环境下执行; 如果当前运行的方法,已经存在事务, 就会加入当前的事务; Propagation.REQUIRED_NEW 指定当前的方法必须在事务的环境下执行; 如果当前运行的方法,已经存在事务: 事务会挂起; 会始终开启一个新的事务,执行完后; 刚才挂起的事务才继续运行