[TOC]
CGLIB与JDK动态代理
使用JDK创建代理有一个限制,即它只能为接口创建代理实例,这一点可以从Proxy的接口方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)中看得很清楚;第二个传入参数interfaces就是需要代理实例实现的接口列表。对于没有通过接口定义业务方法的类,如何动态创建代理实例呢?JDK动态代理技术显然已经黔驴技穷,CGLIB作为一个替代者,填补了这些空缺。
proxy-target-class
proxy-target-class这个属性是决定基于接口还是基于类的代理被创建,默认为false。如果proxy-target-class属性值被设置为true,那么基于类的代理将起作用(这个时候需要cglib库)。如果proxy-target-class被设置为false或者这个属性被省略,那么表示使用JDK动态代理织入增强,但是,即使设置为false,如果目标类没有声明接口,则Spring将自动使用CGLIB动态代理。
通俗理解:当要使用实现了某个接口的类让Spring来生成Bean时,无需再AOP配置中添加proxy-target-class,因为它默认是false。但如果要使用一个指定的类,让Spring来生成Bean,并使用它的某个方法时,需要在AOP配置上加上一句proxy-target-class=true,否则使用CGLIB,会出现
Java.lang.ClassCastException:com.sun.proxy.$Proxy6 cannot be cast to glut.daoImpl.DAOImpl
类似的错误。
使用@Autowired和@Resource自动装配Bean
解决方案
从Spring2.5起,可以通过@Autowired或者@Resource注解一个设值方法,构造程序,字段甚至任意方法自动装配特定的属性。
工作原理
为了要求Spring自动装配具有@Autowired或者@Resource注解的属性,你必须在IoC容器中注册一个AutowiredAnnotationBeanPostProcess实例。如果你使用一个Bean工厂,就必须通过API注册这个Bean后处理器,否则,你只能在你的应用上下文里声明一个实例。
你也可以简单地在Bean配置文件中包含
- 自动装配一个或多个兼容类型的Bean
@Autowired注解还可以应用到一个字段,即使这个字段没有声明为public。这样,你不能省略这个字段的设值方法或者构造程序的声明。Spring通过反射把匹配的Bean注入这个字段。但是,用@Autowired注解非公开的字段将降低代码的可测试性,因为代码将很难进行单元测试(黑盒测试无法使用模拟对象之类的方式操纵这一状态) - 使用限定符的按类型自动装配
默认情况下,按照类型的自动装配在IoC容器中有超过一个类型兼容的Bean时无效。但是,Spring允许你指定一个候选Bean,这个Bean的名称在@Qualifier注解中提供。
@Autowired
@Qualifier("datePrefixGenerator")
- 按照名称自动装配
需要按照名称自动装配Bean属性,可以用JSR-250 @Resource注解为一个设值方法,构造程序或者字段加上注解。默认情况下,Spring将试图找到一个与属性同名的Bean。但是你可以显式地在name属性中指定Bean名称
@Resouce(name = "datePrefixGenerator")
从Classpath中扫描组件
有了应用到组件类的典型化注解,就能通过声明一个XML元素
Camel-cased命名法:当一个命名包含多个单词时,每个单词的第一个字母大写
因此,下面的语句是有效的(假定你已经实例化了一个包含
SequenceService service = (SequenceService)context.getBean("sequenceService");
注意,这个元素还将注册一个AutowiredAnnotationBeanPostProcessor实例,这个实例能够自动装配带有@Autowired注解的属性。
过滤扫描的组件
默认情况下,Spring将检测所有用@Component,@Repository,@Service,@Controller或者本身加上@Component注解的自定义注解类型。你可以应用一个或多个包含/排除过滤器自定义这一扫描。Spring支持4中过滤器表达式。annotation和assignable类型用于指定过滤的注解类型和类/接口。regex和aspectj类型允许指定正则表达式和AspectJ切入点表达式匹配类。你还可以用use-default-filters属性禁用默认过滤器。
例如,下面的组件扫描包含了所有名称中包含Dao或Service的类,排除带有@Controller注解的类:
因为你已经应用了include过滤器检测所有名称包含Dao或者Service的类,SequenceDaoImpl和SequenceService组件就能在没有典型化注解的情况下被自动检测出来。
命名检测到的组件
默认情况下,Spring将非限定类名的第一个字符改为小写来命名检测到的组件。你可以在典型化注解值中显式地指定组件的名称。
设置Bean作用域
在Spring 2.x或者更新版本中,Bean的作用域在
作用域 | 描述 |
---|---|
Singleton | 每个Spring IoC容器创建一个Bean实例 |
Prototype | 每次请求时创建一个新的Bean实例 |
Request | 为每个HTTP请求创建一个Bean实例,仅在Web应用上下文中有效 |
Session | 为每个HTTP会话创建一个Bean实例,仅在Web应用上下文中有效 |
GlobalSession | 为每个全局HTTP会话创建一个Bean实例,仅在门户应用上下文有效 |
自定义Bean初始化和析构
下面的列表展示了Spring IoC容器管理Bean周期的步骤。这个列表将随着IoC容器更多特性的引入而扩展。
- 构造程序或者工厂方法创建Bean实例
- 想Bean属性设置值和Bean引用
- 调用初始化回调方法
- Bean就绪
- 容器关闭时,调用析构回调方法
Spring有三种识别初始化和析构回调方法的方式。
- 你的Bean可以实现 InitializingBean 和 DisposableBean 生命周期接口,并且实现用于初始化和析构的 afterPropertiesSet() 和 destory() 方法
public class Cashier implements InitializingBean, DisposableBean {
...
public void afterPropertiesSet() throws Exception {
openFile();
}
public void destory() throws Exception {
closeFile();
}
}
但是,实现这些专利接口将会使你的Bean变成Spring专用的,无法再Spring IoC容器之外重用
- 你可以在Bean声明中设置 init-method 和 destory-method 属性,指定回调方法名称。
- 在Spring 2.5或更高版本中,你还可以用生命周期注解 @PostContruct 和 @PreDestory 注解初始化和析构回调方法,这两个注解在JSR-250定义,然后你可以在IoC容器中注册 CommonAnnotationBeanPost Processor 实例来调用这些回调方法。
public class Cashier {
...
@PostConstruct
public void openFile() throws IOException {...}
@PreDestory
public void closeFile() throws IOException {...}
}
启用Spring的AspectJ注解支持
在Bean配置文件中定义一个空的XML元素
用AspectJ注解声明aspect
- 前置通知(Before advice)
为了创建在程序特定执行点之前处理横切关注点的前置通知,你可以使用@Before注解,并将切入点表达式作为注解值
最终通知(after advice)
在连接点结束之后执行,不管返回结果还是抛出异常。后置通知(after returning advice)
最终通知不管连接点正常返回还是抛出异常都执行。如果你希望仅当连接点返回时记录,应该用后置通知异常通知(after throwing advice)
仅当连接点抛出异常时执行环绕通知(around advice)
获得连接点的完全控制,这样可以在一个通知中组合前述的通知的所有行动。甚至可以控制合适以及是否继续原来的连接点执行。注意环绕通知,连接点的参数类型必须是ProceedingJoinPoint。这是JoinPoint的一个子接口,允许你控制合适继续原始连接点。
@Aspect
public class CalculatorLoggingAspect {
@Around("execution(* *.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
....
}
}