Spring
2018-09-03 21:40:27 191 举报当某个角色 需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在spring中 创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由spring来完成,然后注入调用者 因此也称为依赖注入。
●谁依赖于谁:当然是应用程序依赖内部的属性
●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
- 控制指的是:当前对象对内部成员的控制权。
- 反转指的是:这种控制权不由当前对象管理了,由其他(类,第三方容器)来管理。获得依赖对象的方式反转了。
- 原理就是通过Java的反射技术来实现的!通过反射我们可以获取类的所有信息(成员变量、类名等等等)!
- 再通过配置文件(xml)或者注解来描述类与类之间的关系
- 我们就可以通过这些配置信息和反射技术来构建出对应的对象和依赖关系了!
必须是个公有(public)类
有无参构造函数
用公共方法暴露内部成员属性(getter,setter)
实现这样规范的类,被称为Java 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声明了销毁方法,该方法也会被调用。
1) Bean的建立,由BeanFactory读取Bean定义文件,并创建Bean实例;
2) 执行Bean的属性注入,Setter注入;
3) 如果Bean类实现了org.springframework.beans.factory.BeanNameAware接口,则执行其setBeanName方法;
4) 如果Bean类实现了org.springframework.beans.factory.BeanFactoryAware接口,则执行其setBeanFactory方法;
5) 如果容器中有实现org.springframework.beans.factory.BeanPostProcessors接口的实例,则任何Bean在初始化之前都会执行这个实例的processBeforeInitialization()方法;
6) 如果Bean类实现了org.springframework.beans.factory.InitializingBean接口,则执行其afterPropertiesSet()方法;
7) 调用Bean的初始化方法”init-method” (!!注意,init-method方法没有参数);
8) 如果容器中有实现org.springframework.beans.factory.BeanPostProcessors接口的实例,则任何Bean在初始化之后都会执行这个实例的processAfterInitialization()方法;
9) 使用Bean做一些业务逻辑….
10) 使用完,容器关闭,如果Bean类实现了org.springframework.beans.factory.DisposableBean接口,则执行它的destroy()方法;
11) 在容器关闭时,可以在Bean定义文件中使用“destory-method”定义的方法,销毁Bean (!!注意,destory-method方法没有参数);
ApplicationContext中bean的生命周期也是类似的。
ase-package属性指定要自动检测扫描的包。
基于xml配置Bean
(需要提供setter方法)
<bean id="userDao" class="com.baobaotao.anno.UserDao"/>
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 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方法)
public class TestConfiguration {
public TestConfiguration() {
System.out.println("TestConfiguration容器启动初始化。。。");
}
// @Bean注解注册bean,同时可以指定初始化和销毁方法
// @Bean(name="testBean",initMethod="start",destroyMethod="cleanUp")
@Bean
@Scope("prototype")
public TestBean testBean() {
return new TestBean();
}
}
(2)、@Bean注解默认作用域为单例singleton作用域,可通过@Scope(“prototype”)设置为原型作用域;
(3)、既然@Bean的作用是注册bean对象,那么完全可以使用@Component、@Controller、@Service、@Ripository等注解注册bean,当然需要配置@ComponentScan注解进行自动扫描。
复制代码
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();
}
ApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppContext.class)
}
<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>
<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>
@Bean等价于<Bean></Bean>
@ComponentScan等价于<context:component-scan base-package="com.dxz.demo"/>
@ImportResource("classpath:applicationContext-configuration.xml")
public class WebConfig {
}
@ImportResource("classpath:applicationContext-configuration.xml")
@Import(TestConfiguration.class)
public class WebConfig {
}
@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();
}
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
String value() default "";
}
从定义来看, @Configuration 注解本质上还是 @Component,因此 <context:component-scan/> 或者 @ComponentScan 都能处理@Configuration 注解的类。
(所有的属性必须有setter)
<array>
<value>小白书</value>
<value>白皮书</value>
<value>把妹秘籍</value>
</array>
</property>
<propertyname="courses">
<list>
<value>java从入门到精通</value>
<value>java从精通到精辟</value>
</list>
</property>
<!-- Set 注入 -->
<propertyname="games">
<set>
<value>王者荣耀</value>
<value>LOL</value>
<value>dota</value>
</set>
<map>
<entry>
<key><value>ICBC</value></key>
<value>工商银行</value>
</entry>
<entrykey="ABC">
<value>农业银行</value>
</entry>
</map>
</property>
<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值
- 多个构造函数时,配置上产生歧义,复杂度升高
- 构造函数不利于类的继承和扩展
- 构造函数注入会引起循环依赖的问题
<constructor-arg ref="logDao"></constructor-arg>
- 构造函数保证重要属性预先设置
- 无需提供每个属性Setter方法,减少类的方法个数
- 可更好的封装类变量,避免外部错误调用
pe的组合,会先进行constructor,如果不成功,再进行byType。
配置文件中配置
<context:annotation-config />
在属性上不需要setter方法
型的,这个注解可以用在属性上面,setter方面上面以及构造器上面。使用这个注解时,就不需要在类中为属性添加setter方法
了。但是这个属性是强制性的,也就是说必须得装配上,如果没有找到合适的bean能够装配上,就会抛出异常。这时可以使用requir
ed=false来允许可以不被装配上,默认值为true。
的,也会抛出这个异常。此时需要进一步明确要装配哪一个Bean,这时可以组合使用@Qualifier注解,值为Bean的名字即可。
到了缩小自动装配候选bean的范围的作用。
如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
//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>
抛出异常。如果要统一所有bean的自动装配类型,可以在<beans>标签中配置default-autowire属性。当然如果配置了autowire属
性,我们依然可以手动装配属性,手动装配会覆盖自动装配。
<beanid=“foo”class=“...Foo” autowire=“autowire type”>
在传统的依赖注入配置中,我们必须要明确要给属性装配哪一个bean的引用,一旦bean很多,就不好维护了。基于这样的场景,spring使用注解
来进行自动装配,解决这个问题。自动装配就是开发人员不必知道具体要装配哪个bean的引用,这个识别的工作会由spring来完
成。与自动装配配合的还有“自动检测”,这 个动作会自动识别哪些类需要被配置成bean,进而来进行装配。这样我们就明白了,自
动装配是为了将依赖注入“自动化”的一个简化配置的操作。
<array>
<value>小白书</value>
<value>白皮书</value>
<value>把妹秘籍</value>
</array>
</property>
<propertyname="courses">
<list>
<value>java从入门到精通</value>
<value>java从精通到精辟</value>
</list>
</property>
<!-- Set 注入 -->
<propertyname="games">
<set>
<value>王者荣耀</value>
<value>LOL</value>
<value>dota</value>
</set>
<map>
<entry>
<key><value>ICBC</value></key>
<value>工商银行</value>
</entry>
<entrykey="ABC">
<value>农业银行</value>
</entry>
</map>
</property>
<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值
- 多个构造函数时,配置上产生歧义,复杂度升高
- 构造函数不利于类的继承和扩展
- 构造函数注入会引起循环依赖的问题
<constructor-arg ref="logDao"></constructor-arg>
- 构造函数保证重要属性预先设置
- 无需提供每个属性Setter方法,减少类的方法个数
- 可更好的封装类变量,避免外部错误调用
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 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>
配置文件中配置
<context:annotation-config />
型的,这个注解可以用在属性上面,setter方面上面以及构造器上面。使用这个注解时,就不需要在类中为属性添加setter方法
了。但是这个属性是强制性的,也就是说必须得装配上,如果没有找到合适的bean能够装配上,就会抛出异常。这时可以使用requir
ed=false来允许可以不被装配上,默认值为true。
的,也会抛出这个异常。此时需要进一步明确要装配哪一个Bean,这时可以组合使用@Qualifier注解,值为Bean的名字即可。
到了缩小自动装配候选bean的范围的作用。
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/>
两个文件的加载是有先后顺序的。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就可以了。
事务的传播性一般在事务嵌套时候使用
+MyCheckedException的用法), 那么整个事务是一定要 rollback 的
失败回滚,内部事务是不会回滚的。如果内部事务失败回滚,如果他抛出的异常被
外部事务捕获,外部事务事务仍然可能提交(主要看B抛出的异常是不是A会回滚的异常)。
void methodA() { try { ServiceB.methodB(); } catch (SomeException) { // 执行其他业务, 如 ServiceC.methodC(); } }
外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback (+MyCheckedException).
public interface TransactionDefinition{ int getIsolationLevel(); int getPropagationBehavior(); int getTimeout(); boolean isReadOnly(); }
(执行具体的事务操作(回滚或提交))
Public interface PlatformTransactionManager{ TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status)throws TransactionException; void rollback(TransactionStatus status)throws TransactionException; }
public interface TransactionStatus{ boolean isNewTransaction(); void setRollbackOnly(); boolean isRollbackOnly(); }
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>
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>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
@Transactional(propagation = Propagation.REQUIRED)
public boolean transfer(Long fromId, Long toId, double amount) {
return bankDao.transfer(fromId, toId, amount);
}
isolation:事务隔离级别定义;默认为“DEFAULT”
timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
readonly:事务只读设置,默认为false,表示不是只读;
rollbackfor:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚;
norollbackfor:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
基于 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>
传播行为 [,隔离级别] [,只读属性] [,超时属性] [不影响提交的异常] [,导致回滚的异常])
- 超时属性的取值必须以“TIMEOUT_”开头,后面跟一个int类型的值,表示超时时间,单位是秒。
<property name="*Service">
PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED,TIMEOUT_20,
+AbcException,+DefException,-HijException
</property>
<property name="test">PROPAGATION_REQUIRED,readOnly</property>
(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>
<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>定义切入点,与通知,把tx与aop的配置关联,才是完整的声明事务配置 -->
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>
- <!-- 配置事务拦截器-->
- <bean id="transactionInterceptor"
- class="org.springframework.transaction.interceptor.TransactionInterceptor">
- <!-- 事务拦截器bean需要依赖注入一个事务管理器 -->
- <property name="transactionManager" ref="transactionManager"/>
- <property name="transactionAttributes">
- <!-- 下面定义事务传播属性-->
- <props>
- <prop key="insert*">PROPAGATION_REQUIRED</prop>
- <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
- <prop key="*">PROPAGATION_REQUIRED</prop>
- </props>
- </property>
- </bean>
- <!-- 定义BeanNameAutoProxyCreator,该bean是个bean后处理器,无需被引用,因此没有id属性
- 这个bean后处理器,根据事务拦截器为目标bean自动创建事务代理
- <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
- 指定对满足哪些bean name的bean自动生成业务代理 -->
- <property name="beanNames">
- <!-- 下面是所有需要自动创建事务代理的bean-->
- <list>
- <value>personDao</value>
- </list>
- <!-- 此处可增加其他需要自动创建事务代理的bean-->
- </property>
- <!-- 下面定义BeanNameAutoProxyCreator所需的事务拦截器-->
- <property name="interceptorNames">
- <list>
- <value>transactionInterceptor</value>
- <!-- 此处可增加其他新的Interceptor -->
- </list>
- </property>
- </bean>
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("通过代理工厂设置代理对象,拦截代理方法");//调用方法
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">
<list>
<!-- 添加到其中的 Bean 自动就被代理拦截了(下面的是对应的bean的名称,那么当请求InnovatePartBorrowController任何一个方法都会执行MethodTimeAdvice里面的Invoke方法) -->
<value>innovatePartBorrowController</value>
</list>
</property>
</bean>
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方法模块化.Spring 通过 SpringAOP 框架支持声明式事务管理.
(Aspect Oriented Programming)
切面是通知和切点的结合。通知和切点定义了切面的全部内容——它是什么,在何时何处完成其功能。
Signature getSignature() :
获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息;
getSignature().getName();获取调用的方法名 java.lang.Object getThis() :获取代理对象本身;
java.lang.Object getTarget() :获取连接点所在的目标对象;
Signature getSignature() :
获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息;
getSignature().getName();获取调用的方法名 java.lang.Object getThis() :获取代理对象本身;
java.lang.Object getTarget() :获取连接点所在的目标对象;
java.lang.Object proceed() throws java.lang.Throwable:
//执行目标方法
java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable://传入的新的参数去执行目标方法
每个方法都是一个连接点
就是spring允许你执行通知(Advice)的地方,那可就真多了,基本每个方法的钱、后(两者都有也行),或抛出异常是时都可以是连接点,spring只支持方法连接点。其他如AspectJ还可以让你在构造器或属性注入时都行,不过那不是咱们关注的,只要记住,和方法有关的前前后后都是连接点。
(具体在哪些方法上织入通知、这是条件,筛选出需要织入的连接点)
切点有助于缩小切面所通知的连接点的范围。如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”,
上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有十几个连接点了对吧,但是你并不想在所有方法附近都织入通知(使用叫织入),你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
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+)")
//scope :方法作用域,如public,private,protect
//returnt-type:方法返回值类型
//fully-qualified-class-name:方法所在类的完全限定名称
//parameters 方法参数
execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))
//匹配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("execution(public * com.zejian.dao.UserDaoImpl.*(int , ..))")
//匹配名称中带有后缀Service的Bean。
@Pointcut("bean(*Service)")
private void myPointcut1(){}
//匹配了任意实现了UserDao接口的代理对象的方法进行过滤
@Pointcut("this(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut2(){}
//匹配了任意实现了UserDao接口的目标对象的方法进行过滤
@Pointcut("target(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut3(){}
//匹配使用了MarkerAnnotation注解的类(注意是类)
@Pointcut("@within(com.zejian.spring.annotation.MarkerAnnotation)")
private void myPointcut4(){}
//匹配使用了MarkerAnnotation注解的方法(注意是方法)
@Pointcut("@annotation(com.zejian.spring.annotation.MarkerAnnotation)")
private void myPointcut5(){}
@Before("execution(public * net.aazj.service..*.getUser(..)) && args(userId,..)")
public void before3(int userId) {
System.out.println("userId-----" + userId);
}
它会拦截 net.aazj.service 包下或者子包下的getUser方法,并且该方法的第一个参数必须是int型的,
那么使用切点表达式args(userId,..) 就可以使我们在切面中的处理方法before3中可以访问这个参数。
@AfterReturning
(
pointcut=
"execution(* com.abc.service.*.access*(..)) && args(time, name)"
,
returning=
"returnValue"
)
public
void
access(Date time, Object returnValue, String name) {
System.out.println(
"目标方法中的参数String = "
+ name);
System.out.println(
"目标方法中的参数Date = "
+ time);
System.out.println(
"目标方法的返回结果returnValue = "
+ returnValue);
}
表达式中增加了args(time,
name)部分,意味着可以在增强处理方法(access方法)中定义time和name两个属性——这两个形参的类型可以随意指定,但一旦指定了这两个参数的类型,则这两个形参类型将用于限制该切入点只匹配第一个参数类型为Date,第二个参数类型为name的方法(方法参数个数和类型若有不同均不匹配)。
bean(userService): bean的id为 "userService" 的所有方法;
bean(*Service): bean的id为 "Service"字符串结尾的所有方法;
//自定义的接口类,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(){
return (ExInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
/**
* 调用被代理类(目标对象)的任意方法都会触发invoke方法
* @param proxy 代理类
* @param method 被代理类的方法
* @param args 被代理类的方法参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//过滤不需要该业务的方法
if("execute".equals(method.getName())) {
//调用前验证权限
AuthCheck.authCheck();
//调用目标对象的方法
Object result = method.invoke(target, args);
//记录日志数据
Report.recordLog();
return result;
}eles if("delete".equals(method.getName())){
//.....
}
//如果不需要增强直接执行原方法
return method.invoke(target,args);
}
}
//测试验证
public static void main(String args[]){
A a=new A();
//创建JDK代理
JDKProxy jdkProxy=new JDKProxy(a);
//创建代理对象
ExInterface proxy=jdkProxy.createProxy();
//执行代理对象方法
proxy.execute();
}
完全可以把InvocationHandler看成一个回调函数(Callback),Proxy方法创建代理对象proxy后,当调用任何方法(接口中的所有方法)时,将会回调InvocationHandler#invoke方法,因此我们可以在invoke方法中来控制被代理对象(目标对象)的方法执行,从而在该方法前后动态增加其他需要执行的业务。
//被代理的类即目标对象
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生成代理:
// 1.声明增强类实例,用于生产代理类
Enhancer enhancer = new Enhancer();
// 2.设置被代理类字节码,CGLIB根据字节码生成被代理类的子类
enhancer.setSuperclass(target.getClass());
// 3.//设置回调函数,即一个方法拦截
enhancer.setCallback(this);
// 4.创建代理:
return (A) enhancer.create();
}
/**
* 回调函数
* @param proxy 代理对象
* @param method 委托类方法
* @param args 方法参数
* @param methodProxy 每个被代理的方法都对应一个MethodProxy对象,
* methodProxy.invokeSuper方法最终调用委托类(目标类)的原始方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//过滤不需要该业务的方法
if("execute".equals(method.getName())) {
//调用前验证权限(动态添加其他要执行业务)
AuthCheck.authCheck();
//调用目标对象的方法(执行A对象即被代理对象的execute方法)
Object result = methodProxy.invokeSuper(proxy, args);
//记录日志数据(动态添加其他要执行业务)
Report.recordLog();
return result;
}else if("delete".equals(method.getName())){
//.....
return methodProxy.invokeSuper(proxy, args);
}
//如果不需要增强直接执行原方法
return methodProxy.invokeSuper(proxy, args);
}
}
现在可以自己想一想,怎么搞出来这个伪装类,才不会被调用者发现(过JVM的检查,JAVA是强类型检查,哪里都要检查类型)。
1.实现和目标类相同的接口,我也实现和你一样的接口,反正上层都是接口级别的调用,这样我就伪装成了和目标类一样的类(实现了同一接口,咱是兄弟了),也就逃过了类型检查,到java运行期的时候,利用多态的后期绑定(所以spring采用运行时),伪装类(代理类)就变成了接口的真正实现,而他里面包裹了真实的那个目标类,最后实现具体功能的还是目标类,只不过伪装类在之前干了点事情(写日志,安全检查,事物等)。
这就好比,一个人让你办件事,每次这个时候,你弟弟就会先出来,当然他分不出来了,以为是你,你这个弟弟虽然办不了这事,但是他知道你能办,所以就答应下来了,并且收了点礼物(写日志),收完礼物了,给把事给人家办了啊,所以你弟弟又找你这个哥哥来了,最后把这是办了的还是你自己。但是你自己并不知道你弟弟已经收礼物了,你只是专心把这件事情做好。
顺着这个思路想,要是本身这个类就没实现一个接口呢,你怎么伪装我,我就压根没有机会让你搞出这个双胞胎的弟弟,那么就用第2种代理方式,创建一个目标类的子类,生个儿子,让儿子伪装我
2.生成子类调用,这次用子类来做为伪装类,当然这样也能逃过JVM的强类型检查,我继承的吗,当然查不出来了,子类重写了目标类的所有方法,当然在这些重写的方法中,不仅实现了目标类的功能,还在这些功能之前,实现了一些其他的(写日志,安全检查,事物等)。
这次的对比就是,儿子先从爸爸那把本事都学会了,所有人都找儿子办事情,但是儿子每次办和爸爸同样的事之前,都要收点小礼物(写日志),然后才去办真正的事。当然爸爸是不知道儿子这么干的了。这里就有件事情要说,某些本事是爸爸独有的(final的),儿子学不了,学不了就办不了这件事,办不了这个事情,自然就不能收人家礼了。
前一种兄弟模式,spring会使用JDK的java.lang.reflect.Proxy类,它允许Spring动态生成一个新类来实现必要的接口,织入通知,并且把对这些接口的任何调用都转发到目标类。
后一种父子模式,spring使用CGLIB库生成目标类的一个子类,在创建这个子类的时候,spring织入通知,并且把对这个子类的调用委托到目标类。
相比之下,还是兄弟模式好些,他能更好的实现松耦合,尤其在今天都高喊着面向接口编程的情况下,父子模式只是在没有实现接口的时候,也能织入通知,应当做一种例外。
AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别。Spring 2.0后便使用了与AspectJ一样的注解。
请注意,Spring 只是使用了与 AspectJ 5 一样的注解,但仍然没有使用 AspectJ 的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ 的编译器。
在通知上使用
execution关键字定义的切点表达式
@Aspect
public class MyAspect {
/**
* 前置通知
*/
@Before("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
public void before(){
System.out.println("前置通知....");
}
/**
* 后置返回通知
* returnVal,切点方法执行后的返回值
*/
@AfterReturning(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))",returning = "returnVal")
public void AfterReturning(Object returnVal){
System.out.println("后置返回通知...."+returnVal);
}
/**
* 环绕通知
* @param joinPoint 可用于执行切点的类
* @return
* @throws Throwable
*/
@Around("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知前....");
Object obj= (Object) joinPoint.proceed();
System.out.println("环绕通知后....");
return obj;
}
/**
* 异常通知
* @param e
*/
@AfterThrowing(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))",throwing = "e")
public void afterThrowable(Throwable e){
System.out.println("异常通知:msg="+e.getMessage());
}
/**
* 无论什么情况下都会执行的方法
*/
@After(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
public void after(){
System.out.println("后置通知....");
}
}
/**
* 使用Pointcut定义切点
*/
@Pointcut("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
private void myPointcut(){}
/**
* 应用切入点函数
*/
@After(value="myPointcut()")
public void afterDemo(){
System.out.println("后置通知....");
}
<!-- 启动@aspectj的自动代理支持-->
<aop:aspectj-autoproxy />
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=====后置通知..来了");
}
}
<!-- 定义切面 -->
<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(..))" />
<!-- 定义通知 order 定义优先级,值越小优先级越大-->
<aop:aspect ref="myAspectXML" order="0">
<!-- 定义通知
method 指定通知方法名,必须与MyAspectXML中的相同
pointcut 指定切点函数
-->
<aop:before method="before" pointcut-ref="pointcut" />
<!-- 后置通知 returning="returnVal" 定义返回值 必须与类中声明的名称一样-->
<aop:after-returning method="afterReturn" pointcut-ref="pointcut" returning="returnVal" />
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pointcut" />
<!--异常通知 throwing="throwable" 指定异常通知错误信息变量,必须与类中声明的名称一样-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="throwable"/>
<!--
method : 通知的方法(最终通知)
pointcut-ref : 通知应用到的切点方法
-->
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
public aspect MyAspectJDemo {
/**
* 定义切点,日志记录切点
pointcut 函数名 : 匹配表达式
函数一般使用call()或者execution()进行匹配
*/
pointcut recordLog():call(* HelloWord.sayHello(..));
/**
* 定义切点,权限验证(实际开发中日志和权限一般会放在不同的切面中,这里仅为方便演示)
*/
pointcut authCheck():call(* HelloWord.sayHello(..));
/**
* 定义前置通知!
*/
before():authCheck(){
System.out.println("sayHello方法执行前验证权限");
}
/**
* 定义后置通知
*/
after():recordLog(){
System.out.println("sayHello方法执行后记录日志");
}
/**
* 定义后置通知带返回值
* after(参数)returning(返回值类型):连接点函数{
* 函数体
* }
*/
after()returning(int x): get(){
System.out.println("返回值为:"+x);
}
/**
* 异常通知
* after(参数) throwing(返回值类型):连接点函数{
* 函数体
* }
*/
after() throwing(Exception e):sayHello2(){
System.out.println("抛出异常:"+e.toString());
}
/**
* 环绕通知 可通过proceed()控制目标函数是否执行
* Object around(参数):连接点函数{
* 函数体
* Object result=proceed();//执行目标函数
* return result;
* }
*/
Object around():aroundAdvice(){
System.out.println("sayAround 执行前执行");
Object result=proceed();//执行目标函数
System.out.println("sayAround 执行后执行");
return result;
}
}
2.@Aspectj 注解驱动的切面
3.纯POJO切面
4.注入式Aspectj切面编程
前面3个是spring基于代理的AOP编程,所以局限于方法拦截.如果要对属性或构造器拦截者应调用 Aspectj里面的切面.
可以延迟对象的加载,即在运行期再确定调用者和被调用者的关系。(解耦)
? 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。
例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为"?" 具体设置为 0 0 0 10 * ?
- 表示区间。例如在小时上设置 "10-12",表示 10,11,12点都会触发。
, 表示指定多个值,例如在周字段上设置 "MON,WED,FRI" 表示周一,周三和周五触发
/ 用于递增触发。如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)。
在月字段上设置'1/3'所示每月1号开始,每隔三天触发一次。
L 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]),
在周字段上表示星期六,相当于"7"或"SAT"。如果在"L"前加上数字,则表示该数据的最后一个。
例如在周字段上设置"6L"这样的格式,则表示“本 月最后一个星期五"
W 表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上设置"15W",表示离每月15号最近的那个工作日触发。
如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.
如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 "1W",它则表示每月1号往后最近的工作日触发。
如果1号正是周六,则将在3号下周一触发。(注,"W"前只能设置具体的数字,不允许区间"-").
小提示
'L'和 'W'可以一组合使用。如果在日字段上设置"LW",则表示在本月的最后一个工作日触发(一般指发工资 )
# 序号(表示每月的第几个周几),例如在周字段上设置"6#3"表示在每月的第三个周六.注意如果指定"#5",
正好第五周没有周六,则不会触发该配置(用 在母亲节和父亲节再合适不过了)
小提示
周字段的设置,若使用英文字母是不区分大小写的 MON 与mon相同.
@ComponentScan("org.sang")
@EnableScheduling//开启对计划任务的支持
public class MyConfig {
}
BeanFactory和ApplicationContext有什么区别
BeanFactory 可以理解为含有bean集合的工厂类。BeanFactory 包含了种bean的定义,以便在接收到客户端请求时将对应的bean实例化。
BeanFactory还能在实例化对象的时生成协作类之间的关系。此举将bean自身与bean客户端的配置中解放出来。BeanFactory还包含了bean生命周期的控制,调用客户端的初始化方法(initialization methods)和销毁方法(destruction methods)。
application context是beanFactory的子接口,拥有BeanFactory的所有功能,但applicationcontext在此基础上还提供了其他的功能。
- 提供了支持国际化的文本消息
- 统一的资源文件读取方式
- 已在监听器中注册的bean的事件
且beanFactory是延迟加载,需要类的时候才创建类的实例,而ApplicationContext在初始化时就加载完成了所有的单例bean
以下是三种较常见的 ApplicationContext 实现方式:
1、ClassPathXmlApplicationContext:从classpath的XML配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得
ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);
2、FileSystemXmlApplicationContext :由文件系统中的XML配置文件读取上下文。ApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);
3、XmlWebApplicationContext:由Web应用的XML文件读取上下文。
解释Spring Bean的生命周期
Spring Bean的生命周期简单易懂。在一个bean实例被初始化时,需要执行一系列的初始化操作以达到可用的状态。同样的,当一个bean不在被调用时需要进行相关的析构操作,并从bean容器中移除。
Spring bean factory 负责管理在spring容器中被创建的bean的生命周期。Bean的生命周期由两组回调(call back)方法组成。
- 初始化之后调用的回调方法。
- 销毁之前调用的回调方法。
Spring框架提供了以下四种方式来管理bean的生命周期事件:
- InitializingBean和DisposableBean回调接口
- 针对特殊行为的其他Aware接口
- Bean配置文件中的Custom init()方法和destroy()方法
- @PostConstruct和@PreDestroy注解方式
使用customInit()
和customDestroy()
方法管理
bean
生命周期的代码样例如下:
- <beans> <bean id="demoBean" class="com.somnus.task.DemoBean" init-method="customInit" destroy-method="customDestroy"></bean> </beans>
Spring Bean的作用域
Spring容器中的bean可以分为5个范围。所有范围的名称都是自说明的,但是为了避免混淆,还是让我们来解释一下:
- singleton:这种bean范围是默认的,这种范围确保不管接受到多少个请求,每个容器中只有一个bean的实例,单例的模式由bean factory自身来维护。
- prototype:原形范围与单例范围相反,为每一个bean请求提供一个实例。
- request:在请求bean范围内会每一个来自客户端的网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
- Session:与请求范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
- global-session:global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。
Spring框架中的单例Beans是线程安全的吗
构造方法注入和设值注入有什么区别
- 在设值注入方法支持大部分的依赖注入,如果我们仅需要注入int、string和long型的变量,我们不要用设值的方法注入。对于基本类型,如果我们没有注入的话,可以为基本类型设置默认值。在构造方法注入不支持大部分的依赖注入,因为在调用构造方法中必须传入正确的构造参数,否则的话为报错。
- 设值注入不会重写构造方法的值。如果我们对同一个变量同时使用了构造方法注入又使用了设置方法注入的话,那么构造方法将不能覆盖由设值方法注入的值。很明显,因为构造方法尽在对象被创建时调用。
- 在使用设值注入时有可能还不能保证某种依赖是否已经被注入,也就是说这时对象的依赖关系有可能是不完整的。而在另一种情况下,构造器注入则不允许生成依赖关系不完整的对象。
- 在设值注入时如果对象A和对象B互相依赖,在创建对象A时Spring会抛出s
ObjectCurrentlyInCreationException异常,因为在B对象被创建之前A对象是不能被创建的,反之亦然。所以Spring用设值注入的方法解决了循环依赖的问题,因对象的设值方法是在对象被创建之前被调用的。
Spring框架中有哪些不同类型的事件
Spring的ApplicationContext
提供了支持事件和代码中监听器的功能。
我们可以创建bean用来监听在ApplicationContext
中发布的事件。ApplicationEven
t类和在ApplicationContext
接口
中处理的事件,如果一个bean实现了ApplicationListener
接口,当一个ApplicationEvent
被发布以后,bean会自动被通知。
- public class AllApplicationEventListener implements ApplicationListener < ApplicationEvent >{
- @Override
- public void onApplicationEvent(ApplicationEvent applicationEvent)
- {
- //process event
- }
- }
Spring 提供了以下5中标准的事件:
- 上下文更新事件(ContextRefreshedEvent):该事件会在ApplicationContext被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
- 上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
- 上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
- 上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
- 请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。
扩展ApplicationEvent
类来开发自定义的事件。
- public class CustomApplicationEvent extends ApplicationEvent{
- public CustomApplicationEvent ( Object source, final String msg ){
- super(source);
- System.out.println("Created a Custom event");
- }
- }
为了监听这个事件,还需要创建一个监听器:- public class CustomEventListener implements ApplicationListener < CustomApplicationEvent >{
- @Override
- public void onApplicationEvent(CustomApplicationEvent applicationEvent) {
- //handle event
- }
- }
之后通过applicationContext接口的
publishEvent()方法来发布自定义事件。
- CustomApplicationEvent customEvent = new CustomApplicationEvent(applicationContext, "Test message");
- applicationContext.publishEvent(customEvent);
FileSystemResource和ClassPathResource有何区别
在FileSystemResource
中需要给出spring-config.xml
文件在你项目中的相对路径或者绝对路径。在ClassPathResource
中spring会在ClassPath中自动搜寻配置文件,所以要把ClassPathResource
文件放在ClassPath下。
如果将spring-config.xml
保存在了src文件夹下的话,只需给出配置文件的名称即可,因为src文件夹是默认。
简而言之,ClassPathResource在环境变量中读取配置文件,FileSystemResource在配置文件中读取配置文件。
Spring 框架中都用到了哪些设计模式
Spring框架中使用到了大量的设计模式,下面列举了比较有代表性的:
- 代理模式—在AOP和remoting中被用的比较多。
- 单例模式—在spring配置文件中定义的bean默认为单例模式。
- 模板方法—用来解决代码重复的问题。比如. RestTemplate,
JmsTemplate
,JpaTemplate。
- 前端控制器—Spring提供了
DispatcherServlet
来对请求进行分发。
- 视图帮助(View Helper )—Spring提供了一系列的JSP标签,高效宏来辅助将分散的代码整合在视图里。
- 依赖注入—贯穿于
BeanFactory
/ApplicationContext
接口的核心理念。
- 工厂模式—BeanFactory用来创建对象的实例。
2.可以使用容易提供的众多服务,如事务管理,消息服务等
3.容器提供单例模式支持
4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能
5.容器提供了众多的辅助类,能加快应用的开发
6.spring对于主流的应用框架提供了集成支持,如hibernate,JPA,Struts等
7.spring属于低侵入式设计,代码的污染极低
8.独立于各种应用服务器
9.spring的DI机制降低了业务对象替换的复杂性
10.Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可以自由选择spring 的部分或全部