Spring
Spring基础
框架组成
Spring的起源
百度百科中关于Spring的起源介绍如下
要谈Spring的历史,就要先谈J2EE。J2EE应用程序的广泛实现是在1999年和2000年开始的,它的出现带来了诸如事务管理之类的核心中间层概念的标准化,但是在实践中并没有获得绝对的成功,因为开发效率,开发难度和实际的性能都令人失望。
曾经使用过EJB开发JAVA EE应用的人,一定知道,在EJB开始的学习和应用非常的艰苦,很多东西都不能一下子就很容易的理解。EJB要严格地实现各种不同类型的接口,类似的或者重复的代码大量存在。而配置也是复杂和单调,同样使用JNDI进行对象查找的代码也是单调而枯燥。虽然有一些开发工作随着xdoclet的出现,而有所缓解,但是学习EJB的高昂代价,和极低的开发效率,极高的资源消耗,都造成了EJB的使用困难。而Spring出现的初衷就是为了解决类似的这些问题。
Spring的一个最大的目的就是使JAVA EE开发更加容易。同时,Spring之所以与Struts、Hibernate等单层框架不同,是因为Spring致力于提供一个以统一的、高效的方式构造整个应用,并且可以将单层框架以最佳的组合揉和在一起建立一个连贯的体系。可以说Spring是一个提供了更完善开发环境的一个框架,可以为POJO(Plain Ordinary Java Object)对象提供企业级的服务。
Spring的形成,最初来自Rod Jahnson所著的一本很有影响力的书籍《Expert One-on-One J2EE Design and Development (opens new window)》,就是在这本书中第一次出现了Spring的一些核心思想,该书出版于2002年。
Spring的特性和优势
Spring Framework有哪些特性,用了这个框架对开发而言有什么好处呢?
-
特性:
- 非入侵式:基于Spring开发的应用中的对象可以不依赖于Spring的API
- 控制反转:IOC——Inversion of Control,指的是将对象的创建权交给 Spring 去创建。使用 Spring 之前,对象的创建都是由我们自己在代码中new创建。而使用 Spring 之后。对象的创建都是给了 Spring 框架。
- 依赖注入:DI——Dependency Injection,是指依赖的对象不需要手动调用 setXX 方法去设置,而是通过配置赋值。
- 面向切面编程:Aspect Oriented Programming——AOP
- 容器:Spring 是一个容器,因为它包含并且管理应用对象的生命周期
- 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
- 一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上 Spring 自身也提供了表现层的 SpringMVC 和持久层的 Spring JDBC)
-
优势:
-
大大降低开发企业级应用的难度,可以自由选择servlet容器,如Tomcat或者其他商业产品作为应用程序的服务器。
-
Spring 在一个单元模式中是有组织的。即使包和类的数量非常大,你只要担心你需要的,而其它的就可以忽略了。
-
整合了一些现有的技术,像ORM 框架、日志框架、JEE、Quartz 和 JDK 计时器,其他视图技术等,避免重复编码。
-
易于测试
-
拥有设计良好的web MVC架构
-
对JavaEE中非常难用的API(JDBC、JavaMail、远程调用等)进行了封装,使这些API应用难度大大降低
-
轻量级,有利于在内存和CPU资源有限的计算机上部署应用程序。
-
提供了一致的事务管理接口
-
Spring组件组成
Spring Framework有哪些组件呢?
-
核心容器(Core Container)
- Beans:提供了框架的基础部分,包括控制反转和依赖注入。
- Core:封装了 Spring 框架的底层部分,包括资源访问、类型转换及一些常用工具类。
- Context:建立在 Core 和 Beans 模块的基础之上,集成 Beans 模块功能并添加资源绑定、数据验证、国际化、Java EE 支持、容器生命周期、事件传播等。ApplicationContext 接口是上下文模块的焦点。
- SpEL:提供了强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring 容器获取 Bean,它也支持列表投影、选择和一般的列表聚合等。
-
数据访问/集成(Data Access/Integration)
- JDBC:提供了一个 JDBC 的样例模板,使用这些模板能消除传统冗长的 JDBC 编码还有必须的事务控制,而且能享受到 Spring 管理事务的好处。
- ORM:提供与流行的“对象-关系”映射框架无缝集成的 API,包括 JPA、JDO、Hibernate 和 MyBatis 等。而且还可以使用 Spring 事务管理,无需额外控制事务。
- OXM:提供了一个支持 Object /XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。将 Java 对象映射成 XML 数据,或者将XML 数据映射成 Java 对象。
- JMS:指 Java 消息服务,提供一套 “消息生产者、消息消费者”模板用于更加简单的使用 JMS,JMS 用于用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
- Transactions:支持编程和声明式事务管理。
-
Web
- Web:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IOC 容器初始化以及 Web 应用上下文。
- Servelt:提供了一个 Spring MVC Web 框架实现。Spring MVC 框架提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等及一套非常易用的 JSP 标签,完全无缝与 Spring 其他技术协作。
- WebSocket:提供了简单的接口,用户只要实现响应的接口就可以快速的搭建 WebSocket Server,从而实现双向通讯。
- Webflux:Spring WebFlux 是 Spring Framework 5.x中引入的新的响应式web框架。与Spring MVC不同,它不需要Servlet API,是完全异步且非阻塞的,并且通过Reactor项目实现了Reactive Streams规范。Spring WebFlux 用于创建基于事件循环执行模型的完全异步且非阻塞的应用程序。
- Portlet(Sping 5.x中已被移除):提供了在 Portlet 环境中使用 MVC 实现,类似 Web-Servlet 模块的功能。
-
AOP、Aspects、Instrumentation和Messaging
- AOP:提供了面向切面编程实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态的把这些功能添加到需要的代码中,这样各司其职,降低业务逻辑和通用功能的耦合。
- Aspects:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
- Instrumentation:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
- Message:Spring 4.0 以后新增了消息(Spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。
- jcl:Spring 5.x中新增了日志框架集成的模块。
-
Test
- Spring 支持 Junit 和 TestNG 测试框架,而且还额外提供了一些基于 Spring 的测试功能,比如在测试 Web 框架时,模拟 Http 请求的功能。
Why Spring?
那么为什么用Spring呢?来看看官网对这个问题的回答
Spring简单案例
- 用户dao层接口类
public interface UserDao {
/**
* 获取用户
* @return
*/
public List<User> findUserList();
}
- 用户dao层实现类
public class UserDaoImpl implements UserDao {
@Override
public List<User> findUserList() {
System.out.println("获取用户信息成功!");
return null;
}
}
- 用户service层接口
public interface UserService {
public List<User> getUserList();
}
- 用户service层实现类
public class UserServiceImpl implements UserService {
//依赖于UserDao
private UserDao userDao = new UserDaoImpl();
//提供userDao的set方法
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
//实现接口方法
@Override
public List<User> getUserList() {
return userDao.findUserList();
}
}
Withot Spring
所有的对象都需要通过new关键字来创建,代码耦合度高,不利于系统维护和拓展,测试难度大
@Test
public void test1(){
UserDao userDao = new UserDaoImpl();
UserServiceImpl userServiceImpl = new UserServiceImpl();
userServiceImpl.setUserDao(userDao);
userServiceImpl.getUserList();
}
With Spring
Spring配置文件:将userDaoImpl,userServiceImpl交给Spring容器进行管理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="userDaoImpl" class="wen.base.impl.UserDaoImpl"/>
<!--必须提供set方法才能使用set注入-->
<bean id="userServiceImpl" class="wen.base.impl.UserServiceImpl">
<property name="userDao" ref="userDaoImpl"/>
</bean>
</beans>
测试:不再需要使用new关键字来创建对象,bean的管理完全交由Spring,bean的创建与使用分离,实现了解耦,当业务发生改变时只需修改配置问题即可,便于维护,易于测试。
@Test
public void test2(){
UserServiceImpl userServiceImpl = context.getBean("userServiceImpl", UserServiceImpl.class);
userServiceImpl.getUserList();
}
小结
- Spring框架管理这些Bean的创建工作,即由用户管理Bean转变为框架管理Bean,这个就叫控制反转 - Inversion of Control (IoC)
- Spring 框架托管创建的Bean放在哪里呢? 这便是IoC Container
- Spring 框架为了更好让用户配置Bean,必然会引入不同方式来配置Bean? 这便是xml配置,Java配置,注解配置等支持
- Spring 框架既然接管了Bean的生成,必然需要管理整个Bean的生命周期等
- 应用程序代码从Ioc Container中获取依赖的Bean,注入到应用程序中,这个过程叫 依赖注入(Dependency Injection,DI) ; 所以说控制反转是通过依赖注入实现的,其实它们是同一个概念的不同角度描述。通俗来说就是IoC是设计思想,DI是实现方式
- 在依赖注入时,有哪些方式呢?这就是构造器方式,@Autowired, @Resource, @Qualifier… 同时Bean之间存在依赖(可能存在先后顺序问题,以及循环依赖问题等)
控制反转(IOC)
Spring框架管理这些Bean的创建工作,即由用户管理Bean转变为框架管理Bean,这个就叫控制反转 - Inversion of Control (IoC)
bean
Spring里面的bean就类似是定义的一个组件,而这个组件的作用就是实现某个功能的,这里所定义的bean就相当于给了你一个更为简便的方法来调用这个组件去实现你要完成的功能。
IOC
IOC(Inversion Of Controller)即控制反转,是一种设计思想,IOC意味着将对象的控制交给了容器,而不是传统的在对象内部直接控制。
- 谁控制谁,控制什么?
传统的方式我们通过new关键字来控制对象的创建,是程序主动去创建对象之间的依赖关系。有了Spring之后,有一个专门的容器来控制对象的创建,所以是IOC控制对象,控制的则是外部资源的获取(不只是对象,还包括资源文件等)。
- 为何是反转,哪些方面反转了?
- 正转:对象内部主动控制去直接获取依赖对象
- 反转:容器帮忙创建和注入对象
由于对象的管理交给了容器,当一个对象需要获取一个对象的依赖时需要通过容器来查找和注入而不是在自身内部创建,因此是反转了。很明显,是依赖对象的获取被反转了。
传统方式
主动创建对象,通过组合的形式建立依赖
IOC/DI
bean的管理交给了Spring容器,客户端不再需要主动去创建对象了
优点:
传统的程序在类内部主动创建对象,从而导致了类与类之间高耦合,难于测试;有了IOC容器之后,把创建和查找依赖对象的控制权交给了容器,由容器注入组合对象,所以对象之间是松散耦合,便于测试,利于功能复用,使整个体系结构变得十分灵活。
好莱坞法则:
”别找我们,我们找你“
DI
DI(dependency Injection)即依赖注入,IOC的实现途径是DI,即IOC是设计思想,DI是实现方式
依赖注入:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
IOC配置的三种方式
xml配置
详见依赖注入的三种方式。
- 优点:可以使用于任何场景,结构清晰,通俗易懂
- 缺点:配置繁琐,不易维护,枯燥无味,扩展性差
Java配置
使用@Configuration注解声明一个类为Spring的配置类
-
@Bean:声明一个bean组件
-
@ComponentScan:组件扫描
-
@Import:引入其他配置类
@Configuration
@ComponentScan("wen.base.impl")
@Import(MyConfiguration2.class)
public class MyConfiguration {
@Bean
public Car getCar(){
return new Car("001", "凯迪拉克");
}
@Bean
public Student student(){
return new Student();
}
}
@Configuration
@ComponentScan("wen.base")
public class MyConfiguration2 {
@Bean
public Assistant assistant(){
return new Assistant();
}
}
测试:获取bean实例,name与方法名保持一致
/**
* 获取bean实例,name与方法名保持一致
*/
@Test
public void getCar(){
Car car = context.getBean("getCar", Car.class);
System.out.println(car); // Car(id=001, name=凯迪拉克)
}
- 优点:适用于任何场景,配置方便,因为是纯Java代码,扩展性高,十分灵活
- 缺点:由于是采用Java类的方式,声明不明显,如果大量配置,可读性比较差
注解配置
jdk1.5之后提供的新特性
使用@Autowired, @Resource, @Qualifier等注解可以实现依赖注入,效果等同于xml配置
1、开启注解支持及包扫描
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解支持-->
<context:annotation-config/>
<!--包扫描-->
<context:component-scan base-package="wen"/>
</beans>
2、使用注解注入
注意到这里的@Qualifier注解,值为"run",由于ActionService有两个实现类,可以通过@Qualifier注解的值来标注使用哪个实现类
@Data
public class Student {
private String id;
private String name;
private int age;
private Course course;
private List hobby;
private Map cards;
private String[] roommates;
private Properties info;
@Autowired
@Qualifier("run")
private ActionService actionService; // 注解方式进行注入,@Autowired配置在set方法上同样可以,但一般别那么干
public void study(){
actionService.doAction();
}
}
3、依赖对象实现类
使用@Service注解来标明这是一个被Spring托管的bean
-
@Controller:控制层
-
@Service:业务层
-
@Repository:持久化层
@Service("run") //Service的value可写可不写,不写值默认是类名的小驼峰形式
public class Run implements ActionService {
@Override
public void doAction() {
System.out.println("开始锻炼!");
}
}
@Service
public class Study implements ActionService {
@Override
public void doAction() {
System.out.println("开始一天的学习!");
}
}
4、测试
@Test
public void test7(){
Student student = context.getBean("student", Student.class);
student.study(); // 开始锻炼!
}
- 优点:开发便捷,通俗易懂,方便维护。
- 缺点:具有局限性,对于一些第三方资源,无法添加注解。只能采用XML或JavaConfig的方式配置。
依赖注入的三种方式
set注入
属性必须有set方法才能进行set注入,且被注入对象同样需要被Spring托管
上面的案例中已经实现了set注入,这里补充一些其他类型的属性的注入方式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="course" class="wen.base.pojo.Course" name="myCourse"></bean>
<!--Set注入:属性必须有set方法才能使用Set注入-->
<bean id="student" class="wen.base.pojo.Student">
<!--值注入-->
<property name="id" value="1"/>
<property name="name" value="补浩"/>
<property name="age" value="32"/>
<!--引用注入:这里使用别名来作映射-->
<property name="course" ref="myCourse"/>
<!--list-->
<property name="hobby">
<list>
<value>英雄联盟</value>
<value>梦幻西游</value>
</list>
</property>
<!--map-->
<property name="cards">
<map>
<entry key="身份证" value="430102199003142365"/>
<entry key="学生证号" value="20152314"/>
</map>
</property>
<!--数组-->
<property name="roommates">
<array>
<value>张三</value>
<value>李四</value>
<value>王五</value>
</array>
</property>
<!--Property-->
<property name="info">
<props>
<prop key="籍贯">怀化</prop>
<prop key="工作单位">湖南科创信息技术股份有限公司</prop>
</props>
</property>
</bean>
</beans>
构造器注入
必须有有参构造才可以使用构造器注入的方式进行注
这里我们使用lombok的@AllArgsConstructor注解自动生成有参构造方法
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Course {
private String id;
private String name;
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 构造器注入:必须有有有参构造才能使用构造器注入-->
<bean id="course" class="wen.base.pojo.Course" name="myCourse">
<constructor-arg name="id"><value>001</value></constructor-arg>
<constructor-arg name="name"><value>高等数学</value></constructor-arg>
</bean>
</beans>
命名空间注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--命名空间注入-->
<!--单例模式:默认模式-->
<bean id="teacher" class="wen.base.pojo.Teacher" p:name="朴老师" p:age="32" scope="singleton"/>
<!--原型链模式-->
<bean id="leader" class="wen.base.pojo.Leader" c:name="补浩" c:position="教导主任" scope="prototype"/>
</beans>
面向切面编程(AOP)
Aspect Oriented Programming,即为面向切面编程
什么是AOP?
AOP最早是AOP联盟的组织提出的,指定的一套规范,spring将AOP的思想引入框架之中,通过预编译方式和运行期间动态代理实现程序的统一维护的一种技术。
AOP的理念
将分散在各个业务逻辑代码中相同的代码通过横向切割的方式抽取到一个独立的模块中!
AOP术语
-
连接点(JointPoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,Spring只支持方法执行连接点,在AOP中表示为在哪里干。
-
切入点(PointCut):选择一组相关连接点的模式,即可以认为连接点的集合,Spring支持perl5正则表达式和AspectJ切入点模式,Spring默认使用AspectJ语法,在AOP中表示为在哪里干的集合。
-
通知(Advice):
在连接点上执行的行为,通知提供了在AOP中需要在切入点所选择的连接点处进行扩展现有行为的手段;包括前置通知(before advice)、后置通知(after advice)、环绕通知(around advice),在Spring中通过代理模式实现AOP,并通过拦截器模式以环绕连接点的拦截器链织入通知;在AOP中表示为干什么。
-
切面(Aspect):横切关注点的模块化,比如上边提到的日志组件。可以认为是通知、引入和切入点的组合;在Spring中可以使用Schema和@AspectJ方式进行组织实现;在AOP中表示为在哪干和干什么集合。
-
引入(inter-type declaration):也称为内部类型声明,为已有的类添加额外新的字段或方法,Spring允许引入新的接口(必须对应一个实现)到所有被代理对象(目标对象), 在AOP中表示为干什么(引入什么)。
-
目标对象(Target Object):需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被通知的对象,从而也可称为被通知对象;由于Spring AOP 通过代理模式实现,从而这个对象永远是被代理对象,在AOP中表示为对谁干。
-
织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。在AOP中表示为怎么实现的
- **AOP代理(AOP Proxy):**AOP框架使用代理模式创建的对象,从而实现在连接点处插入通知(即应用切面),就是通过代理来对目标对象应用切面。在Spring中,AOP代理可以用JDK动态代理或CGLIB代理实现,而通过拦截器模型应用切面。在AOP中表示为怎么实现的一种典型方式。
通知类型
- 前置通知(Before Advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
- 后置通知(After Returning Advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
- 异常通知(After Throwing Advice):在方法抛出异常退出时执行的通知。
- 最终通知(After (finally) Advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
- 环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。
AOP的配置方式
xml配置方式
- 定义目标类
public interface UserService {
public void add();
public void del();
public void update();
public void search();
}
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("新增一个用户");
}
@Override
public void del() {
System.out.println("删除一个用户");
}
@Override
public void update() {
System.out.println("编辑一个用户");
}
@Override
public void search() {
System.out.println("查询用户");
}
}
- 定义切面类
实现接口方式
public class BeforeLog implements MethodBeforeAdvice {
/**
* @param method the method being invoked
* @param args the arguments to the method
* @param target the target of the method invocation. May be {@code null}.
* @throws Throwable
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass()+"的"+method.getName()+"开始执行!");
}
}
public class AfterLog implements AfterReturningAdvice {
/**
*
* @param returnValue the value returned by the method, if any
* @param method the method being invoked
* @param args the arguments to the method
* @param target the target of the method invocation. May be {@code null}.
* @throws Throwable
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass()+"的"+method.getName()+"方法执行完毕!");
}
}
自定义切面类方式
@Aspect
public class DiyLog {
public void before(){
System.out.println("操作前");
}
public void after(){
System.out.println("操作后");
}
}
- xml配置AOP
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="userService" class="wen.proxy.impl.UserServiceImpl"/>
<bean id="beforeLog" class="wen.proxy.log.BeforeLog"/>
<bean id="afterLog" class="wen.proxy.log.AfterLog"/>
<bean id="diyLog" class="wen.proxy.log.DiyLog"/>
<!--开启注解支持-->
<aop:aspectj-autoproxy/>
<!--使用API的方式实现AOP-->
<aop:config>
<!--切入点:expression表达式,execution(返回值类型 类名.方法名.(参数名))-->
<aop:pointcut id="userPointCut" expression="execution(* wen.proxy.impl.UserServiceImpl.*(..))"/>
<!--执行环绕增强-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="userPointCut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="userPointCut"/>
<aop:aspect ref="diyLog">
<aop:pointcut id="userPointCut" expression="execution(* wen.proxy.impl.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="userPointCut"/>
<aop:after method="after" pointcut-ref="userPointCut"/>
</aop:aspect>
</aop:config>
</beans>
- 测试
private ApplicationContext context;
@Before
public void init(){
context = new ClassPathXmlApplicationContext("applicationContent.xml");
}
@Test
public void test5(){
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
AspectJ注解方式
基于XML的声明式AspectJ存在一些不足,需要在Spring配置文件配置大量的代码信息,为了解决这个问题,Spring 使用了@AspectJ框架为AOP的实现提供了一套注解。
注解名称 | 解释 |
---|---|
@Aspect | 用来定义一个切面。 |
@pointcut | 用于定义切入点表达式。在使用时还需要定义一个包含名字和任意参数的方法签名来表示切入点名称,这个方法签名就是一个返回值为void,且方法体为空的普通方法。 |
@Before | 用于定义前置通知,相当于BeforeAdvice。在使用时,通常需要指定一个value属性值,该属性值用于指定一个切入点表达式(可以是已有的切入点,也可以直接定义切入点表达式)。 |
@AfterReturning | 用于定义后置通知,相当于AfterReturningAdvice。在使用时可以指定pointcut / value和returning属性,其中pointcut / value这两个属性的作用一样,都用于指定切入点表达式。 |
@Around | 用于定义环绕通知,相当于MethodInterceptor。在使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。 |
@After-Throwing | 用于定义异常通知来处理程序中未处理的异常,相当于ThrowAdvice。在使用时可指定pointcut / value和throwing属性。其中pointcut/value用于指定切入点表达式,而throwing属性值用于指定-一个形参名来表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法抛出的异常。 |
@After | 用于定义最终final 通知,不管是否异常,该通知都会执行。使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。 |
@DeclareParents | 用于定义引介通知,相当于IntroductionInterceptor (不要求掌握)。 |
- 定义接口和实现类
public interface UserService {
public void add();
public void del();
public void update();
public void search();
}
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("新增一个用户");
}
@Override
public void del() {
System.out.println("删除一个用户");
}
@Override
public void update() {
System.out.println("编辑一个用户");
}
@Override
public void search() {
System.out.println("查询用户");
}
}
- 自定义切面类
@Aspect
public class AnnotationPointCut {
@Before("execution(* wen.proxy.impl.UserServiceImpl.*(..))")
public void before(){
System.out.println("方法执行前");
}
@After("execution(* wen.proxy.impl.UserServiceImpl.*(..))")
public void after(){
System.out.println("方法执行后");
}
@Around("execution(* wen.proxy.impl.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前:判断当前用户是否登录");
Object proceed = joinPoint.proceed();
System.out.println("环绕后:提交事务");
}
}
- 测试
private ApplicationContext context;
@Before
public void init(){
context = new ClassPathXmlApplicationContext("applicationContent.xml");
}
@Test
public void test5(){
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
切入点申明规则
-
ret-type-pattern 返回类型模式, name-pattern名字模式和param-pattern参数模式是必选的, 其它部分都是可选的。返回类型模式决定了方法的返回类型必须依次匹配一个连接点。 你会使用的最频繁的返回类型模式是
*
,它代表了匹配任意的返回类型。 -
declaring-type-pattern, 一个全限定的类型名将只会匹配返回给定类型的方法。
-
name-pattern 名字模式匹配的是方法名。 你可以使用
*
通配符作为所有或者部分命名模式。 -
param-pattern 参数模式稍微有点复杂:()匹配了一个不接受任何参数的方法, 而(…)匹配了一个接受任意数量参数的方法(零或者更多)。 模式()匹配了一个接受一个任何类型的参数的方法。 模式(,String)匹配了一个接受两个参数的方法,第一个可以是任意类型, 第二个则必须是String类型。
通用切入点表达式例子:
// 任意公共方法的执行:
execution(public * *(..))
// 任何一个名字以“set”开始的方法的执行:
execution(* set*(..))
// AccountService接口定义的任意方法的执行:
execution(* com.xyz.service.AccountService.*(..))
// 在service包中定义的任意方法的执行:
execution(* com.xyz.service.*.*(..))
// 在service包或其子包中定义的任意方法的执行:
execution(* com.xyz.service..*.*(..))
// 在service包中的任意连接点(在Spring AOP中只是方法执行):
within(com.xyz.service.*)
// 在service包或其子包中的任意连接点(在Spring AOP中只是方法执行):
within(com.xyz.service..*)
// 实现了AccountService接口的代理对象的任意连接点 (在Spring AOP中只是方法执行):
this(com.xyz.service.AccountService)// 'this'在绑定表单中更加常用
// 实现AccountService接口的目标对象的任意连接点 (在Spring AOP中只是方法执行):
target(com.xyz.service.AccountService) // 'target'在绑定表单中更加常用
// 任何一个只接受一个参数,并且运行时所传入的参数是Serializable 接口的连接点(在Spring AOP中只是方法执行)
args(java.io.Serializable) // 'args'在绑定表单中更加常用; 请注意在例子中给出的切入点不同于 execution(* *(java.io.Serializable)): args版本只有在动态运行时候传入参数是Serializable时才匹配,而execution版本在方法签名中声明只有一个 Serializable类型的参数时候匹配。
// 目标对象中有一个 @Transactional 注解的任意连接点 (在Spring AOP中只是方法执行)
@target(org.springframework.transaction.annotation.Transactional)// '@target'在绑定表单中更加常用
// 任何一个目标对象声明的类型有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行):
@within(org.springframework.transaction.annotation.Transactional) // '@within'在绑定表单中更加常用
// 任何一个执行的方法有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行)
@annotation(org.springframework.transaction.annotation.Transactional) // '@annotation'在绑定表单中更加常用
// 任何一个只接受一个参数,并且运行时所传入的参数类型具有@Classified 注解的连接点(在Spring AOP中只是方法执行)
@args(com.xyz.security.Classified) // '@args'在绑定表单中更加常用
// 任何一个在名为'tradeService'的Spring bean之上的连接点 (在Spring AOP中只是方法执行)
bean(tradeService)
// 任何一个在名字匹配通配符表达式'*Service'的Spring bean之上的连接点 (在Spring AOP中只是方法执行)
bean(*Service)