Spring
2018-09-03 21:40:27 191 举报
AI智能生成
spring
作者其他创作
大纲/内容
Spring
为什么要使用Spring?
DI(Dependency Injection,依赖注入)
当某个角色 需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在spring中 创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由spring来完成,然后注入调用者 因此也称为依赖注入。
p style=\
IOC(inversion of control,控制反转)
是通过IOC容器实现的,由IOC容器负责创建和获取依赖对象,对象只是被动地接受依赖对象。
IOC,另外一种说法叫DI(Dependency Injection),即依赖注入。它是一种设计思想。在任何一个有实际开发意义的程序项目中,我们会使用很多类来描述它们特有的功能,并且通过类与类之间的相互协作来完成特定的业务逻辑。这个时候,每个类都需要负责管理与自己有交互的类的引用和依赖,代码将会变的异常难以维护和极度的高耦合。而IOC的出现正是用来解决这个问题,我们通过IOC将这些相互依赖对象的创建、协调工作交给Spring容器去处理,每个对象只需要关注其自身的业务逻辑关系就可以了。在这样的角度上来看,获得依赖的对象的方式,进行了反转,变成了由spring容器控制对象如何获取外部资源(包括其他对象和文件资料等等)。
例子
面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
原理就是通过Java的反射技术来实现的!通过反射我们可以获取类的所有信息(成员变量、类名等等等)!再通过配置文件(xml)或者注解来描述类与类之间的关系我们就可以通过这些配置信息和反射技术来构建出对应的对象和依赖关系了!
AOP(Aspect Oriented Programming,面向切面编程)
AOP的本质是在一系列纵向的控制流程中,把那些相同的子流程提取成一个横向的面
简化java开发
激发pojo的潜能
依赖注入
应用切面
使用模版消除样板式代码
容纳你的bean
与应用上下文共事
bean的生命周期
Spring是一个库,它的功能是提供了一个软件框架,这个框架目的是使软件之间的逻辑更加清晰,配置更灵活,实现这个目的的手段使用AOP和IoC,而AOP和IoC是一种思想
Bean
JavaBean的规范
生命周期
1.Spring对bean进行实例化;2.Spring将值和bean的引用注入到bean对应的属性中;3.如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;4.如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;5.如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;6.如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法;7.如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用init-method声明了初始化方法,该方法也会被调用;8.如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法;9.此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;10.如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。
作用域
单例(Singleton):在整个应用中,只创建bean的一个实例。
对无状态的bean则应该使用singleton作用域
无状态则不保存信息,是线程安全的,可以共享
原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
当一个Bean被设置为prototype 后Spring就不会对一个bean的整个生命周期负责,容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了
对有状态的bean应该使用prototype作用域
所谓有状态就是该bean有保存信息的能力,不能共享,否则会造成线程安全问题
会话(Session):在Web应用中,为每个会话创建一个bean实例。
请求(Rquest):在Web应用中,为每个请求创建一个bean实例。
在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。
声明Bean
注解(自动检测)
@Component 组件,没有明确的角色(都可以用)
@Service 在业务逻辑层使用
@Repository 在数据访问层使用
@Controller 在展现层使用(MVC -> Spring MVC)使用
Spring支持将@Named(Java依赖注入规范)作为@Component注解的替代方案。两者之间有一些细微的差异,但是在大多数场景中,它们是可以互相替换的。
需要在applicationContext.xml中声明<contex:component-scan...>一项,指明Spring容器扫描组件的包目录。
span style=\
基于xml配置Bean(需要提供setter方法)
<bean id=\"userDao\" class=\"com.baobaotao.anno.UserDao\"/>
(工厂方法注入)
静态工厂
public class CarFactory { public static Car createHongQiCar(){ Car car = new Car(); car.setBrand(\"红旗CA72\"); return car; } public static Car createCar(){ Car car = new Car(); return car; }}
<bean id=\"car6\" class=\"com.baobaotao.ditype.CarFactory\" factory-method=\"createCar\"></bean>
实例工厂
public class CarFactory { public Car createHongQiCar(){ Car car = new Car(); car.setBrand(\"红旗CA72\"); return car; } public static Car createCar(){ Car car = new Car(); return car; }}
<!-- 工厂方法--> <bean id=\"carFactory\" class=\"com.baobaotao.ditype.CarFactory\" /> <bean id=\"car5\" factory-bean=\"carFactory\" factory-method=\"createHongQiCar\"> </bean>
基于java类提供Bean定义(配置类)(需要提供setter方法)
@Configuation
@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的<beans>,作用为:配置spring容器(应用上下文)
][
@Configuration启动容器+@Bean注册Bean,@Bean下管理bean的生命周期
@Configurationpublic class TestConfiguration { public TestConfiguration() { System.out.println(\"TestConfiguration容器启动初始化。。。\
(1)、@Bean注解在返回实例的方法上,如果未通过@Bean指定bean的名称,则默认与标注的方法名相同; (2)、@Bean注解默认作用域为单例singleton作用域,可通过@Scope(“prototype”)设置为原型作用域; (3)、既然@Bean的作用是注册bean对象,那么完全可以使用@Component、@Controller、@Service、@Ripository等注解注册bean,当然需要配置@ComponentScan注解进行自动扫描。
@Configuration启动容器+@Component注册Bean
配置类的注册方式
配置类的注册方式是将其传递给 AnnotationConfigApplicationContext 构造函数复制代码public static void main(String[] args) { // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class); //获取bean TestBean tb = (TestBean) context.getBean(\"testBean\"); tb.sayHello(); }
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(AppContext.class)}
配置Web应用程序(web.xml中配置AnnotationConfigApplicationContext)
之前
<web-app> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> <servlet-name>sampleServlet</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> </servlet>...</web-app>
之后
<web-app> <context-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context. support.AnnotationConfigWebApplicationContext </param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value> demo.AppContext </param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> <servlet-name>sampleServlet</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context. support.AnnotationConfigWebApplicationContext </param-value> </init-param> </servlet>...</web-app>
您要将 web.xml 中的上述代码更改为使用 AnnotationConfigApplicationContext 类。切记,XmlWebApplicationContext 是 Spring 为 Web 应用程序使用的默认上下文实现,因此您永远不必在您的web.xml 文件中显式指定这个上下文类。现在,您将使用基于 Java 的配置,因此在配置 Web 应用程序时,需要在web.xml 文件中指定 AnnotationConfigApplicationContext 类。现在定义了 AnnotationConfigWebApplicationContext 上下文类,并将其作为上下文参数和 servlet 元素的一部分。上下文配置位置现在指向 AppContext 配置类。
@Configuation总结
@Configuation等价于<Beans></Beans> @Bean等价于<Bean></Bean> @ComponentScan等价于<context:component-scan base-package=\"com.dxz.demo\"/>
组合多个配置类
在@configuration中引入spring的xml配置文件
@Configuration@ImportResource(\"classpath:applicationContext-configuration.xml\")public class WebConfig {}
在@configuration中引入其它注解配置
@Configuration@ImportResource(\"classpath:applicationContext-configuration.xml\")@Import(TestConfiguration.class)public class WebConfig {}
@configuration嵌套(嵌套的Configuration必须是静态类)
@Configuration@ComponentScan(basePackages = \"com.dxz.demo.configuration3\")public class TestConfiguration { public TestConfiguration() { System.out.println(\"TestConfiguration容器启动初始化。。。\"); } @Configuration static class DatabaseConfig { @Bean DataSource dataSource() { return new DataSource(); } }}
http://www.cnblogs.com/duanxz/p/7493276.html
https://blog.csdn.net/isea533/article/details/78072133?locationNum=7&fps=1
@Configuration 注解:@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Componentpublic @interface Configuration { String value() default \"\";}从定义来看, @Configuration 注解本质上还是 @Component,因此 <context:component-scan/> 或者 @ComponentScan 都能处理@Configuration 注解的类。
依赖注入(所有的属性必须有setter)
属性注入
通过setXxx()方法注入Bean的属性值或依赖对象
注入简单值
<propertyname=\"name\"value=\"陈冠希\"></property>
注入其他bean
<propertyname=\"address\"ref=\"address\"></property>
注入数组
<property name=\"books\"> <array> <value>小白书</value> <value>白皮书</value> <value>把妹秘籍</value> </array> </property>
注入集合
注入list
<propertyname=\"courses\"> <list> <value>java从入门到精通</value> <value>java从精通到精辟</value> </list> </property>
注入set
<!-- Set 注入 --> <propertyname=\"games\"> <set> <value>王者荣耀</value> <value>LOL</value> <value>dota</value> </set>
注入map
<propertyname=\"cards\"> <map> <entry> <key><value>ICBC</value></key> <value>工商银行</value> </entry> <entrykey=\"ABC\"> <value>农业银行</value> </entry> </map> </property>
注入空值
<propertyname=\"wife\"><null/></property>
注入Properties
<propertyname=\"properties\"> <props> <propkey=\"driver\">com.mysql.jdbc.Driver</prop> <propkey=\"url\">jdbc:mysql://localhost:3306/mybatis</prop> <propkey=\"username\">root</prop> <propkey=\"password\">root</prop> </props> </property>
属性过多时,构造函数变的臃肿可怕构造函数注入灵活性不强,有时需要为属性注入null值多个构造函数时,配置上产生歧义,复杂度升高构造函数不利于类的继承和扩展构造函数注入会引起循环依赖的问题
构造器注入
使用构造函数注入的前提是Bean必须提供带参数的构造函数
<constructor-arg ref=\"logDao\"></constructor-arg>
构造函数保证重要属性预先设置无需提供每个属性Setter方法,减少类的方法个数可更好的封装类变量,避免外部错误调用
装配
概念
创建应用对象之间协作关系的行为称为装配。也就是说当一个对象的属性是另一个对象时,实例化时,需要为这个对象属性进行实例化。这就是装配。如果一个对象只通过接口来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行切换。
装配的类型
no:顾名思义, 显式指明不使用Spring的自动装配功能
byName:根据属性和组件的名称匹配关系来实现bean的自动装配
byType:根据属性和组件的类型匹配关系来实现bean的自动装配,有多个适合类型的对象时装配失败
constructor:与byType类似是根据类型进行自动装配,但是要求待装配的bean有相应的构造函数
在constructor模式下,存在单个实例则优先按类型进行参数匹配(无论名称是否匹配),当存在多个类型相同实例时,按名称优先匹配,如果没有找到对应名称,则注入失败,此时可以使用autowire-candidate=”false” 过滤来解决。
autodetect:autodetect是constructor与byTyp style=\
自动装配
基于注解的自动装配配置文件中配置<context:annotation-config />
@Autowired Spring提供的注解在属性上不需要setter方法
@Autowired注解是byType类p style=\
另一种情况是同时有多个bean是一个类型p style=\
@Qualifier
@Qualifier注解限定Bean的名称
@Qualifier注解使用byName进行装配,这样可以在多个类型一样的bean中,明确使用哪一个名字的bean来进行装配。@Qualifier注解起p style=\
@Inject 是JSR-330提供的注解
按类型匹配注入的Bean的,只不过它没有required属性。必须强制装配,否则报错。可以与@Named一起使用
@Named是JSR-330提供的注解
使用byName进行装配
两套自动装配的注解组合
@Resource 是JSR-250提供的注解
@Resource要求提供一个Bean名称的属性,如果属性为空,则自动采用标注处的变量名或方法名作为Bean的名称。
如果@Resource未指定\"value\"属性,则也可以根据属性方法得到需要注入的Bean名称。@Resource则按名称匹配注入Bean。(类似于Xml中使用<constructor-arg ref=\"logDao\"></constructor-arg>或者<property name=\"logDao\" ref=\"logDao\"></property>进行注入,如果使用了@Autowired或者Resource等,这不需要在定义Bean时使用属性注入和构造方法注入了)
装配顺序
如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
@Inject注解来源于Java依赖注入规范
@value
作用:对简单类型的属性自动注入
@Value接收一个String的值,该值指定了将要被注入到内置的java类型属性值
@Value会与properties文件结合使用
SpEL
//SpEL表达方式,其中代表xml配置文件中的id值configProperties @Value(\"#{configProperties['jdbc.username']}\") private String userName;
<!--基于SpEL表达式 配置多个properties id值为configProperties 提供java代码中使用 --> <bean id=\"configProperties\" class=\"org.springframework.beans.factory.config.PropertiesFactoryBean\"> <property name=\"locations\"> <list> <value>classpath:/conf/jdbc.properties</value> </list> </property> </bean> <!--基于SpEL表达式 配置单个properties --> <util:properties id=\"configProperties\" location=\"classpath:conf/jdbc.properties\"/>
占位符
//占位符方式 @Value(\"${jdbc.url}\") private String url;
<!--基于占位符方式 配置单个properties --> <context:property-placeholder location=\"conf/jdbc.properties\"/> <!--基于占位符方式 配置多个properties --> <bean id=\"propertyConfigurer\" class=\"org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer\"> <property name=\"location\" value=\"conf/jdbc.properties\"/> </bean>
基于xml的自动装配
装配的bean必须是唯一与属性进行吻合的,不能多也不能少,有且只有一个可以进行装配的bean,才能自动装配成功。否则会p style=\
自动装配依赖对象 <beanid=“foo”class=“...Foo” autowire=“autowire type”>
开发人员不必知道具体要装配哪个bean的引用,这个识别的工作会由spring来完成。在传统的依赖注入配置中,我们必须要明确要给属性装配哪一个bean的引用,一旦bean很多,就不好维护了。基于这样的场景,spring使用注解p style=\
自动向Bean注入依赖
手工装配依赖对象
* 在xml配置文件中,通过在bean节点下配置
构造函数注入
* 在java代码中使用@Autowired或@Resource注解方式进行装配
注解注入配置文件中配置<context:annotation-config />
@Autowired Spring提供的注解
装配Bean
使用表达式装配
spEL的基本原理
在spEL值上执行操作
在spEL中筛选集合
自动装配bean属性
4种类型的自动装配
默认的自动装配
混合使用自动装配和显示装配
使用注解装配
使用@Autowired
借助@Inject实现基于标准的自动装配
在注解中使用表达式
自动检测Bean
为自动检测标注bean
过滤组件扫描
依赖注入和装配的关系
依赖注入的本质就是装配,装配是依赖注入的具体行为。
<bean id=\"hello\" class=\"com.maven.Hello\"><constructor-arg value=\"hello\" /></bean>这是使用构造器注入来装配bean。
bean的延迟加载
在某些情况下,我们可能希望把bean的创建延迟到使用阶段,以免消耗不必要的内存,Spring也非常自愿地支持了延迟bean的初始化。因此可以在配置文件中定义bean的延迟加载,这样Spring容器将会延迟bean的创建直到真正需要时才创建。通常情况下,从一个已创建的bean引用另外一个bean,或者显示查找一个bean时会触发bean的创建即使配置了延迟属性,因此如果Spring容器在启动时创建了那些设为延长加载的bean实例,不必惊讶,可能那些延迟初始化的bean可能被注入到一个非延迟创建且作用域为singleton的bean。在xml文件中使用bean的lazy-init属性可以配置改bean是否延迟加载,如果需要配置整个xml文件的bean都延迟加载则使用defualt-lazy-init属性,请注意lazy-init属性会覆盖defualt-lazy-init属性。
default-lazy-init=\"true\"
<!-- lazy-init=\"false\" 表示非延长加载--> <bean name=\"accountDao\" lazy-init=\"false\" class=\"com.zejian.spring.springIoc.dao.impl.AccountDaoImpl\"/>
<context:component-scan/>与<context:annotation-config/>
使用@Autowired、@Resource、@Value等自动装配注解时用<context:annotation-config/>进行注解驱动注册,从而使注解生效。实际上这样<context:annotation-config/>一条配置,它的作用是式地向Spring 容器注册AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor以及RequiredAnnotationBeanPostProcessor 这 4 个BeanPostProcessor。
使用@Service、@Component、@Controller 、@Repository等注解时,需要在xml配置文件声明包扫描驱动<context:component-scan/>,它的作用是Spring容器在启动时会启动注解驱动去扫描对应包下的bean对象并将创建它们的实例,这样我们就无法一个个地进行bean配置声明了,极大简化了编程代码。请注意,当spring的xml配置文件出了<context:component-scan/>后,<context:annotation-config/>就可以退休了,因为<context:component-scan/>已包含了<context:annotation-config/>的功能了。在大部分情况下,都会直接使用<context:component-scan/>进行注解驱动注册和包扫描功能。
spring事务
常见问题
事务不生效
Bean是否是代理对象
入口函数是否是public的
数据库是否支持事务(Mysql的MyIsam不支持事务)
切点是否配置正确
容器装配顺序造成的事务无法回滚两个文件的加载是有先后顺序的。Spring会优先加载在context-param标签中引入的文件,也就是这里的mybatis-context.xml,然后再加载application-context.xml。在加载mybatis-context.xml时,Spring就会装配@Controlle和@Service的容器,并把他们放到Spring的容器中来;接着加载application-context.xml,这时Spring也会把@Service和@Controller注解的实例装配到Spring容器中,但是这时由于先前在加载mybatis-context.xml时已经装配过@Service和@Controller的容器,所以这时新装配的容器会覆盖掉先前的容器,所以Spring容器中就只剩下后来装配的容器了。这种装配顺序就会引来一个问题,由于我的事务是在mybatis-context.xml文件中声明的,所以这个文件中的Service容器是带有事务能力的;但是这里装配的Service容器会被后来application-context.xml中的Service容器替换掉,而application-context.xml中的Service容器是没有事务能力的,所以最后造成在Spring容器中的Service是没有事务能力的。要解决这个问题,只需只在mybatis-context.xml中生成Service的带事务的容器,而在application-context.xml中就不生成Service容器了。又由于Service是Controller类中的一个属性,所以在装配Controller前要先装配好Service。为了达到以上目的,由于是先加载myabtis-context.xml,后加载application-context.xml,所以我们只需在myabtis-context.xml装配Service,不装配Controller;然后在application-context.xml中装配Controller,不装配Service就可以了。
业务代码是否吞掉异常
基本概念
事务的目的
数据资源所承载的系统状态始终处于'正确'的状态
事务特性(ACID)
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
4种隔离级别
Read Uncommitted (读取未提交内容)
Read Committed (读取提交内容)
Repeatable (可重读)
Serializable(可串行化)
几个问题
脏读
事务 A 读取了事务 B 未提交的数据,并在这个基础上又做了其他操作。
不可重复读
事务 A 读取了事务 B 已提交的更改数据。
幻读
事务 A 读取了事务 B 已提交的新增数据。
持久性(Durability)
事务的7种传播行为事务的传播性一般在事务嵌套时候使用
PROPAGATION_REQUIRED
如果没有,就新建一个事务;如果有,就加入当前事务。
span style=\"font-size:14px;\
PROPAGATION_REQUIRES_NEW
如果没有,就新建一个事务;如果有,就将当前事务挂起且只直到新的事务提交或者回滚才恢复执行。意思就是创建了一个新事务,它和原来的事务没有任何关系了。
新起一个事务,那么就是存在两个不同的事务。如果 内部已经提交,那么 外部事务失败回滚,内部事务是不会回滚的。如果内部事务失败回滚,如果他抛出的异常被 外部事务捕获,外部事务事务仍然可能提交(主要看B抛出的异常是不是A会回滚的异常)。
PROPAGATION_NESTED
如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。也就是传说中的“嵌套事务”了,所嵌套的子事务与主事务之间是有关联的(当主事务提交或回滚,子事务也会提交或回滚,反之,子事务不影响主事务)。
内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。
span style=\"color:#ff0000;\
内部事务rollback了外部事务的两种选择
void methodA() { try { ServiceB.methodB(); } catch (SomeException) { span class=\"hljs-comment\
PROPAGATION_NOT_SUPPORTED
如果没有,就以非事务方式执行;如果有,就将当前事务挂起(这种方式非常强硬,没有就没有,有我也不支持你,把你挂起来,不鸟你。)
PROPAGATION_SUPPORTS
如果没有,就以非事务方式执行;如果有,就使用当前事务(这种方式非常随意,没有就没有,有就有)
PROPAGATION_MANDATORY
如果没有,就抛出异常;如果有,就使用当前事务。
PROPAGATION_NEVER
如果没有,就以非事务方式执行;如果有,就抛出异常。
Spring 解决的只是方法之间的事务传播
事务超时(Transaction Timeout
为了解决事务时间太长,消耗太多的资源,所以故意给事务设置一个最大时常,如果超过了,就回滚事务。
只读事务(Readonly Transaction)
事务的回滚规则
默认回滚RuntimeException异常,而Checked Exception异常不回滚,捕获异常不抛出也不会回滚,但可以强制事务回滚:TransactionAspectSupport.currentTransactionStatus().isRollbackOnly();
spring事务的核心接口
三个核心接口实现编程式事务
TransactionDefinition(定义事务(事务的规则))
public interface TransactionDefinition{int getIsolationLevel();int getPropagationBehavior();int getTimeout();boolean isReadOnly();}
它包含了事务的静态属性,比如:事务传播行为、超时时间等等。
Spring 为我们提供了一个默认的实现类:DefaultTransactionDefinition
PlatformTransactionManager(事务管理器)(执行具体的事务操作(回滚或提交))
Public interface PlatformTransactionManager{ TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status)throws TransactionException; void rollback(TransactionStatus status)throws TransactionException;}
DataSourceTransactionManager:适用于使用JDBC和iBatis进行数据持久化操作的情况。
HibernateTransactionManager:适用于使用Hibernate进行数据持久化操作的情况。
JpaTransactionManager:适用于使用JPA进行数据持久化操作的情况。
另外还有JtaTransactionManager 、JdoTransactionManager、JmsTransactionManager等等。
如果我们使用JTA进行事务管理,我们可以通过 JNDI 和 Spring 的 JtaTransactionManager 来获取一个容器管理的 DataSource。JtaTransactionManager 不需要知道 DataSource 和其他特定的资源,因为它将使用容器提供的全局事务管理。而对于其他事务管理器,比如DataSourceTransactionManager,在定义时需要提供底层的数据源作为其属性,也就是 DataSource。与 HibernateTransactionManager 对应的是 SessionFactory,与 JpaTransactionManager 对应的是 EntityManagerFactory 等等。
TransactionStatus(运行着的事务的状态)
PlatformTransactionManager.getTransaction(…) 方法返回一个 TransactionStatus 对象。
public interface TransactionStatus{ boolean isNewTransaction(); void setRollbackOnly(); boolean isRollbackOnly();}
事务管理的意义
“按照给定的事务规则来执行提交或者回滚操作”。
“给定的事务规则”就是用 TransactionDefinition 表示的,
“按照……来执行提交或者回滚操作”便是用 PlatformTransactionManager
而 TransactionStatus 用于表示一个运行着的事务的状态。
编程式事务
直接使用PlatformTransactionManager来进行编程式事务管理
public class BankServiceImpl implements BankService {private BankDao bankDao;private TransactionDefinition txDefinition;private PlatformTransactionManager txManager;......public boolean transfer(Long fromId, Long toId, double amount) {TransactionStatus txStatus = txManager.getTransaction(txDefinition);boolean result = false;try {result = bankDao.transfer(fromId, toId, amount);txManager.commit(txStatus);} catch (Exception e) {result = false;txManager.rollback(txStatus);System.out.println(\"Transfer Error!\");}return result;}}<bean id=\"bankService\" class=\"footmark.spring.core.tx.programmatic.origin.BankServiceImpl\"><property name=\"bankDao\" ref=\"bankDao\"/><property name=\"txManager\" ref=\"transactionManager\"/><property name=\"txDefinition\"><bean class=\"org.springframework.transaction.support.DefaultTransactionDefinition\"><property name=\"propagationBehaviorName\" value=\"PROPAGATION_REQUIRED\"/></bean></property></bean>
事务管理的代码散落在业务逻辑代码中,破坏了原有代码的条理性,并且每一个业务方法都包含了类似的启动事务、提交/回滚事务的样板代码。
直接使用TransactionTemplate来进行编程式事务管理
public class BankServiceImpl implements BankService {private BankDao bankDao;private TransactionTemplate transactionTemplate;......public boolean transfer(final Long fromId, final Long toId, final double amount) {return (Boolean) transactionTemplate.execute(new TransactionCallback(){public Object doInTransaction(TransactionStatus status) {Object result;try {result = bankDao.transfer(fromId, toId, amount);} catch (Exception e) {status.setRollbackOnly();result = false;System.out.println(\"Transfer Error!\");}return result;}});}}相应的XML配置如下:清单 7. 基于 TransactionTemplate 的事务管理示例配置文件<bean id=\"bankService\"class=\"footmark.spring.core.tx.programmatic.template.BankServiceImpl\"><property name=\"bankDao\" ref=\"bankDao\"/><property name=\"transactionTemplate\" ref=\"transactionTemplate\"/></bean>
创建基于SavePoint的嵌套事务
声明式事务
配置方式
注解元数据驱动的声明式事务(@Transactional)
\t<!-- 配置事务管理器 -->\t<bean id=\"transactionManager\" \t\tclass=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\">\t\t<property name=\"dataSource\" ref=\"dataSource\"></property>\t</bean>\t\t<!-- 启用事务注解 -->\t<tx:annotation-driven transaction-manager=\"transactionManager\"/>
在需要使用事务的类上添加注解即可 @Transactional
@Transactional(propagation = Propagation.REQUIRED)public boolean transfer(Long fromId, Long toId, double amount) {return bankDao.transfer(fromId, toId, amount);}
propagation:事务传播行为, isolation:事务隔离级别定义;默认为“DEFAULT” timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统; readonly:事务只读设置,默认为false,表示不是只读; rollbackfor:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚; norollbackfor:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该注解来覆盖类级别的定义。虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
XML元数据驱动的声明式事务
基于 TransactionInterceptor和ProxyFactoryBean 的事务管理
<beans...>......<bean id=\"transactionInterceptor\"class=\"org.springframework.transaction.interceptor.TransactionInterceptor\"><property name=\"transactionManager\" ref=\"transactionManager\"/><property name=\"transactionAttributes\"><props><prop key=\"transfer\">PROPAGATION_REQUIRED</prop></props></property></bean><bean id=\"bankServiceTarget\"class=\"footmark.spring.core.tx.declare.origin.BankServiceImpl\"><property name=\"bankDao\" ref=\"bankDao\"/></bean><bean id=\"bankService\"class=\"org.springframework.aop.framework.ProxyFactoryBean\"><property name=\"target\" ref=\"bankServiceTarget\"/><property name=\"interceptorNames\"><list><idref bean=\"transactionInterceptor\"/></list></property></bean>......</beans>
TransactionInterceptor的两个主要属性
transactionManager(指定一个事务管理器,并将具体事务相关的操作委托给它)
Properties 类型的 transactionAttributes 属性(来定义事务规则)
来定义事务规则,该属性的每一个键值对中,键指定的是方法名,方法名可以使用通配符,而值就表示相应方法的所应用的事务属性。
事务的规则(传播行为 [,隔离级别] [,只读属性] [,超时属性] [不影响提交的异常] [,导致回滚的异常])
传播行为是唯一必须设置的属性,其他都可以忽略,Spring为我们提供了合理的默认值。
传播行为的取值必须以“PROPAGATION_”开头,具体包括:PROPAGATION_MANDATORY、PROPAGATION_NESTED、PROPAGATION_NEVER、PROPAGATION_NOT_SUPPORTED、PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_SUPPORTS,共七种取值。
隔离级别的取值必须以“ISOLATION_”开头,具体包括:ISOLATION_DEFAULT、ISOLATION_READ_COMMITTED、ISOLATION_READ_UNCOMMITTED、ISOLATION_REPEATABLE_READ、ISOLATION_SERIALIZABLE,共五种取值。
如果事务是只读的,那么我们可以指定只读属性,使用“readOnly”指定。否则我们不需要设置该属性。
超时属性的取值必须以“TIMEOUT_”开头,后面跟一个int类型的值,表示超时时间,单位是秒。
不影响提交的异常是指,即使事务中抛出了这些类型的异常,事务任然正常提交。必须在每一个异常的名字前面加上“+”。异常的名字可以是类名的一部分。比如“+RuntimeException”、“+tion”等等。
导致回滚的异常是指,当事务中抛出这些类型的异常时,事务将回滚。必须在每一个异常的名字前面加上“-”。异常的名字可以是类名的全部或者部分,比如“-RuntimeException”、“-tion”等等。
示例
<property name=\"*Service\">PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED,TIMEOUT_20,+AbcException,+DefException,-HijException</property>
针对所有方法名以 Service 结尾的方法,使用 PROPAGATION_REQUIRED 事务传播行为,事务的隔离级别是 ISOLATION_READ_COMMITTED,超时时间为20秒,当事务抛出 AbcException 或者 DefException 类型的异常,则仍然提交,当抛出 HijException 类型的异常时必须回滚事务。这里没有指定\"readOnly\",表示事务不是只读的。
<property name=\"test\">PROPAGATION_REQUIRED,readOnly</property>
针对所有方法名为 test 的方法,使用 PROPAGATION_REQUIRED 事务传播行为,并且该事务是只读的。除此之外,其他的属性均使用默认值。比如,隔离级别和超时时间使用底层事务性资源的默认值,并且当发生未检查异常,则回滚事务,发生已检查异常则仍提交事务。
配置好了 TransactionInterceptor,我们还需要配置一个 ProxyFactoryBean 来组装 target 和advice。这也是典型的 Spring AOP 的做法。通过 ProxyFactoryBean 生成的代理类就是织入了事务管理逻辑后的目标类。
配置文件太多。我们必须针对每一个目标对象配置一个 ProxyFactoryBean;另外,虽然可以通过父子 Bean 的方式来复用 TransactionInterceptor 的配置,但是实际的复用几率也不高;这样,加上目标对象本身,每一个业务类可能需要对应三个 <bean/> 配置,随着业务类的增多,配置文件将会变得越来越庞大,管理配置文件又成了问题。
基于TransactionProxyFactoryBean的配置 (Spring 经典的声明式事务管理)(TransactionInterceptor 和 ProxyFactoryBean 的配置合二为一)
<beans......>......<bean id=\"bankServiceTarget\"class=\"footmark.spring.core.tx.declare.classic.BankServiceImpl\"><property name=\"bankDao\" ref=\"bankDao\"/></bean><bean id=\"bankService\"class=\"org.springframework.transaction.interceptor.TransactionProxyFactoryBean\"><property name=\"target\" ref=\"bankServiceTarget\"/><property name=\"transactionManager\" ref=\"transactionManager\"/><property name=\"transactionAttributes\"><props><prop key=\"transfer\">PROPAGATION_REQUIRED</prop></props></property></bean>......</beans>
基于<tx/>命名空间的配置
<beans......>......<bean id=\"bankService\" class=\"footmark.spring.core.tx.declare.namespace.BankServiceImpl\"><property name=\"bankDao\" ref=\"bankDao\"/></bean><tx:advice id=\"bankAdvice\" transaction-manager=\"transactionManager\"><tx:attributes><tx:method name=\"transfer\" propagation=\"REQUIRED\"/></tx:attributes></tx:advice><aop:config><aop:pointcut id=\"bankPointcut\" expression=\"execution(* *.transfer(..))\"/><aop:advisor advice-ref=\"bankAdvice\" pointcut-ref=\"bankPointcut\"/></aop:config>......</beans>
<tx:advice>定义事务通知,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过<tx:attributes>指定具体需要拦截的方法<tx:method>拦截方法,其中参数有: name:方法名称,将匹配的方法注入事务管理,可用通配符 propagation:事务传播行为, isolation:事务隔离级别定义;默认为“DEFAULT” timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统; read-only:事务只读设置,默认为false,表示不是只读; rollback-for:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚; no-rollback-for:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;<!-- 定义切入点,expression为切人点表达式,如下是指定impl包下的所有方法,具体以自身实际要求自定义 --><aop:config> <aop:pointcut expression=\"execution(* com.kaizhi.*.service.impl.*.*(..))\" id=\"pointcut\"/> <!--<aop:advisor>定义切入点,与通知,font color=\"#c41230\
如果配置的事务管理器 Bean 的名字取值为“transactionManager”,则我们可以省略 <tx:advice> 的 transaction-manager 属性,因为该属性的默认值即为“transactionManager”。
利用切点表达式,一个配置可以匹配多个方法
基于BeanNameAutoProxyCreator的配置
<!-- 配置事务拦截器--><beanid=\"transactionInterceptor\"class=\"org.springframework.transaction.interceptor.TransactionInterceptor\"><!-- 事务拦截器bean需要依赖注入一个事务管理器 --><propertyname=\"transactionManager\"ref=\"transactionManager\"/><propertyname=\"transactionAttributes\"><!-- 下面定义事务传播属性--><props><propkey=\"insert*\">PROPAGATION_REQUIRED</prop><propkey=\"find*\"span style=\"color:#006699;\
是个bean后处理器,BeanNameAutoProxyCreator是个根据bean名生成自动代理的代理创建器(可以跟自己创建的拦截器一起用)
MethodInterceptor 接口,在调用目标对象的方法时,就可以实现在调用方法之前、调用方法过程中、调用方法之后对其进行控制。
public static class adviseMethodInterceptor implements MethodInterceptor{ @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object result=null; try{ System.out.println(\"方法执行之前:\"+methodInvocation.getMethod().toString()); result= methodInvocation.proceed(); System.out.println(\"方法执行之后:\"+methodInvocation.getMethod().toString()); System.out.println(\"方法正常运行结果:\"+result); return result; }catch (Exception e){ System.out.println(\"方法出现异常:\"+e.toString()); System.out.println(\"方法运行Exception结果:\"+result); return result; } } }
ProxyFactory proxyFactory=new ProxyFactory();//创建一个代理对象 proxyFactory.setTarget(new TestMethodInterceptor());//设置代理对象的目标对象 proxyFactory.addAdvice(new adviseMethodInterceptor());//设置代理对象的方法拦截器 Object proxy = proxyFactory.getProxy();//得到生成的代理对象 TestMethodInterceptor methodInterceptor = (TestMethodInterceptor) proxy; methodInterceptor.doSomeThing(\"通过代理工厂设置代理对象,拦截代理方法\");//调用方法
<bean id=\"adviseMethodInterceptor\" class=\"com.i2p.admin.interceptor.adviseMethodInterceptor\" /> <!-- 根据 Bean 的名字自动实现代理拦截 --> <bean class=\"org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator\"> <property name=\"interceptorNames\"> <list> <value>methodTimeAdvice</value> </list> </property> <property name=\"beanNames\
参数
beanNames
设置哪些bean需要自动生成代理
interceptorNames
指定事务拦截器,自动创建事务代理时,系统会根据这些事务拦截器的属性来生成对应的事务代理。
定义了两个org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator,按照顺序分别计作A,B,A会为目标对象T生成代理对象P1,B会为P1生成代理对象P2,代理逻辑就变成: P2代理P1,P1代理T,而不是P2、P1都代理T。所以,调用T的方法时,应该先执行P1,但P2代理了P1,最终先执行P2,再执行P1,在执行T。这种情况下,如果T的方法上有注解,只有P1能够拿到注解信息,P2是拿不到的。所以,不要定义个多个org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator的bean。
实现原理
AOP
PlatformTransactionManager
最细粒度只能作用到方法级别
编程式事物与声明式事物d不同点
Spring的编程式事务即在代码中使用编程的方式进行事务处理,可以做到比声明式事务更细粒度。有两种方式一是使用TransactionManager,另外就是TransactionTemplate。
1)Spring的声明式事务管理在底层是建立在AOP的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。因为事务管理本身就是一个典型的横切逻辑,正是AOP的用武之地。依赖注入容器为声明式事务管理提供了基础设施,使得Bean对于Spring框架而言是可管理的;而Spring AOP则是声明式事务管理的直接实现者。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
基本框架
核心容器
Spring上下文
SpringAOP
SpringDAO
SpringORM
SpringWeb模块
SpringMVC框架
面向切面编程(Aspect Oriented Programming)
AOP术语
AOP概念
在AOP的编程方法中,主要在于对关注点的提起及抽象。我们可以把一个复杂的系统看作是由多个关注点来有机组合来实现,一个典型的系统可能会包括几个方面的关注点,如核心业务逻辑、性能、数据存储、日志、授权、安全、线程及错误检查等,另外还有开发过程中的关注点,如易维护、易扩展等。把每个关注点与核心业务模块分离,作为单独的功能,横切几个核心业务模块,使得每份功能代码不再单独入侵到核心业务类的代码中,即核心模块只需关注自己相关的业务,当需要外围业务(日志,权限,性能监测、事务控制)时,这些外围业务会通过一种特殊的技术自动应用到核心模块中,这些关注点有个特殊的名称,叫做“横切关注点”,
关注点
关注点就是我们要考察或解决的问题。如订单的处理,用户的验证、用户日志记录等都属于关注点。
核心关注点
是指系统中的核心功能,即真正的商业逻辑。如在一个电子商务系统中,订单处理、客户管理、库存及物流管理都是属于系统中的核心关注点。
横切关注点
他们分散在每个各个模块中解决同一样的问题,跨越多个模块。如用户验证、日志管理、事务处理、数据缓存都属于横切关注点。
Aspect 切面
切面是一个关注点的模块化,这个关注点可能会横切多个对象和模块,事务管理是横切关注点的很好的例子。切面是通知和切点的结合。通知和切点定义了切面的全部内容——它是什么,在何时何处完成其功能。
切面优先级
目标方法前执行(”进入”)的通知函数,最高优先级的通知将会先执行,在执行在目标方法后执行(“退出”)的通知函数,最高优先级会最后执行。而对于在同一个切面定义的通知函数将会根据在类中的声明顺序执行。
实现org.springframework.core.Ordered 接口,该接口用于控制切面类的优先级,同时重写getOrder方法,定制返回值,返回值(int 类型)越小优先级越大。
通知(增强)Advice
增强是织入到目标类连接点上的一段程序代码,通知定义了切面要完成的工作以及何时使用(该什么时候执行),应该应用在某个方法被调用之前?之后?还是抛出异常时?等等。
5种通知类型
Before 前置通知
在方法调用之前调用通知
After 后置通知
在方法完成之后调用通知,无论方法执行成功与否
After-returning 后置返回通知
在方法执行成功之后调用通知
可以通过returning = “returnVal”注明参数的名称而且必须与通知函数的参数名称相同
After-throwing 异常通知
在方法抛出异常后进行通知
由throwing来声明一个接收异常信息的变量,同样异常通知也用于Joinpoint参数,需要时加上即可,
Around 环绕通知
通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
JoinPoint
java.lang.Object[] getArgs():获取连接点方法运行时的入参列表; Signature getSignature() :span style=\"font-family:Arial;font-size:14px;line-height:26px;\
ProceedingJoinPoint
Join point 连接点
每个方法都是一个连接点就是spring允许你执行通知(Advice)的地方,那可就真多了,基本每个方法的钱、后(两者都有也行),或抛出异常是时都可以是连接点,spring只支持方法连接点。其他如AspectJ还可以让你在构造器或属性注入时都行,不过那不是咱们关注的,只要记住,和方法有关的前前后后都是连接点。
切点Pointcut
span style=\"color:black\"(具体在哪些方法上织入通知、这是条件,筛选出需要织入的连接点)切点有助于缩小切面所通知的连接点的范围。如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”,上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有十几个连接点了对吧,但是你并不想在所有方法附近都织入通知(使用叫织入),你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
切入点指示符
通配符
..
匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包
+
匹配给定类的任意子类
*
匹配任意数量的字符
类型签名表达式(within)
只能指定(限定)包路径(类名也可以看做是包路径),表示某个包下或者子包下的所有方法
语法格式
within(<type name>)
案例
//匹配com.zejian.dao包及其子包中所有类中的所有方法@Pointcut(\"within(com.zejian.dao..*)\")//匹配UserDaoImpl类中所有方法@Pointcut(\"within(com.zejian.dao.UserDaoImpl)\")//匹配UserDaoImpl类及其子类中所有方法@Pointcut(\"within(com.zejian.dao.UserDaoImpl+)\")//匹配所有实现UserDao接口的类的所有方法@Pointcut(\"within(com.zejian.dao.UserDao+)\")
方法签名表达式(execution)
根据方法签名进行过滤
span class=\"hljs-comment\
//匹配UserDaoImpl类中的所有方法@Pointcut(\"execution(* com.zejian.dao.UserDaoImpl.*(..))\")//匹配UserDaoImpl类中的所有公共的方法@Pointcut(\"execution(public * com.zejian.dao.UserDaoImpl.*(..))\")//匹配UserDaoImpl类中的所有公共方法并且返回值为int类型@Pointcut(\"execution(public int com.zejian.dao.UserDaoImpl.*(..))\")//匹配UserDaoImpl类中第一个参数为int类型的所有公共的方法@Pointcut(\
bean
Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;
//匹配名称中带有后缀Service的Bean。@Pointcut(\"bean(*Service)\")private void myPointcut1(){}
this
用于匹配当前AOP代理对象类型的执行方法;请注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配
//匹配了任意实现了UserDao接口的代理对象的方法进行过滤@Pointcut(\"this(com.zejian.spring.springAop.dao.UserDao)\")private void myPointcut2(){}
target
用于匹配当前目标对象类型的执行方法;
//匹配了任意实现了UserDao接口的目标对象的方法进行过滤@Pointcut(\"target(com.zejian.spring.springAop.dao.UserDao)\")private void myPointcut3(){}
@target():限制连接点匹配目特定的执行对象,这些对象对应的类要具备指定类型的注解.
@within
用于匹配所以持有指定注解类型内的方法;请注意与within是有区别的, within是用于匹配指定类型内的方法执行;
//匹配使用了MarkerAnnotation注解的类(注意是类)@Pointcut(\"@within(com.zejian.spring.annotation.MarkerAnnotation)\")private void myPointcut4(){}
@annotation(com.zejian.spring.MarkerMethodAnnotation)
根据所应用的注解进行方法过滤
//匹配使用了MarkerAnnotation注解的方法(注意是方法)@Pointcut(\"@annotation(com.zejian.spring.annotation.MarkerAnnotation)\")private void myPointcut5(){}
args()
限制连接点匹配参数为指定类型的执行方法,限定方法的参数的类型
args(net.aazj.pojo.User): 参数为User类型的方法。
向注解处理方法传递参数
pre class=\"prettyprint hljs kotlin\" style=\
code class=\"java spaces\" style=\
@args()
限制连接点匹配参数由指定注解标注的执行方法
@args(org.springframework.transaction.annotation.Transactional): 参数的类型 带有 @Transactional 注解 的所有方法
bean()
指定某个bean的名称
切点指示符可以使用运算符语法进行表达式的混编,如and、or、not(或者&&、||、!)
引入Introduction
引入是指给一个现有类添加方法或字段属性,引入还可以在不改变现有类代码的情况下,让现有的Java类实现新的接口(以及一个对应的实现)。相对于Advice可以动态改变程序的功能或流程来说,引介(Introduction)则用来改变一个类的静态结构。比如你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制
织入Weaving
span style=\"color:black\"“引入”我把它看做是一个定义,也就是一个名词,而“织入”我把它看做是一个动作,一个动词,也就是切面在指定的连接点被织入到目标对象中。简单理解为aspect(切面)应用到目标函数(类)的过程
织入方式
运行时织入
动态织入(Spring AOP)
在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的
动态代理技术
Java JDK的动态代理(Proxy,底层通过反射实现) 必须要有接口
//自定义的接口类,JDK动态代理的实现必须有对应的接口类public interface ExInterface { void execute();}//A类,实现了ExInterface接口类public class A implements ExInterface{ public void execute(){ System.out.println(\"执行A的execute方法...\"); }}//代理类的实现public class JDKProxy implements InvocationHandler{ /** * 要被代理的目标对象 */ private A target; public JDKProxy(A target){ this.target=target; } /** * 创建代理类 * @return * @param loader 类加载器,一般传递目标对象(A类即被代理的对象)的类加载器 * @param interfaces 目标对象(A)的实现接口 * @param InvocationHandler接口 回调处理句柄(后面会分析到) */ public ExInterface createProxy(){ span class=\"hljs-keyword\
font color=\"#c41230\
CGLIB的动态代理(底层通过继承实现) 字节码生成技术实现AOP
//被代理的类即目标对象public class A { public void execute(){ System.out.println(\"执行A的execute方法...\"); }}//代理类public class CGLibProxy implements MethodInterceptor { /** * 被代理的目标类 */ private A target; public CGLibProxy(A target) { super(); this.target = target; } /** * 创建代理对象 * @return */ public A createProxy(){ // 使用CGLIB生成代理: span class=\"hljs-comment\
被代理的类无需接口即可实现动态代理,而CGLibProxy代理类需要实现一个方法拦截器接口MethodInterceptor并重写intercept方法,类似JDK动态代理的InvocationHandler接口,也是理解为回调函数,同理每次调用代理对象的方法时,intercept方法都会被调用,利用该方法便可以在运行时对方法执行前后进行动态增强。关于代理对象创建则通过Enhancer类来设置的,Enhancer是一个用于产生代理对象的类,作用类似JDK的Proxy类,因为CGLib底层是通过继承实现的动态代理,因此需要传递目标对象(如A)的Class,同时需要设置一个回调函数对调用方法进行拦截并进行相应处理,最后通过create()创建目标对象(如A)的代理对象,运行结果与前面的JDK动态代理效果相同。
类加载器织入
-指通过特殊的的类加载器,在虚拟机JVM加载字节码的时候进行织入,比如AspectWerkz(已并入AspecJ)及JBoss就使用这种方式。
编译器织入
静态织入 (AspectJ)
编译期织入
在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。
ajc编译器,是一种能够识别aspect语法的编译器
链接期(编译后)织入
将aspect类和java目标类同时编译成字节码文件后,再进行织入处理,这种方式比较有助于已编译好的第三方jar和Class文件进行织入操作
aop原理
spring用代理类包裹切面,把他们织入到Spring管理的bean中。也就是说代理类伪装成目标类,它会截取对目标类中方法的调用,让调用者对目标类的调用都先变成调用伪装类,伪装类中就先执行了切面,再把调用转发给真正的目标bean。 现在可以自己想一想,怎么搞出来这个伪装类,才不会被调用者发现(过JVM的检查,JAVA是强类型检查,哪里都要检查类型)。 1.实现和目标类相同的接口,我也实现和你一样的接口,反正上层都是接口级别的调用,这样我就伪装成了和目标类一样的类(实现了同一接口,咱是兄弟了),也就逃过了类型检查,到java运行期的时候,利用多态的后期绑定(所以spring采用运行时),伪装类(代理类)就变成了接口的真正实现,而他里面包裹了真实的那个目标类,最后实现具体功能的还是目标类,只不过伪装类在之前干了点事情(写日志,安全检查,事物等)。 这就好比,一个人让你办件事,每次这个时候,你弟弟就会先出来,当然他分不出来了,以为是你,你这个弟弟虽然办不了这事,但是他知道你能办,所以就答应下来了,并且收了点礼物(写日志),收完礼物了,给把事给人家办了啊,所以你弟弟又找你这个哥哥来了,最后把这是办了的还是你自己。但是你自己并不知道你弟弟已经收礼物了,你只是专心把这件事情做好。 顺着这个思路想,要是本身这个类就没实现一个接口呢,你怎么伪装我,我就压根没有机会让你搞出这个双胞胎的弟弟,那么就用第2种代理方式,创建一个目标类的子类,生个儿子,让儿子伪装我 2.生成子类调用,这次用子类来做为伪装类,当然这样也能逃过JVM的强类型检查,我继承的吗,当然查不出来了,子类重写了目标类的所有方法,当然在这些重写的方法中,不仅实现了目标类的功能,还在这些功能之前,实现了一些其他的(写日志,安全检查,事物等)。 这次的对比就是,儿子先从爸爸那把本事都学会了,所有人都找儿子办事情,但是儿子每次办和爸爸同样的事之前,都要收点小礼物(写日志),然后才去办真正的事。当然爸爸是不知道儿子这么干的了。这里就有件事情要说,某些本事是爸爸独有的(final的),儿子学不了,学不了就办不了这件事,办不了这个事情,自然就不能收人家礼了。 前一种兄弟模式,spring会使用JDK的java.lang.reflect.Proxy类,它允许Spring动态生成一个新类来实现必要的接口,织入通知,并且把对这些接口的任何调用都转发到目标类。 后一种父子模式,spring使用CGLIB库生成目标类的一个子类,在创建这个子类的时候,spring织入通知,并且把对这个子类的调用委托到目标类。 相比之下,还是兄弟模式好些,他能更好的实现松耦合,尤其在今天都高喊着面向接口编程的情况下,父子模式只是在没有实现接口的时候,也能织入通知,应当做一种例外。
Spring Aop(动态AOP)
开发方式
基于注解开发
在通知上使用execution关键字定义的切点表达式
@Aspectpublic class MyAspect { /** * 前置通知 */ @Before(\"execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))\") public void before(){ System.out.println(\"前置通知....\"); } span class=\"hljs-javadoc\
使用@pointcut来定义切点匹配表达式
/** * 使用Pointcut定义切点 */@Pointcut(\"execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))\")private void myPointcut(){}/** * 应用切入点函数 */@After(value=\"myPointcut()\")public void afterDemo(){ System.out.println(\"后置通知....\");}
一定要在配置文件中启动Aspect的注解功能支持 <!-- 启动@aspectj的自动代理支持--> <aop:aspectj-autoproxy />
基于Xml开发
切面类
public class MyAspectXML { public void before(){ System.out.println(\"MyAspectXML====前置通知\"); } public void afterReturn(Object returnVal){ System.out.println(\"后置返回通知-->返回值:\"+returnVal); } public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println(\"MyAspectXML=====环绕通知前\"); Object object= joinPoint.proceed(); System.out.println(\"MyAspectXML=====环绕通知后\"); return object; } public void afterThrowing(Throwable throwable){ System.out.println(\"MyAspectXML======异常通知:\"+ throwable.getMessage()); } public void after(){ System.out.println(\"MyAspectXML=====后置通知..来了\"); }}
xml配置
<!-- 定义切面 --> <bean name=\"myAspectXML\" class=\"com.zejian.spring.springAop.AspectJ.MyAspectXML\" /> <!-- 配置AOP 切面 --> <aop:config> <!-- 定义切点函数 --> <aop:pointcut id=\"pointcut\" expression=\"execution(* com.zejian.spring.springAop.dao.ProductDao.add(..))\" /> <!-- 定义其他切点函数 --> <aop:pointcut id=\"delPointcut\" expression=\"execution(* com.zejian.spring.springAop.dao.ProductDao.delete(..))\" /> span class=\"hljs-comment\
Spring AOP 确实是通过 CGLIB或者JDK代理 来动态地生成代理对象,这个代理对象指的就是 AOP 代理累,而 AOP 代理类的方法则通过在目标对象的切入点动态地织入增强处理,从而完成了对目标方法的增强。
AspectJ-Aop(静态AOP)
AspectJ是一个java实现的AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(依赖于特殊编译器(ajc编译器)),可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联
public aspect MyAspectJDemo { span class=\"hljs-javadoc\
AspectJ的织入方式
静态编译期织入
1.基于代理的AOP编程2.@Aspectj 注解驱动的切面3.纯POJO切面4.注入式Aspectj切面编程前面3个是spring基于代理的AOP编程,所以局限于方法拦截.如果要对属性或构造器拦截者应调用 Aspectj里面的切面.
为Spring添加REST功能
了解REST
编写面向资源的控制器
表达资源
编写REST客户端
提交RESTful表单
spring计划任务
span class=\"hljs-annotation\" style=\
格式
通配符说明
配置类
spel
字面值:#{5} 值为5,<property name=\"\" value=\"#{user}\
面试
h3 style=\
Spring框架并没有对a href=\"http://howtodoinjava.com/2012/10/22/singleton-design-pattern-in-java/\" rel=\"nofollow\" class=\"external\" style=\
ol style=\
自定义事件
1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦 2.可以使用容易提供的众多服务,如事务管理,消息服务等 3.容器提供单例模式支持 4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能 5.容器提供了众多的辅助类,能加快应用的开发 6.spring对于主流的应用框架提供了集成支持,如hibernate,JPA,Struts等 7.spring属于低侵入式设计,代码的污染极低 8.独立于各种应用服务器 9.spring的DI机制降低了业务对象替换的复杂性 10.Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可以自由选择spring的部分或全部
0 条评论
回复 删除
下一页