Spring教程检视阅读(中)
Spring 自动装配 byName
这种模式由属性名称指定自动装配。Spring 容器看作 beans,在 XML 配置文件中 beans 的 auto-wire 属性设置为 byName。然后,它尝试将它的属性与配置文件中定义为相同名称的 beans 进行匹配和连接。如果找到匹配项,它将注入这些 beans,否则,它将抛出异常。
实例:
public class SoftEngineer {
public SoftEngineer() {
System.out.println("----------SoftEngineer constructor");
}
private Computer computer;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Computer getComputer() {
return computer;
}
public void setComputer(Computer computer) {
this.computer = computer;
}
public void useComputer(){
computer.coding();
}
}
public class Computer {
public Computer() {
System.out.println("-------------Computer constructor");
}
public void coding(){
System.out.println("--------------product codes");
}
}
SoftEngineer softEngineer = (SoftEngineer)context.getBean("softEngineer");
softEngineer.useComputer();
<bean id="softEngineer" class="com.self.SoftEngineer" autowire="byName">
<property name="name" value="lairf"></property>
</bean>
<bean id="computer" class="com.self.Computer"/>
Spring 自动装配 byType
这种模式由属性类型指定自动装配。Spring 容器看作 beans,在 XML 配置文件中 beans 的 autowire 属性设置为 byType。然后,如果它的 type 恰好与配置文件中 beans 名称中的一个相匹配,它将尝试匹配和连接它的属性。如果找到匹配项,它将注入这些 beans,否则,它将抛出异常。
<bean id="softEngineer" class="com.self.SoftEngineer" autowire="byType">
<property name="name" value="lairf"></property>
</bean>
<bean id="computer" class="com.self.Computer"/>
Spring 由构造函数自动装配
在 XML 配置文件中 beans 的 autowire 属性设置为 constructor。然后,它尝试把它的构造函数的参数与配置文件中 beans 名称中的一个进行匹配和连线。如果找到匹配项,它会注入这些 bean,否则,它会抛出异常。
例子:
public class SoftEngineer {
public SoftEngineer(Computer computer, String name) {
this.computer = computer;
this.name = name;
}
private Computer computer;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void useComputer(){
computer.coding();
}
}
<bean id="softEngineer" class="com.self.SoftEngineer" autowire="constructor">
<constructor-arg name="name" value="happy"></constructor-arg>
</bean>
<bean id="computer" class="com.self.Computer"/>
Spring 基于注解的配置
使用相关类,方法或字段声明的注解,将 bean 配置移动到组件类本身。
注解连线在默认情况下在 Spring 容器中不打开。因此,在可以使用基于注解的连线之前,我们将需要在我们的 Spring 配置文件中启用它。
<!--配置注解打开-->
<context:annotation-config/>
几个重要的注解:
序号 | 注解 & 描述 |
---|---|
1 | @Required@Required 注解应用于 bean 属性的 setter 方法。 |
2 | @Autowired@Autowired 注解可以应用到 bean 属性的 setter 方法,非 setter 方法,构造函数和属性。 |
3 | @Qualifier通过指定确切的将被连线的 bean,@Autowired 和 @Qualifier 注解组合使用可以用来删除混乱。 |
4 | JSR-250 AnnotationsSpring 支持 JSR-250 的基础的注解,其中包括了 @Resource,@PostConstruct 和 @PreDestroy 注解。 |
Spring @Required 注释
@Required 注释应用于 bean 属性的 setter 方法,它表明受影响的 bean 属性在配置时必须放在 XML 配置文件中,否则容器就会抛出一个 BeanInitializationException 异常。
Caused by: org.springframework.beans.factory.BeanInitializationException: Property 'qualifyStatus' is required for bean 'manager'
例子:
public class Manager {
private String name;
private String qualifyPaper;
private String qualifyStatus;
public String getName() {
return name;
}
@Required
public void setName(String name) {
this.name = name;
}
public String getQualifyPaper() {
return qualifyPaper;
}
public void setQualifyPaper(String qualifyPaper) {
this.qualifyPaper = qualifyPaper;
}
public String getQualifyStatus() {
return qualifyStatus;
}
@Required
public void setQualifyStatus(String qualifyStatus) {
this.qualifyStatus = qualifyStatus;
}
@Override
public String toString() {
return "Manager{" +
"name='" + name + '\'' +
", qualifyPaper='" + qualifyPaper + '\'' +
", qualifyStatus='" + qualifyStatus + '\'' +
'}';
}
}
<!--配置注解打开-->
<context:annotation-config/>
<bean id="manager" class="com.self.Manager">
<property name="name" value="laist"></property>
<property name="qualifyPaper" value="LevelOne"></property>
<property name="qualifyStatus" value="1"/>
</bean>
Spring @Autowired 注释
@Autowired 注释对在哪里和如何完成自动连接提供了更多的细微的控制。
Setter 方法中的 @Autowired
你可以在 java 文件中的 setter 方法中使用 @Autowired 注释去除xml中的元素配置。当 Spring遇到一个在 setter 方法中使用的 @Autowired 注释,它会在方法中视图执行 byType 自动连接。
注意是执行 byType 自动连接。
例子:
public class SoftEngineer {
public SoftEngineer() {
System.out.println("----------SoftEngineer constructor");
}
private Computer computer;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Computer getComputer() {
return computer;
}
@Autowired
public void setComputer(Computer computer) {
this.computer = computer;
}
public void useComputer(){
computer.coding();
}
}
配置文件里只需要把bean配置注册上即可,不需要其他动作。后面还可以通过@Component注解来省略掉注册动作。
<bean id="softEngineer" class="com.self.SoftEngineer"/>
<bean id="computer" class="com.self.Computer"/>
属性中的 @Autowired
你可以在属性中使用 @Autowired 注释来除去 setter 方法。当时使用 属性中使用 @Autowired为自动连接属性传递的时候,Spring 会将这些传递过来的值或者引用自动分配给那些属性。
例子:
public class SoftEngineer {
public SoftEngineer() {
System.out.println("----------SoftEngineer constructor");
}
@Autowired
private Computer computer;
public void useComputer(){
computer.coding();
}
}
构造函数中的 @Autowired
你也可以在构造函数中使用 @Autowired。一个构造函数 @Autowired 说明当创建 bean 时,即使在 XML 文件中没有使用 元素配置 bean ,构造函数也会被自动连接。
public class SoftEngineer {
@Autowired
public SoftEngineer(Computer computer) {
this.computer = computer;
}
private Computer computer;
public void useComputer(){
computer.coding();
}
}
@Autowired 的(required=false)选项
默认情况下,@Autowired 注释意味着依赖是必须的,它类似于 @Required 注释,然而,你可以使用 @Autowired 的 (required=false) 选项关闭默认行为。
即使你不为 name属性传递任何参数,下面的示例也会成功运行,但是对于 qualifyStatus属性则需要一个参数。
public class Manager {
private String name;
private String qualifyPaper;
private String qualifyStatus;
public String getName() {
return name;
}
//@Required
@Autowired(required = false)
public void setName(String name) {
this.name = name;
}
public String getQualifyPaper() {
return qualifyPaper;
}
public void setQualifyPaper(String qualifyPaper) {
this.qualifyPaper = qualifyPaper;
}
public String getQualifyStatus() {
return qualifyStatus;
}
//@Required
@Autowired
public void setQualifyStatus(String qualifyStatus) {
this.qualifyStatus = qualifyStatus;
}
@Override
public String toString() {
return "Manager{" +
"name='" + name + '\'' +
", qualifyPaper='" + qualifyPaper + '\'' +
", qualifyStatus='" + qualifyStatus + '\'' +
'}';
}
}
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'manager': Unsatisfied dependency expressed through method 'setQualifyStatus' parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'manager': Unsatisfied dependency expressed through method 'setQualifyStatus' parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
在使用@Autowired
给属性值注入时会报Field injection is not recommended警告。这个在spring3之后会报这个警告,原因是对于field的注入方式:
优点:
变量方式注入非常简洁,没有任何多余代码,非常有效的提高了java的简洁性。即使再多几个依赖一样能解决掉这个问题。
缺点:
不能有效的指明依赖。相信很多人都遇见过一个bug,依赖注入的对象为null,在启动依赖容器时遇到这个问题都是配置的依赖注入少了一个注解什么的,然而这种方式就过于依赖注入容器了,当没有启动整个依赖容器时,这个类就不能运转,在反射时无法提供这个类需要的依赖。
set的注入方式:
在使用set方式时,这是一种选择注入,可有可无,即使没有注入这个依赖,那么也不会影响整个类的运行。
构造器的注入方式:
在使用构造器方式时已经显式注明必须强制注入。通过强制指明依赖注入来保证这个类的运行。
//新
private final IMcSettingsSourceService mcSettingsSourceService;
@Autowired
public SettingsSourceController(IMcSettingsSourceService mcSettingsSourceService) {
this.mcSettingsSourceService = mcSettingsSourceService;
}
//旧
@Autowired
private IMcSettingsSourceService mcSettingsSourceService;
//如果不想有这个提示除了使用新的构造器注入的话,还可以使用@Resource
@Resource
private IMcSettingsSourceService mcSettingsSourceService;
Spring @Qualifier 注释
可能会有这样一种情况,当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。
也就是说,@Qualifier注解一般用在一个接口对应有多个实现的时候,需要注明要引入的依赖是哪个实现类。
例子:
public interface Language {
public void speak();
}
public class ChineseLanguage implements Language {
@Override
public void speak() {
System.out.println("-------speak Chinese-------");
}
}
public class EnglishLanguage implements Language {
@Override
public void speak() {
System.out.println("-------speak English-------");
}
}
public class SoftEngineer {
public SoftEngineer() {
System.out.println("----------SoftEngineer constructor");
}
@Autowired
private Computer computer;
@Autowired
@Qualifier("chineseLanguage")
private Language language;
public void useComputer(){
computer.coding();
}
public void conmunicate(){
language.speak();
}
}
<bean id="softEngineer" class="com.self.SoftEngineer"/>
<bean id="computer" class="com.self.Computer"/>
<bean id="chineseLanguage" class="com.self.ChineseLanguage"/>
<bean id="englishLanguage" class="com.self.EnglishLanguage"/>
SoftEngineer softEngineer = (SoftEngineer)context.getBean("softEngineer");
softEngineer.useComputer();
softEngineer.conmunicate();
输出:
----------SoftEngineer constructor
-------------Computer constructor
--------------product codes
-------speak Chinese-------
Spring JSR-250 注释
Spring还使用基于 JSR-250 注释,它包括 @PostConstruct, @PreDestroy 和 @Resource 注释。因为你已经有了其他的选择,尽管这些注释并不是真正所需要的,但是关于它们仍然让我给出一个简短的介绍。
@PostConstruct 和 @PreDestroy 注释:
为了定义一个 bean 的安装和卸载,我们使用 init-method 和/或 destroy-method 参数简单的声明一下 。init-method 属性指定了一个方法,该方法在 bean 的实例化阶段会立即被调用。同样地,destroy-method 指定了一个方法,该方法只在一个 bean 从容器中删除之前被调用。
你可以使用 @PostConstruct 注释作为初始化回调函数的一个替代,@PreDestroy 注释作为销毁回调函数的一个替代,其解释如下示例所示。
例子:
public class Message {
private Integer code;
private String msg;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@PostConstruct
public void init(){
System.out.println("----------------init-----------------");
}
@PreDestroy
public void destroy(){
System.out.println("----------------destroy-----------------");
}
}
<bean id="message" class="com.self.Message">
<property name="msg" value="hello lairf"></property>
</bean>
Message message = (Message)context.getBean("message");
System.out.println("=============================message==="+message.getMsg());
context.registerShutdownHook();
@Resource 注释:
你可以在字段中或者 setter 方法中使用 @Resource 注释,它和在 Java EE 5 中的运作是一样的。@Resource 注释使用一个 ‘name’ 属性,该属性以一个 bean 名称的形式被注入。你可以说,它遵循 by-name 自动连接语义。
@Resource依赖注入时查找bean的规则:(以用在field上为例)
1. 既不指定name属性,也不指定type属性,则自动按byName方式进行查找。如果没有找到符合的bean,则回退为一个原始类型byType进行查找,如果找到就注入。
//这样也能注入成功是因为byName连接找不到的话会用byType去查找注入。因此也能注入成功
@Resource
private IOrderService service;
如果没有明确地指定一个 ‘name’,默认名称源于字段名或者 setter 方法。在字段的情况下,它使用的是字段名;在一个 setter 方法情况下,它使用的是 bean 属性名称。
例子:
public class SoftEngineer {
public SoftEngineer() {
System.out.println("----------SoftEngineer constructor");
}
@Resource
//@Resource(name = "computer")
private Computer computer;
public void useComputer(){
computer.coding();
}
}
Spring 基于 Java 的配置
即用java写配置类。
基于 Java 的配置选项,可以使你在不用配置 XML 的情况下编写大多数的 Spring 。
@Configuration 和 @Bean 注解
带有 @Configuration 的注解类表示这个类可以使用 Spring IoC 容器作为 bean 定义的来源。@Bean 注解告诉 Spring,一个带有 @Bean 的注解方法将返回一个对象,该对象应该被注册为在 Spring 应用程序上下文中的 bean。配置类可以声明多个 @Bean。一旦定义了配置类,你就可以使用 AnnotationConfigApplicationContext 来加载并把他们提供给 Spring 容器 。
注入 Bean 的依赖性
当 @Beans 依赖对方时,表达这种依赖性非常简单,只要有一个 bean 方法调用另一个。
@Import 注解:
@import 注解允许从另一个配置类中加载 @Bean 定义。
生命周期回调 @Bean(initMethod = "init", destroyMethod = "cleanup" )
@Bean 注解支持指定任意的初始化和销毁的回调方法,就像在 bean 元素中 Spring 的 XML 的初始化方法和销毁方法的属性:
指定 Bean 的范围:
默认范围是单实例,但是你可以重写带有 @Scope 注解的该方法。
例子:
public class SoftEngineer {
public SoftEngineer(Computer computer,Language language) {
this.computer = computer;
this.language = language;
}
}
@Configuration
@Import(HiConfig.class)
public class HelloConfig {
@Scope("prototype")
@Bean(initMethod = "init",destroyMethod = "destroy")
public Message message(){
return new Message();
}
@Bean
public Computer computer(){
return new Computer();
}
@Bean
public SoftEngineer softEngineer(){
return new SoftEngineer(computer(),englishLanguage());
}
@Bean
public Language englishLanguage(){
return new EnglishLanguage();
}
}
@Configuration
public class HiConfig {
}
ApplicationContext ctx = new AnnotationConfigApplicationContext(HelloConfig.class);
Message message = ctx.getBean(Message.class);
message.setMsg("hello feture");
System.out.println("=============================message==="+message.getMsg());
SoftEngineer engineer = ctx.getBean(SoftEngineer.class);
engineer.useComputer();
engineer.conmunicate();
Spring 中的事件处理
Spring 的核心是 ApplicationContext,它负责管理 beans 的完整生命周期。当加载 beans 时,ApplicationContext 发布某些类型的事件。例如,当上下文启动时,ContextStartedEvent 发布,当上下文停止时,ContextStoppedEvent 发布。
通过 ApplicationEvent 类和 ApplicationListener 接口来提供在 ApplicationContext 中处理事件。如果一个 bean 实现 ApplicationListener,那么每次 ApplicationEvent 被发布到 ApplicationContext 上,那个 bean 会被通知。
注意:实现了ApplicationListener的消费类需要注册bean到容器中,否则不会去订阅消费。
Spring 提供了以下的标准事件:
序号 | Spring 内置事件 & 描述 |
---|---|
1 | ContextRefreshedEventApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。 |
2 | ContextStartedEvent当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。 |
3 | ContextStoppedEvent当使用 ConfigurableApplicationContext 接口中的 stop() 方法停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作。 |
4 | ContextClosedEvent当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。 |
5 | RequestHandledEvent这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。 |
由于 Spring 的事件处理是单线程的,所以如果一个事件被发布,直至并且除非所有的接收者得到的该消息,该进程被阻塞并且流程将不会继续。因此,如果事件处理被使用,在设计应用程序时应注意。
为了监听上下文事件,一个 bean 应该实现只有一个方法 onApplicationEvent() 的 ApplicationListener 接口。因此,我们写一个例子来看看事件是如何传播的,以及如何可以用代码来执行基于某些事件所需的任务。
例子:
public class Message {
private Integer code;
private String msg;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@PostConstruct
public void init(){
System.out.println("----------------init-----------------");
}
@PreDestroy
public void destroy(){
System.out.println("----------------destroy-----------------");
}
}
<bean id="message" class="com.self.Message">
<property name="msg" value="hello lairf"></property>
</bean>
<bean id="startEventListener" class="com.self.eventlistener.StartEventListener"/>
<bean id="refreshEventListener" class="com.self.eventlistener.RefreshEventListener"/>
<bean id="stopEventListener" class="com.self.eventlistener.StopEventListener"/>
<bean id="closeEventListener" class="com.self.eventlistener.CloseEventListener"/>
AbstractApplicationContext context = new ClassPathXmlApplicationContext("spring/appcontext-core.xml");
context.start();
Message message = (Message)context.getBean("message");
System.out.println("=============================message==="+message.getMsg());
context.stop();
context.close();
输出:
----------------init-----------------
---------ContextRefreshedEvent--Consume----------
---------ContextStartedEvent--Consume----------
=============================message===hello lairf
---------ContextStoppedEvent--Consume----------
---------ContextClosedEvent--Consume----------
----------------destroy-----------------
Spring 中的自定义事件
自定义事件需要三个步骤:
1 | 通过扩展 ApplicationEvent,创建一个事件类 CustomEvent。这个类必须定义一个默认的构造函数,它应该从 ApplicationEvent 类中继承的构造函数。 |
---|---|
2 | 一旦定义事件类,你可以从任何类中发布它,假定 EventClassPublisher 实现了 ApplicationEventPublisherAware。你还需要在 XML 配置文件中声明这个类作为一个 bean,之所以容器可以识别 bean 作为事件发布者,是因为它实现了 ApplicationEventPublisherAware 接口。 |
3 | 发布的事件可以在一个类中被处理,假定 EventClassHandler 实现了 ApplicationListener 接口,而且实现了自定义事件的 onApplicationEvent 方法。 |
例子:
public class CustomEvent extends ApplicationEvent {
public CustomEvent(Object source) {
super(source);
}
@Override
public String toString() {
return "CustomEvent";
}
}
public class CustomEventPublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void publish(){
applicationEventPublisher.publishEvent(new CustomEvent(this));
}
}
注意,这里的ApplicationEventPublisher是从下面这个操作获得到的对象。在bean实例化时,会对bean进行判断,如果是ApplicationEventPublisherAware类型的bean说明是个发布者,会对其设置ApplicationEventPublisher。
org.springframework.context.support.ApplicationContextAwareProcessor#invokeAwareInterfaces
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
public class CustomEventListener implements ApplicationListener<CustomEvent> {
@Override
public void onApplicationEvent(CustomEvent event) {
System.out.println("---------CustomEvent--Consume----------"+event.toString());
}
}
注册到容器中:
<bean class="com.self.customevent.CustomEventListener" id="customEventListener"/>
<bean class="com.self.customevent.CustomEventPublisher" id="customEventPublisher"/>
CustomEventPublisher customEventPublisher = (CustomEventPublisher)context.getBean("customEventPublisher");
customEventPublisher.publish();
customEventPublisher.publish();
Spring 框架的 AOP
Spring 框架的一个关键组件是面向切面的编程(AOP)框架。面向切面的编程需要把程序逻辑分解成不同的部分称为所谓的关注点。跨一个应用程序的多个点的功能被称为横切关注点,这些横切关注点在概念上独立于应用程序的业务逻辑。有各种各样的常见的很好的切面的例子,如日志记录、审计、声明式事务、安全性和缓存等。
在 OOP 中,关键单元模块度是类,而在 AOP 中单元模块度是切面。依赖注入帮助你对应用程序对象相互解耦和 AOP 可以帮助你从它们所影响的对象中对横切关注点解耦。AOP 是像编程语言的触发物,如 Perl,.NET,Java 或者其他。
Spring AOP 模块提供拦截器来拦截一个应用程序,例如,当执行一个方法时,你可以在方法执行之前或之后添加额外的功能。
AOP 术语
在我们开始使用 AOP 工作之前,让我们熟悉一下 AOP 概念和术语。这些术语并不特定于 Spring,而是与 AOP 有关的。
项 | 描述 |
---|---|
Aspect | 一个模块具有一组提供横切需求的 APIs。例如,一个日志模块为了记录日志将被 AOP 方面调用。应用程序可以拥有任意数量的切面,这取决于需求。 |
Join point | 在你的应用程序中它代表一个点,你可以在插入 AOP 切面。你也能说,它是在实际的应用程序中,其中一个操作将使用 Spring AOP 框架。 |
Advice | 这是实际行动之前或之后执行的方法。这是在程序执行期间通过 Spring AOP 框架实际被调用的代码。 |
Pointcut | 这是一组一个或多个连接点,通知应该被执行。你可以使用表达式或模式指定切入点正如我们将在 AOP 的例子中看到的。 |
Introduction | 引用允许你添加新方法或属性到现有的类中。 |
Target object | 被一个或者多个切面所通知的对象,这个对象永远是一个被代理对象。也称为被通知对象。 |
Weaving | Weaving 把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时,类加载时和运行时完成。 |
通知的类型
Spring 方面可以使用下面提到的五种通知工作:
通知 | 描述 |
---|---|
前置通知 | 在一个方法执行之前,执行通知。 |
后置通知 | 在一个方法执行之后,不考虑其结果,执行通知。 |
返回后通知 | 在一个方法执行之后,只有在方法成功完成时,才能执行通知。 |
抛出异常后通知 | 在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。 |
环绕通知 | 在建议方法调用之前和之后,执行通知。 |
实现自定义方面
Spring 支持 @AspectJ annotation style 的方法和基于模式的方法来实现自定义切面。这两种方法已经在下面两个子节进行了详细解释。
方法 | 描述 |
---|---|
XML Schema based | 方面是使用常规类以及基于配置的 XML 来实现的。 |
@AspectJ based | @AspectJ 引用一种声明切面的风格作为带有 Java 5 注释的常规 Java 类注释。 |
Spring 中基于 AOP 的 XML架构
步骤:
声明一个 aspect
一个 aspect 是使用 元素声明的,支持的 bean 是使用 ref 属性引用的。
声明一个切入点
一个切入点有助于确定使用不同建议执行的感兴趣的连接点(即方法)。
声明建议
你可以使用 <aop:{ADVICE NAME}> 元素在一个 中声明五个建议中的任何一个。
例子:
public class Logging {
public void beforeAdvice(){
System.out.println("------------before method---------------");
}
public void afterAdvice(){
System.out.println("------------after method---------------");
}
public void afterReturningAdvice(Object retVal){
System.out.println("------------afterReturning method---------------"+retVal);
}
public void afterThrowingAdvice(IllegalArgumentException e){
System.out.println("------------afterThrowing method---------------"+e);
}
}
public class Message {
private Integer code;
private String msg;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@PostConstruct
public void init(){
System.out.println("----------------init-----------------");
}
@PreDestroy
public void destroy(){
System.out.println("----------------destroy-----------------");
}
public void throwingException() {
System.out.println("----------------init-----------------");
throw new IllegalArgumentException();
}
}
Message message = (Message)context.getBean("message");
System.out.println("=============================message==="+message.getMsg());
System.out.println("=============================code==="+message.getCode());
message.throwingException();
<aop:config>
<aop:aspect id="firstAspect" ref="logging">
<aop:pointcut id="methodAll" expression="execution(* com.self.*.*(..))"/>
<aop:before method="beforeAdvice" pointcut-ref="methodAll"/>
<aop:after method="afterAdvice" pointcut-ref="methodAll"/>
<aop:after-returning method="afterReturningAdvice" pointcut-ref="methodAll" returning="retVal"/>
<aop:after-throwing method="afterThrowingAdvice" pointcut-ref="methodAll" throwing="e"/>
</aop:aspect>
</aop:config>
<bean id="message" class="com.self.Message">
<property name="msg" value="hello lairf"></property>
<property name="code" value="1"></property>
</bean>
<bean id="logging" class="com.self.aspect.Logging"/>
输出:
----------------init-----------------
------------before method---------------
------------after method---------------
------------afterReturning method---------------hello lairf
=============================message===hello lairf
------------before method---------------
------------after method---------------
------------afterReturning method---------------1
=============================code===1
------------before method---------------
----------------init-----------------
------------after method---------------
------------afterThrowing method---------------java.lang.IllegalArgumentException
Exception in thread "main" java.lang.IllegalArgumentException
想要在一个特殊的方法之前或者之后执行你的建议,可以通过替换使用真实类和方法名称的切入点定义中的星号(*)来定义你的切入点来缩短你的执行。
在运行时报错:
原因:缺少了aspectJ的依赖,添加jar或依赖即可
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.6</version>
</dependency>
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.context.event.internalEventListenerProcessor': BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.aop.aspectj.AspectJPointcutAdvisor#0': Cannot create inner bean '(inner bean)#5f36ba9b' of type [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice] while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#5f36ba9b': Failed to introspect bean class [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice] for lookup method metadata: could not find class that it depends on; nested exception is java.lang.NoClassDefFoundError: org/aspectj/lang/JoinPoint
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.context.event.internalEventListenerProcessor': BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.aop.aspectj.AspectJPointcutAdvisor#0': Cannot create inner bean '(inner bean)#5f36ba9b' of type [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice] while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#5f36ba9b': Failed to introspect bean class [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice] for lookup method metadata: could not find class that it depends on; nested exception is java.lang.NoClassDefFoundError: org/aspectj/lang/JoinPoint
Spring 中基于 AOP 的 @AspectJ
Spring容器配置:参考
<aop:aspectj-autoproxy/>
这个标签有一个属性:proxy-target-class,它的值有false(默认)和true。再将它设置成true之后,结果运行成功了。总结一下原因如下:
<aop:aspectj-autoproxy proxy-target-class="false"/> 基于接口,使用JDK动态代理
<aop:aspectj-autoproxy proxy-target-class="true"/> 基于类,需要使用cglib库.
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = app.getBean(AccountServiceImpl.class);//此处是通过AccountService接口的实现类来获取bean。结果报错了:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'cn.xx.service.impl.AccountServiceImpl' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:347)
说找不到cn.xx.service.impl.AccountServiceImpl这个类。
声明一个切入点
一个切入点有助于确定使用不同建议执行的感兴趣的连接点(即方法)。在处理基于配置的 XML 架构时,切入点的声明有两个部分:
- 一个切入点表达式决定了我们感兴趣的哪个方法会真正被执行。
- 一个切入点标签包含一个名称和任意数量的参数。方法的真正内容是不相干的,并且实际上它应该是空的。
声明建议
你可以使用 @{ADVICE-NAME} 注释声明五个建议中的任意一个.
实例:
@Aspect
public class Monitoring {
@Pointcut("execution(* com.self.Message.*(..))")
public void monitorAll() {
}
@Before("monitorAll()")
public void beforeAdvice() {
System.out.println("===========beforeAdvice=============");
}
@After("monitorAll()")
public void afterAdvice() {
System.out.println("===========afterAdvice=============");
}
@AfterReturning(pointcut = "monitorAll()", returning = "retVal")
public void afterReturning(Object retVal) {
System.out.println("===========afterReturning=============" + retVal);
}
@AfterThrowing(pointcut = "monitorAll()", throwing = "e")
public void afterThrowing(IllegalArgumentException e) {
System.out.println("===========afterThrowing=============" + e);
}
@Around("monitorAll()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Object[] args = point.getArgs();
System.out.println("===========around start============="+ JSON.toJSONString(args));
Object result = point.proceed();
System.out.println("===========around end ============="+result);
return result;
}
}
public class Message {
private Integer code;
private String msg;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Map checkArgs(Map map) {
map.containsKey("check");
map.put("check","doubleCheck");
return map;
}
@PostConstruct
public void init(){
System.out.println("----------------init-----------------");
}
@PreDestroy
public void destroy(){
System.out.println("----------------destroy-----------------");
}
public void throwingException() {
System.out.println("----------------throwingException-----------------");
throw new IllegalArgumentException();
}
}
Message message = (Message)context.getBean("message");
//System.out.println("=============================message==="+message.getMsg());
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("check","check");
hashMap.put("submit","ok");
System.out.println("=============================checkArgs==="+message.checkArgs(hashMap));
//message.throwingException();
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="message" class="com.self.Message">
<property name="msg" value="hello lairf"></property>
<property name="code" value="1"></property>
</bean>
<bean class="com.self.aspect.Monitoring" id="monitoring"/>
输出:
----------------init-----------------
===========around start=============[{"submit":"ok","check":"check"}]
===========beforeAdvice=============
===========around end ============={submit=ok, check=doubleCheck}
===========afterAdvice=============
===========afterReturning============={submit=ok, check=doubleCheck}
=============================checkArgs==={submit=ok, check=doubleCheck}
确实是order越小越是最先执行,但更重要的是最先执行的最后结束。
spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。
如果我们要在同一个方法事务提交后执行自己的AOP,那么把事务的AOP order设置为2,自己的AOP order设置为1,然后在doAfterReturn里边处理自己的业务逻辑。

Spring JDBC 框架
select version(); 查看Mysql版本
5.6.24-72.2-log 生产版本
使用普通的 JDBC 数据库时缺点:写不必要的代码来处理异常,打开和关闭数据库连接等。
Spring JDBC 框架优点:负责所有的低层细节,从开始打开连接,准备和执行 SQL 语句,处理异常,处理事务,到最后关闭连接。
所以当从数据库中获取数据时,你所做的是定义连接参数,指定要执行的 SQL 语句,每次迭代完成所需的工作。
Spring JDBC 提供几种方法和数据库中相应的不同的类与接口。我将给出使用 JdbcTemplate 类框架的经典和最受欢迎的方法。这是管理所有数据库通信和异常处理的中央框架类。
JdbcTemplate 类
JdbcTemplate 类的实例是线程安全配置的。所以你可以配置 JdbcTemplate 的单个实例,然后将这个共享的引用安全地注入到多个 DAOs 中。
使用 JdbcTemplate 类时常见的做法是在你的 Spring 配置文件中配置数据源,然后共享数据源 bean 依赖注入到 DAO 类中,并在数据源的设值函数中创建了 JdbcTemplate。
Spring JDBC 示例
前提需要,数据库支持:
##创建数据库
create DATABASE hello_spring;
##创建表
CREATE TABLE IF NOT EXISTS t_developer (
id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
name VARCHAR(30) NOT NULL COMMENT '姓名',
work_level INT(6) NOT NULL COMMENT '等级',
position VARCHAR(30) DEFAULT NULL COMMENT '职位',
salary DECIMAL(13,2) DEFAULT '0' COMMENT '薪水',
status SMALLINT(2) DEFAULT NULL COMMENT '状态',
PRIMARY KEY (id)
)ENGINE = INNODB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT '开发员工表';
例子:
public interface DeveloperDao {
void setDataSource(DataSource ds);
void insert(Developer developer);
void deleteById(Integer id);
void update(Developer developer);
List<Developer> getDevelopers();
Developer getDeveloperById(Integer id);
}
public class DeveloperDaotJDBCTemplate implements DeveloperDao {
private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
@Override
public void setDataSource(DataSource ds) {
this.dataSource = ds;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public void insert(Developer developer) {
String sql = "insert into t_developer(name,work_level,position,salary,status) values (?,?,?,?,?)";
jdbcTemplate.update(sql,developer.getName(),developer.getWorkLevel(),developer.getPosition(),developer.getSalary(),
developer.getStatus());
System.out.println("-------------insert------------");
}
@Override
public void deleteById(Integer id) {
String sql = "delete from t_developer where id = ?";
jdbcTemplate.update(sql,id);
System.out.println("-------------deleteById------------"+id);
}
@Override
public void update(Developer developer) {
String sql = "update t_developer set name = ? ,work_level = ?, position = ? ,salary = ?, status = ? where id = ?";
jdbcTemplate.update(sql,developer.getName(),developer.getWorkLevel(),developer.getPosition(),developer.getSalary(),
developer.getStatus(),developer.getId());
System.out.println("-------------update------------");
}
@Override
public List<Developer> getDevelopers() {
String sql = "select id,name,work_level,position,salary,status from t_developer where 1=1";
List<Developer> list = jdbcTemplate.query(sql, new DeveloperMapper());
System.out.println("-------------getDevelopers------------");
return list;
}
@Override
public Developer getDeveloperById(Integer id) {
String sql = "select id,name,work_level,position,salary,status from t_developer where id= ?";
Developer developer = jdbcTemplate.queryForObject(sql, new Object[]{id}, new DeveloperMapper());
System.out.println("-------------getDeveloperById------------"+id);
return developer;
}
}
public class DeveloperMapper implements RowMapper<Developer> {
@Override
public Developer mapRow(ResultSet rs, int rowNum) throws SQLException {
Developer developer = new Developer();
developer.setId(rs.getInt("id"));
developer.setName(rs.getString("name"));
developer.setPosition(rs.getString("position"));
developer.setSalary(rs.getBigDecimal("salary"));
developer.setWorkLevel(rs.getInt("work_level"));
developer.setStatus(rs.getInt("status"));
return developer;
}
}
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/hello_spring"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<bean class="com.self.jdbc.impl.DeveloperDaotJDBCTemplate" id="developerDaotJDBCTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--接口和抽象类在bean容器中配置需要声明abstract="true",而该接口是不能被获取使用的-->
<!--<bean class="com.self.jdbc.DeveloperDao" id="developerDao" abstract="true"></bean>-->
//DeveloperDaotJDBCTemplate jdbcTemplate = (DeveloperDaotJDBCTemplate)context.getBean("developerDaotJDBCTemplate");
//无法获得到想要的实现类,还需要研究下像注解@Autowire是怎么把实现类注入到接口中的,或者订单是怎么通过serviceFactory来通过接口获取到实现类的
//DeveloperDao developerDao = (DeveloperDao)context.getBean("DeveloperDao");
//多态可以正常操作
DeveloperDao developerDao = (DeveloperDao)context.getBean("developerDaotJDBCTemplate");
Developer developer = new Developer();
developer.setStatus(1);
developer.setWorkLevel(3);
developer.setSalary(new BigDecimal("87010.78"));
developer.setPosition("贷款");
developer.setName("Master Ting");
developerDao.insert(developer);
Developer dev = developerDao.getDeveloperById(1000);
System.out.println(JSON.toJSONString(dev));
List<Developer> developers = developerDao.getDevelopers();
System.out.println(JSON.toJSONString(developers));
dev.setName("lairffff");
developerDao.update(dev);
developerDao.deleteById(1002);
List<Developer> after = developerDao.getDevelopers();
System.out.println(JSON.toJSONString(after));
public class Developer {
/**
* 主键
*/
private Integer id;
/**
* '姓名'
*/
private String name;
/**
* '等级'
*/
private Integer workLevel;
/**
* '职位'
*/
private String position;
/**
* '薪水'
*/
private BigDecimal salary;
/**
* '状态'
*/
private Integer status;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getWorkLevel() {
return workLevel;
}
public void setWorkLevel(Integer workLevel) {
this.workLevel = workLevel;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
public BigDecimal getSalary() {
return salary;
}
public void setSalary(BigDecimal salary) {
this.salary = salary;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
输出:
-------------insert------------
-------------getDeveloperById------------1000
{"id":1000,"name":"Boss Liu","position":"中台","salary":32000.78,"status":1,"workLevel":6}
-------------getDevelopers------------
[{"id":1000,"name":"Boss Liu","position":"中台","salary":32000.78,"status":1,"workLevel":6},{"id":1001,"name":"Boss Zhou","position":"中台","salary":33000.78,"status":1,"workLevel":6},{"id":1002,"name":"Boss Bin","position":"新零售","salary":53010.78,"status":0,"workLevel":7},{"id":1003,"name":"Student Chen","position":"新零售","salary":13010.78,"status":0,"workLevel":4},{"id":1004,"name":"Student Xu","position":"新零售","salary":17010.78,"status":1,"workLevel":5},{"id":1005,"name":"Master Ting","position":"贷款","salary":87010.78,"status":1,"workLevel":3},{"id":1006,"name":"Master Ting","position":"贷款","salary":87010.78,"status":1,"workLevel":3}]
-------------update------------
-------------deleteById------------1002
-------------getDevelopers------------
[{"id":1000,"name":"lairffff","position":"中台","salary":32000.78,"status":1,"workLevel":6},{"id":1001,"name":"Boss Zhou","position":"中台","salary":33000.78,"status":1,"workLevel":6},{"id":1003,"name":"Student Chen","position":"新零售","salary":13010.78,"status":0,"workLevel":4},{"id":1004,"name":"Student Xu","position":"新零售","salary":17010.78,"status":1,"workLevel":5},{"id":1005,"name":"Master Ting","position":"贷款","salary":87010.78,"status":1,"workLevel":3},{"id":1006,"name":"Master Ting","position":"贷款","salary":87010.78,"status":1,"workLevel":3}]
错误:在从容器中获取bean失败:因为这是个接口,不是bean。
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'DeveloperDao' available
还有其他方法来访问你使用 NamedParameterJdbcTemplate 和 SimpleJdbcTemplate 类的数据库,所以如果你有兴趣学习这些类的话,那么你可以查看 Spring 框架的参考手册。
Spring 中 SQL 的存储过程
数据库需要建立存储过程:
DELIMITER $$
DROP PROCEDURE IF EXISTS `hello_spring`.`getRecord` $$
CREATE PROCEDURE `hello_spring`.`getRecord` (
IN in_id INTEGER,
OUT out_name VARCHAR(30),
OUT out_salary DECIMAL(13,2))
BEGIN
SELECT name, salary
INTO out_name, out_salary
FROM t_developer where id = in_id;
END $$
DELIMITER ;
实例:主要变化
public class DeveloperDaotJDBCTemplate implements DeveloperDao {
private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
private SimpleJdbcCall simpleJdbcCall;
@Override
public void setDataSource(DataSource ds) {
this.dataSource = ds;
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.simpleJdbcCall = new SimpleJdbcCall(dataSource).withProcedureName("getRecord");
}
@Override
public Developer getDeveloperByProcedure(Integer id) {
SqlParameterSource in = new MapSqlParameterSource().addValue("in_id",id);
Map<String, Object> map = simpleJdbcCall.execute(in);
Developer developer = new Developer();
developer.setId((Integer) map.get("out_id"));
developer.setName((String) map.get("out_name"));
developer.setSalary((BigDecimal) map.get("out_salary"));
return developer;
}
}