使用Spring AOP向领域模型注入依赖
来源:互联网 发布:账户表数据库设计 编辑:程序博客网 时间:2024/06/10 01:16
在贫血领域模型这篇译文中,Martin阐述了这种“反模式”的症状和问题,并引用了领域驱动设计中的话来说明领域模型和分层设计之间的关系。对于Spring项目的开发人员来说,贫血领域模型十分常见:模型(或实体)仅仅包含对数据表的映射,通常是一组私有属性和公有getter/setter,所有的业务逻辑都写在服务层中,领域模型仅仅用来传递数据。为了编写真正的领域模型,我们需要将业务逻辑移至模型对象中,这就引出另一个问题:业务逻辑通常需要调用其他服务或模型,而使用new
关键字或由JPA创建的对象是不受Spring托管的,也就无法进行依赖注入。解决这个问题的方法有很多,比较之后我选择使用面向切面编程来实现。
面向切面编程
面向切面编程,或AOP,是一种编程范式,和面向对象编程(OOP)互为补充。简单来说,AOP可以在不修改既有代码的情况下改变代码的行为。开发者通过定义一组规则,在特定的类方法前后增加逻辑,如记录日志、性能监控、事务管理等。这些逻辑称为切面(Aspect),规则称为切点(Pointcut),在调用前还是调用后执行称为通知(Before advice, After advice)。最后,我们可以选择在编译期将这些逻辑写入类文件,或是在运行时动态加载这些逻辑,这是两种不同的织入方式(Compile-time weaving, Load-time weaving)。
对于领域模型的依赖注入,我们要做的就是使用AOP在对象创建后调用Spring框架来注入依赖。幸运的是,Spring AOP已经提供了@Configurable
注解来帮助我们实现这一需求。
Configurable注解
Spring应用程序会定义一个上下文容器,在该容器内创建的对象会由Spring负责注入依赖。对于容器外创建的对象,我们可以使用@Configurable
来修饰类,告知Spring对这些类的实例也进行依赖注入。
假设有一个Report
类(领域模型),其中一个方法需要解析JSON,我们可以使用@Configurable
将容器内的ObjectMapper
对象注入到类的实例中:
@Entity@Configurable(autowire = Autowire.BY_TYPE)public class Report { @Id @GeneratedValue private Integer id; @Autowired @Transient private ObjectMapper mapper; public String render() { mapper.readValue(...); }}
autowire
参数默认是NO
,因此需要显式打开,否则只能使用XML定义依赖。@Autowired
是目前比较推荐的注入方式。@Transient
用于告知JPA该属性不需要进行持久化。你也可以使用transient
关键字来声明,效果相同。- 项目依赖中需要包含
spring-aspects
。如果已经使用了spring-boot-starter-data-jpa
,则无需配置。 - 应用程序配置中需要加入
@EnableSpringConfigured
:
@SpringBootApplication@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)@EnableSpringConfiguredpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}
- 在
src/main/resources
目录下,新建META-INF/aop.xml
文件,用来限定哪些包会用到AOP。否则,AOP的织入操作会作用于所有的类(包括第三方类库),产生不必要的的报错信息。
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd"><aspectj> <weaver> <include within="com.foobar..*"/> </weaver></aspectj>
运行时织入(Load-Time Weaving, LTW)
除了项目依赖和应用程序配置,我们还需要选择一种织入方式来使AOP生效。Spring AOP推荐的方式是运行时织入,并提供了一个专用的Jar包。运行时织入的原理是:当类加载器在读取类文件时,动态修改类的字节码。这一机制是从JDK1.5开始提供的,需要使用-javaagent
参数开启,如:
$ java -javaagent:/path/to/spring-instrument.jar -jar app.jar
在测试时发现,Spring AOP提供的这一Jar包对普通的类是有效果的,但对于使用@Entity
修饰的类就没有作用了。因此,我们改用AspectJ提供的Jar包(可到Maven中央仓库下载):
$ java -javaagent:/path/to/aspectjweaver.jar -jar app.jar
对于Spring Boot应用程序,可以在Maven命令中加入以下参数:
$ mvn spring-boot:run -Drun.agent=/path/to/aspectjweaver.jar
此外,在使用AspectJ作为LTW的提供方后,会影响到Spring的事务管理,因此需要在应用程序配置中加入:
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
AnnotationBeanConfigurerAspect
到这里我们已经通过简单配置完成了领域模型的依赖注入,这背后都是Spring中的AnnotationBeanConfigurerAspect
在做工作。我们不妨浏览一下精简后的源码:
AnnotationBeanConfigurerAspect.aj
public aspect AnnotationBeanConfigurerAspect implements BeanFactoryAware { private BeanConfigurerSupport beanConfigurerSupport = new BeanConfigurerSupport(); public void setBeanFactory(BeanFactory beanFactory) { this.beanConfigurerSupport.setBeanFactory(beanFactory); } public void configureBean(Object bean) { this.beanConfigurerSupport.configureBean(bean); } public pointcut inConfigurableBean() : @this(Configurable); declare parents: @Configurable * implements ConfigurableObject; public pointcut beanConstruction(Object bean) : initialization(ConfigurableObject+.new(..)) && this(bean); after(Object bean) returning : beanConstruction(bean) && inConfigurableBean() { configureBean(bean); }}
.aj
文件是AspectJ定义的语言,增加了pointcut、after等关键字,用来定义切点、通知等;inConfigurationBean
切点用于匹配使用Configurable
修饰的类型;declare parents
将这些类型声明为ConfigurableObject
接口,从而匹配beanConstruction
切点;ConfigurableObject+.new(..)
表示匹配该类型所有的构造函数;after
定义一个通知,表示对象创建完成后执行configureBean
方法;- 该方法会调用
BeanConfigurerSupport
来对新实例进行依赖注入。
其它方案
- 将依赖作为参数传入。比如上文中的
render
方法可以定义为render(ObjectMapper mapper)
。 - 将
ApplicationContext
作为某个类的静态成员,领域模型通过这个引用来获取依赖。 - 编写一个工厂方法,所有新建对象都要通过这个方法生成,进行依赖注入。
- 如果领域模型大多从数据库获得,并且JPA的提供方是Hibernate,则可以使用它的拦截器功能进行依赖注入。
参考资料
- http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#aop-atconfigurable
- http://blog.igorstoyanov.com/2005/12/dependency-injection-or-service.html
- http://jblewitt.com/blog/?p=129
- 使用Spring AOP向领域模型注入依赖
- Spring学习,依赖注入(DI)、控制反转(IoC)和面向切面(AOP)
- spring依赖注入和Aop的理解
- spring依赖注入使用小记
- Spring使用Setter依赖注入
- 使用Spring进行依赖注入
- Spring使用注解依赖注入
- 使用spring的aop实现权限拦截后出现依赖注入为空的问题
- 使用spring的aop实现拦截action后出现依赖注入为空的问题
- spring使用aop时需要设置proxy-target-class="true" 否则无法依赖注入
- spring使用aop时需要设置proxy-target-class="true" 否则无法依赖注入
- spring使用aop时需要设置proxy-target-class="true" 否则无法依赖注入
- spring使用aop时需要设置proxy-target-class="true" 否则无法依赖注入
- spring使用aop时需要设置proxy-target-class="true" 否则无法依赖注入
- spring使用aop时需要设置proxy-target-class="true" 否则无法依赖注入
- spring使用aop时需要设置proxy-target-class="true" 否则无法依赖注入
- spring使用aop时需要设置proxy-target-class="true" 否则无法依赖注入
- 关于依赖注入、Aop
- centos6下增加swap空间
- 环境变量中classpath 和 path区别
- 【思维导图训练1】--思维导图的基本概述
- tomcat startup.bat 无法启动问题解决方法 标签: startup.bat启动脚本tomcatTomcat 2012-12-06 21:52 10904人阅读 评论(0) 收藏 举报
- Qt4.8.7 linux 程序移植到 Qt5.9 windows 记录过程
- 使用Spring AOP向领域模型注入依赖
- 神奇的幻方(NOIP2015)
- 【思维导图训练2】--发散思维的秘诀
- 013讲爬虫网站图片
- maven工程的创建(四)--运行测试
- 【Quartz】——与spring整合
- POJ 3630:Phone List(字典树入门)
- linux学习之旅(二十)& mariadb数据库
- CodeForces