为什么要学习代理模式?因为这就是SpringAOP的底层!
角色分析:
代码步骤:
package com.yujian.demo01;
//租房
public interface Rent {
public void rent();
}
package com.yujian.demo01;
//房东
public class Host implements Rent{
public void rent() {
System.out.println("房东要出租房子");
}
}
package com.yujian.demo01;
public class Proxy implements Rent{
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
public void rent() {
seeHouse();
host.rent();
hetong();
fare();
}
//看房
public void seeHouse(){
System.out.println("中介带你看房");
}
//收中介费
public void fare(){
System.out.println("中介收中介费");
}
//签合同
public void hetong(){
System.out.println("中介与你签租赁合同");
}
}
package com.yujian.demo01;
public class Client {
public static void main(String[] args) {
//房东要租房子
Host host = new Host();
//代理,直接帮房东租房子,但是代理角色会有一些附属操作
Proxy proxy = new Proxy(host);
//你不用面对房东,直接找中介即可
proxy.rent();
}
}
代理模式的好处:
缺点:
package com.yujian.demo02;
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
package com.yujian.demo02;
//真实对象
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("修改了一个用户");
}
public void query() {
System.out.println("查询了一个用户");
}
}
package com.yujian.demo02;
public class UserServiceProxy implements UserService{
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
public void add() {
log("add");
userService.add();
}
public void delete() {
log("delete");
userService.delete();
}
public void update() {
log("update");
userService.update();
}
public void query() {
log("query");
userService.query();
}
//日志方法
public void log(String msg){
System.out.println("[Debug]使用了"+msg+"方法");
}
}
package com.yujian.demo02;
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy();
proxy.setUserService(userService);
proxy.add();
}
}
需要了解两个类: Proxy: 代理, InvocationHandler: 调用处理程序
动态代理的好处:
package com.yujian.demo02;
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
package com.yujian.demo02;
//真实对象
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("修改了一个用户");
}
public void query() {
System.out.println("查询了一个用户");
}
}
package com.yujian.demo04;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//等会我们会用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
//处理代理实例,并返回结果 :
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
public void log(String msg){
System.out.println("执行了"+msg+"方法");
}
}
package com.yujian.demo04;
import com.yujian.demo02.UserService;
import com.yujian.demo02.UserServiceImpl;
public class Client {
public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//代理角色,不存在
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//设置要代理的对象
pih.setTarget(userService);
//动态生成代理类
UserService proxy = (UserService) pih.getProxy();
proxy.delete();
}
}
除了控制反转(IoC)和依赖注入(DI)外,Spring 框架还提供了对面向切面编程(AOP)的支持。本节,我们就对 AOP 面向切面编程进行讲解。
AOP 的全称是“Aspect Oriented Programming”,译为“面向切面编程”,和 OOP(面向对象编程)类似,它也是一种编程思想。
AOP(Aspect Oriented Programming)意为:面向切面(方面)编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
通俗描述 : 不通过修改源代码方式,在主干方法中添加新的功能
AOP 可以被分为以下 2 个不同的类型。
动态 AOP 的织入过程是在运行时动态执行的。其中最具代表性的动态 AOP 实现就是 Spring AOP,它会为所有被通知的对象创建代理对象,并通过代理对象对被原对象进行增强。
相较于静态 AOP 而言,动态 AOP 的性能通常较差,但随着技术的不断发展,它的性能也在不断的稳步提升。
动态 AOP 的优点是它可以轻松地对应用程序的所有切面进行修改,而无须对主程序代码进行重新编译。
静态 AOP 是通过修改应用程序的实际 Java 字节码,根据需要修改和扩展程序代码来实现织入过程的。最具代表性的静态 AOP 实现是 AspectJ。
相较于动态 AOP 来说,性能较好。但它也有一个明显的缺点,那就是对切面的任何修改都需要重新编译整个应用程序。
AOP 是 Spring 的核心之一,在 Spring 中经常会使用 AOP 来简化编程。
在 Spring 框架中使用 AOP 主要有以下优势。
与大多数的技术一样,AOP 已经形成一套属于自己的概念和术语。
名称 | 说明 |
---|---|
Joinpoint(连接点) | AOP 的核心概念,指的是程序执行期间明确定义的一个点,例如方法的调用、类初始化、对象实例化等。 在 Spring 中,连接点则指可以被动态代理拦截目标类的方法。 |
Pointcut(切入点) | 又称切点,指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 |
Advice(通知) | 指拦截到 Joinpoint 之后要执行的代码,即对切入点增强的内容。 |
Target(目标) | 指代理的目标对象,通常也被称为被通知(advised)对象。 |
Weaving(织入) | 指把增强代码应用到目标对象上,生成代理对象的过程。 |
Proxy(代理) | 指生成的代理对象。 |
Aspect(切面) | 切面是切入点(Pointcut)和通知(Advice)的结合。 |
提供声明式事务;允许用户自定义切面
SpringAOP中,通过Advice定义横切逻辑。Spring中支持5种类型的Advice:
通知 | 说明 |
---|---|
before(前置通知) | 通知方法在目标方法调用之前执行 |
after(后置通知) | 通知方法在目标方法返回或异常后调用 |
after-returning(返回后通知) | 通知方法会在目标方法返回后调用 |
after-throwing(抛出异常通知) | 通知方法会在目标方法抛出异常后调用 |
around(环绕通知) | 通知方法会将目标方法封装起来 |
即AOP在不改变原有代码的情况下,去增加新的功能。
用通俗的话讲
连接点:类里面哪些方法可以被增强,这些方法被称为连接点
切入点:实际被真正增强的方法,称为切入点
通知(增强):实际增强的逻辑部分称为通知(增强)。通知的类型:前置通知、后置通知、环绕通知、异常通知、最终通知
切面:把通知应用到切入点过程(是动作)
Spring AOP 是 Spring 框架的核心模块之一,它使用纯 Java 实现,因此不需要专门的编译过程和类加载器,可以在程序运行期通过代理方式向目标类织入增强代码。
【重点】使用AOP织入,需要导入一个依赖包!
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.6version>
dependency>
以下我们对Aspectj进行简单介绍下
AspectJ:Java社区里最完整最流行的AOP框架。在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。
在 Spring 中使用 AspectJ 进行 AOP 操作
实现 AOP 操作的步骤
编写切面类(通过 @Aspect
注解标识这是一个切面类),并且不要忘记将切面类交给 Spring IOC 管理(Component
注解),并编写相应的通知方法与切入点表达式
在 Spring 配置文件中开启 aop 功能:通过 < aop:aspectj-autoproxy/ > 注解开启 aop 功能.
当Spring IOC容器侦测到bean配置文件中的< aop:aspectj-autoproxy >元素时,会自动为与AspectJ切面匹配的bean创建代理
AspectJ支持5种类型的通知注解
[1]@Before
:前置通知,在方法执行之前执行
[2]@After
:后置通知,在方法执行之后执行
[3]@AfterRunning
:返回通知,在方法返回结果之后执行
[4]@AfterThrowing
:异常通知,在方法抛出异常之后执行
[5]@Around
:环绕通知,围绕着方法执行
切入点表达式的相关细节
切入点的作用:通过表达式的方式定位一个或多个具体的连接点(哪些方法需要被增强)
切入点表达式的语法格式:execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))
切入点表达式的举例一
表达式:execution(* com.yujian.service.UserServiceImpl.*(..))
含义:增强 UserServiceImpl
接口中声明的所有方法
解释说明:第一个" * "代表任意修饰符及任意返回值;第二个" * "代表任意方法;“…”匹配任意数量、任意类型的参数
注:若目标类、接口与该切面类在同一个包中可以省略包名
切入点表达式的举例二
表达式:execution(public * UserServiceImpl.*(..))
含义: 增强 UserServiceImpl
接口的所有公有方法(TMD 接口中的方法不都是 public 吗)
切入点表达式的举例三
表达式:execution(public double UserServiceImpl.*(..))
含义:增强 ArithmeticCalculator
接口中返回double类型数值的方法
切入点表达式的举例四
表达式:execution(public double UserServiceImpl.*(double, ..))
含义:第一个参数为double类型的方法。“…” 匹配任意数量、任意类型的参数
切入点表达式的举例五
表达式: execution(public double ArithmeticCalculator.*(double, double))
含义:参数类型为double,double类型的方法
切入点表达式的举例六:在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。
表达式:execution (* *.add(int,..)) || execution(* *.sub(int,..))
含义:任意类中第一个参数为int类型的add方法或sub方法
将切入点表达式应用到实际的切面类中
当前连接点的相关细节
切入点表达式通常都会是从宏观上定位一组方法,和具体某个通知的注解结合起来就能够确定对应的连接点。
那么就一个具体的连接点而言,我们可能会关心这个连接点的一些具体信息,例如:当前连接点所在方法的方法名、当前传入的参数值等等。这些信息都封装在JoinPoint
接口的实例对象中
五种通知的相关细节
通知的概述
前置通知
前置通知:在方法执行之前执行的通知,使用@Before
注解
后置通知
后置通知:后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候,使用@After
注解
返回通知
返回通知:无论连接点是正常返回还是抛出异常,后置通知都会执行。如果只想在连接点返回的时候记录日志,应使用返回通知代替后置通知。使用@AfterReturning
注解
在返回通知中访问连接点的返回值:
@AfterReturning
注解中,就可以访问连接点的返回值。该属性的值即为用来传入返回值的参数名称pointcut
属性中异常通知
异常通知:只在连接点抛出异常时才执行异常通知
将throwing
属性添加到@AfterThrowing
注解中,也可以访问连接点抛出的异常。Throwable是所有错误和异常类的顶级父类,所以在异常通知方法可以捕获到任何错误和异常。
如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型。然后通知就只在抛出这个类型及其子类的异常时才被执行
环绕通知
环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。
对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint
。它是 JoinPoint
的子接口,允许控制何时执行,是否执行连接点。
在环绕通知中需要明确调用ProceedingJoinPoint
的proceed()
方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。
注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();
的返回值,否则会出现空指针异常。
Spring 使用 org.springframework.aop.Advisor 接口表示切面的概念,实现对通知(Adivce)和连接点(Joinpoint)的管理。
在 Spring AOP 中,切面可以分为三类:一般切面、切点切面和引介切面。
切面类型 | 接口 | 描述 |
---|---|---|
一般切面 | org.springframework.aop.Advisor | Spring AOP 默认的切面类型。 由于 Advisor 接口仅包含一个 Advice(通知)类型的属性,而没有定义 PointCut(切入点),因此它表示一个不带切点的简单切面。 这样的切面会对目标对象(Target)中的所有方法进行拦截并织入增强代码。由于这个切面太过宽泛,因此我们一般不会直接使用。 |
切点切面 | org.springframework.aop.PointcutAdvisor | Advisor 的子接口,用来表示带切点的切面,该接口在 Advisor 的基础上还维护了一个 PointCut(切点)类型的属性。 使用它,我们可以通过包名、类名、方法名等信息更加灵活的定义切面中的切入点,提供更具有适用性的切面。 |
引介切面 | org.springframework.aop.IntroductionAdvisor | Advisor 的子接口,用来代表引介切面,引介切面是对应引介增强的特殊的切面,它应用于类层面上,所以引介切面适用 ClassFilter 进行定义。 |
当我们在使用 Spring AOP 开发时,若没有对切面进行具体定义,Spring AOP 会通过 Advisor 为我们定义一个一般切面(不带切点的切面),然后对目标对象(Target)中的所有方法连接点进行拦截,并织入增强代码。
下面我们就通过一个简单的实例演示下一般切面的 AOP 开发流程。
在com.yujian.dao 包下,创建一个名为 UserDao 的接口,代码如下。
package com.yujian.dao;
public interface UserDao {
public void add();
public void delete();
public void modify();
public void get();
}
在 com.yujian.dao.impl 包下,创建 UserDao 的实现类 UserDaoImpl,代码如下。
package com.yujian.dao.impl;
import com.yujian.dao.UserDao;
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("正在执行 UserDao 的 add() 方法……");
}
@Override
public void delete() {
System.out.println("正在执行 UserDao 的 delete() 方法……");
}
@Override
public void modify() {
System.out.println("正在执行 UserDao 的 modify() 方法……");
}
@Override
public void get() {
System.out.println("正在执行 UserDao 的 get() 方法……");
}
}
在 com.yujian.advice 包下,创建一个名为 UserDaoBeforeAdvice 的前置增强类,代码如下。
package com.yujian.advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
* 增强代码
* MethodBeforeAdvice 前置增强
*/
public class UserDaoBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("正在执行前置增强操作…………");
}
}
在 resources目录下创建一个 Spring 的配置文件 bean.xml,配置内容如下。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.yujian.dao.impl.UserDaoImpl">bean>
<bean id="beforeAdvice" class="com.yujian.advice.UserDaoBeforeAdvice"/>
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="userDao"/>
<property name="proxyInterfaces" value="com.yujian.dao.UserDao"/>
<property name="interceptorNames" value="beforeAdvice"/>
bean>
beans>
Spring 能够基于 org.springframework.aop.framework.ProxyFactoryBean 类,根据目标对象的类型(是否实现了接口)自动选择使用 JDK 动态代理或 CGLIB 动态代理机制,为目标对象(Target Bean)生成对应的代理对象(Proxy Bean)。
ProxyFactoryBean 的常用属性如下表所示。
属性 | 描述 |
---|---|
target | 需要代理的目标对象(Bean) |
proxyInterfaces | 代理需要实现的接口,如果需要实现多个接口,可以通过 元素进行赋值。 |
proxyTargetClass | 针对类的代理,该属性默认取值为 false(可省略), 表示使用 JDK 动态代理;取值为 true,表示使用 CGlib 动态代理 |
interceptorNames | 拦截器的名字,该属性的取值既可以是拦截器、也可以是 Advice(通知)类型的 Bean,还可以是切面(Advisor)的 Bean。 |
singleton | 返回的代理对象是否为单例模式,默认值为 true。 |
optimize | 是否对创建的代理进行优化(只适用于CGLIB)。 |
在 com.yujian 包下,创建一个名为 MainApp 的类,代码如下。
package com.yujian;
import com.yujian.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
//获取 ApplicationContext 容器
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//获取代理对象
UserDao userDao = context.getBean("userDaoProxy", UserDao.class);
//调用 UserDao 中的各个方法
userDao.add();
userDao.delete();
userDao.get();
userDao.modify();
}
}
执行 MainApp 中的 main 方法,控制台输出如下。
正在执行前置增强操作…………
正在执行 UserDao 的 add() 方法……
正在执行前置增强操作…………
正在执行 UserDao 的 delete() 方法……
正在执行前置增强操作…………
正在执行 UserDao 的 get() 方法……
正在执行前置增强操作…………
正在执行 UserDao 的 modify() 方法……
从控制台输出可以看出,UserDao 接口中的所有方法都被增强了。
PointCutAdvisor 是 Adivsor 接口的子接口,用来表示带切点的切面。使用它,我们可以通过包名、类名、方法名等信息更加灵活的定义切面中的切入点,提供更具有适用性的切面。
Spring 提供了多个 PointCutAdvisor 的实现,其中常用实现类如如下。
下面我们就通过一个简单的实例,演示下切点切面的 AOP 开发。
在 my-spring-aop-demo 的 com.yujian.dao 包下,创建一个名为 OrderDao,代码如下。
package com.yujian.dao;
public class OrderDao {
public void add() {
System.out.println("正在执行 UserDao 的 add() 方法……");
}
public void adds() {
System.out.println("正在执行 UserDao 的 adds() 方法……");
}
public void delete() {
System.out.println("正在执行 UserDao 的 delete() 方法……");
}
public void modify() {
System.out.println("正在执行 UserDao 的 modify() 方法……");
}
public void get() {
System.out.println("正在执行 UserDao 的 get() 方法……");
}
}
在 com.yujian.advice 包下,创建一个名为 OrderDaoAroundAdvice 的环绕增强类,代码如下。
package com.yujian.advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 增强代码
* 环绕增强
*
* @author c语言中文网 c.biancheng.net
*/
public class OrderDaoAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕增强前********");
//执行被代理对象中的逻辑
Object result = methodInvocation.proceed();
System.out.println("环绕增强后********");
return result;
}
}
在 bean1.xml 中添加以下配置。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="orderDao" class="com.yujian.dao.OrderDao">bean>
<bean id="aroundAdvice" class="com.yujian.advice.OrderDaoAroundAdvice">bean>
<bean id="myPointCutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="patterns" value="com.yujian.dao.OrderDao.add.*,com.yujian.dao.OrderDao.delete.*">property>
<property name="advice" ref="aroundAdvice">property>
bean>
<bean id="orderDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="orderDao">property>
<property name="proxyTargetClass" value="true">property>
<property name="interceptorNames" value="myPointCutAdvisor">property>
bean>
beans>
修改 MainApp 类 main 方法的代码。
package com.yujian;
import com.yujian.dao.OrderDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp1 {
public static void main(String[] args) {
//获取 ApplicationContext 容器
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
//获取代理对象
OrderDao orderDao = context.getBean("orderDaoProxy", OrderDao.class);
//调用 OrderDao 中的各个方法
orderDao.add();
orderDao.adds();
orderDao.delete();
orderDao.get();
orderDao.modify();
}
}
执行 MainApp 中的 main() 方法,控制台输出如下。
环绕增强前********
正在执行 UserDao 的 add() 方法……
环绕增强后********
环绕增强前********
正在执行 UserDao 的 adds() 方法……
环绕增强后********
环绕增强前********
正在执行 UserDao 的 delete() 方法……
环绕增强后********
正在执行 UserDao 的 get() 方法……
正在执行 UserDao 的 modify() 方法……
在前面的案例中,所有目标对象(Target Bean)的代理对象(Proxy Bean)都是在 XML 配置中通过 ProxyFactoryBean 创建的。但在实际开发中,一个项目中往往包含非常多的 Bean, 如果每个 Bean 都通过 ProxyFactoryBean 创建,那么开发和维护成本会十分巨大。为了解决这个问题,Spring 为我们提供了自动代理机制。
Spring 提供的自动代理方案,都是基于后处理 Bean 实现的,即在 Bean 创建的过程中完成增强,并将目标对象替换为自动生成的代理对象。通过 Spring 的自动代理,我们在程序中直接拿到的 Bean 就已经是 Spring 自动生成的代理对象了。
Spring 为我们提供了 3 种自动代理方案:
本节我们就通过两个简单的实例,对 BeanNameAutoProxyCreator 和 DefaultAdvisorAutoProxyCreator 进行演示,至于 AnnotationAwareAspectJAutoProxyCreator,我们会在后续的教程中进行讲解。
在 resources 目录下,创建一个名为 Beans2.xml 的配置文件,配置内容如下。
<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-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="userDao" class="com.yujian.dao.impl.UserDaoImpl">bean>
<bean id="orderDao" class="com.yujian.dao.OrderDao">bean>
<bean id="beforeAdvice" class="com.yujian.advice.UserDaoBeforeAdvice">bean>
<bean id="aroundAdvice" class="com.yujian.advice.OrderDaoAroundAdvice">bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*Dao">property>
<property name="interceptorNames" value="beforeAdvice,aroundAdvice">property>
bean>
beans>
修改 MainApp 中 main 方法,代码如下。
package com.yujian;
import com.yujian.dao.OrderDao;
import com.yujian.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp2 {
public static void main(String[] args) {
//获取 ApplicationContext 容器
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
//获取代理对象
UserDao userDao = context.getBean("userDao", UserDao.class);
//获取代理对象
OrderDao orderDao = context.getBean("orderDao", OrderDao.class);
//调用 UserDao 中的各个方法
userDao.add();
userDao.delete();
userDao.modify();
userDao.get();
//调用 OrderDao 中的各个方法
orderDao.add();
orderDao.adds();
orderDao.delete();
orderDao.get();
orderDao.modify();
}
}
执行 MainApp 中的 main() 方法,控制台输出如下。
正在执行前置增强操作…………
环绕增强前********
正在执行 UserDao 的 add() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 UserDao 的 delete() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 UserDao 的 modify() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 UserDao 的 get() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 UserDao 的 add() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 UserDao 的 adds() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 UserDao 的 delete() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 UserDao 的 get() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 UserDao 的 modify() 方法……
环绕增强后********
修改 Beans2.xml 中的配置,配置内容如下。
<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-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="userDao" class="net.biancheng.c.dao.impl.UserDaoImpl">bean>
<bean id="orderDao" class="net.biancheng.c.dao.OrderDao">bean>
<bean id="beforeAdvice" class="net.biancheng.c.advice.UserDaoBeforeAdvice">bean>
<bean id="aroundAdvice" class="net.biancheng.c.advice.OrderDaoAroundAdvice">bean>
<bean id="myPointCutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="patterns"
value="net.biancheng.c.dao.OrderDao.add.*,net.biancheng.c.dao.OrderDao.delete.*">property>
<property name="advice" ref="aroundAdvice">property>
bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">bean>
beans>
执行 MainApp 中的 main() 方法,控制台输出如下。
正在执行 UserDao 的 add() 方法……
正在执行 UserDao 的 delete() 方法……
正在执行 UserDao 的 modify() 方法……
正在执行 UserDao 的 get() 方法……
环绕增强前********
正在执行 OrderDao 的 add() 方法……
环绕增强后********
环绕增强前********
正在执行 OrderDao 的 adds() 方法……
环绕增强后********
环绕增强前********
正在执行 OrderDao 的 delete() 方法……
环绕增强后********
正在执行 OrderDao 的 get() 方法……
正在执行 OrderDao 的 modify() 方法……
重用切入点定义
在编写AspectJ切面时,可以直接在通知注解中书写切入点表达式。但同一个切点表达式可能会在多个通知中重复出现。
在AspectJ切面中,可以通过@Pointcut
注解将一个切入点声明成简单的方法。切入点的方法体通常是空的。
切入点方法的访问控制符同时也控制着这个切入点的可见性。如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中。在这种情况下,它们必须被声明为public
。在引入这个切入点时,必须将类名也包括在内。如果类没有与这个切面放在同一个包中,还必须包含包名。
其他通知可以通过方法名称引入该切入点
指定切面的优先级
在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的。
切面的优先级可以通过实现Ordered
接口或利用@Order
注解指定。
实现Ordered
接口,getOrder()
方法的返回值越小,优先级越高。
若使用@Order
注解,序号出现在注解中
@Component
@Aspect
@Order(0)
public class CalculatorValidationAspect {
@Component
@Aspect
@Order(1)
public class CalculatorLoggingAspect {
我们知道,Spring AOP 是一个简化版的 AOP 实现,并没有提供完整版的 AOP 功能。通常情况下,Spring AOP 是能够满足我们日常开发过程中的大多数场景的,但在某些情况下,我们可能需要使用 Spring AOP 范围外的某些 AOP 功能。
例如 Spring AOP 仅支持执行公共(public)非静态方法的调用作为连接点,如果我们需要向受保护的(protected)或私有的(private)的方法进行增强,此时就需要使用功能更加全面的 AOP 框架来实现,其中使用最多的就是 AspectJ。
AspectJ 是一个基于 Java 语言的全功能的 AOP 框架,它并不是 Spring 组成部分,是一款独立的 AOP 框架。
但由于 AspectJ 支持通过 Spring 配置 AspectJ 切面,因此它是 Spring AOP 的完美补充,通常情况下,我们都是将 AspectJ 和 Spirng 框架一起使用,简化 AOP 操作。
使用 AspectJ 需要在 Spring 项目中导入 Spring AOP 和 AspectJ 相关 Jar 包。
在以上 3 个 Jar 包中,spring-aop-xxx.jar 和 spring-aspects-xxx.jar 为 Spring 框架提供的 Jar 包,而 aspectjweaver-xxxx.jar 则是 AspectJ 提供的。
AspectJ Jar 包的下载步骤如下。
使用浏览器访问 AspectJ 包下载页面,选择相应的版本,这里我们以 1.9.5 稳定版本为例进行介绍。
图1:AspectJ 下载
我们可以在 Spring 项目中通过 XML 配置,对切面(Aspect 或 Advisor)、切点(PointCut)以及通知(Advice)进行定义和管理,以实现基于 AspectJ 的 AOP 开发。
Spring 提供了基于 XML 的 AOP 支持,并提供了一个名为“aop”的命名空间,该命名空间提供了一个 aop:config 元素。
首先,我们需要在 XML 配置文件中导入 Spring aop 命名空间的约束,如下所示。
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/beans/spring-aop.xsd">
beans>
在Spring配置文件中,使用
<aop:config>
<aop:aspect id="myAspect" ref="webApplicationContext">
aop:aspect>
aop:config>
其中,id用来定义该切面的唯一标识符,ref用于引用普通的Spring Bean
<aop:config>
<aop:pointcut id="myPointCut" expression="execution(* com.yujian.dao.*.*(..))"/>
aop:config>
其中,id用于指定切入点的唯一标识名称,execution用于指定切入点关联的切入点表达式
execution的语法格式为:
execution([权限修饰符] [返回值类型] [类的完全限定名] [方法名称]([参数列表]))
其中:
举例一 : 对com.yujian包下的 UserDao类中的 add()方法进行增强,配置如下.
execution(* com.yujian.UserDao.add(…))
举例二 : 对com.yujian包下的 UserDao类中的所有方法进行增强,配置如下.
execution(* com.yujian.UserDao.*(…))
举例三 : 对com.yujian包下的 所有类中的所有方法进行增强,配置如下.
execution(* com.yujian.*.*(…))
Aspect 支持5种类型的advice,如下:
<aop:config>
<aop:aspect id="myAsepct" ref="webApplicationContext">
<aop:before pointcut-ref="myPointCut" method="..."/>
<aop:after-returning pointcut-ref="myPointCut" method="..."/>
<aop:around pointcut-ref="myPointCut" method="..."/>
<aop:after-throwing pointcut-ref="myPointCut" method="..."/>
<aop:after pointcut-ref="myPointCut" method="..."/>
...
aop:aspect>
aop:config>
下面我们通过一个示例来演示下 Spring 集成 AspectJ 基于 XML 实现 AOP 开发。
新建一个名为 my-spring-asepctj-demo 的 Java 项目,并将以下依赖 Jar 包导入到该项目中。
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.11version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.3.7version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.3.7version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.7version>
dependency>
dependencies>
在 com.yujian.dao 包下,创建一个名为 OrderDao 的接口,代码如下。
package com.yujian.dao;
public interface OrderDao {
public void add();
public void delete();
public int modify();
public void get();
}
在 com.yujian.dao.impl 包下,创建 OrderDao 的实现类 OrderDaoImpl,代码如下。
package com.yujian.dao.impl;
import com.yujian.dao.OrderDao;
public class OrderDaoImpl implements OrderDao {
@Override
public void add() {
System.out.println("正在执行 OrderDao 中的 add() 方法");
}
@Override
public void delete() {
System.out.println("正在执行 OrderDao 中的 delete() 方法");
}
@Override
public int modify() {
System.out.println("正在执行 OrderDao 中的 modify() 方法");
return 1;
}
@Override
public void get() {
//异常
int a = 10 / 0;
System.out.println("正在执行 OrderDao 中的 get() 方法");
}
}
在 com.yujian. 包下,创建一个名为 MyOrderAspect 的类,代码如下。
package com.yujian;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyOrderAspect {
public void before() {
System.out.println("前置增强……");
}
public void after() {
System.out.println("最终增强……");
}
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕增强---前……");
proceedingJoinPoint.proceed();
System.out.println("环绕增强---后……");
}
public void afterThrow(Throwable exception) {
System.out.println("异常增强…… 异常信息为:" + exception.getMessage());
}
public void afterReturning(Object returnValue) {
System.out.println("后置返回增强…… 方法返回值为:" + returnValue);
}
}
在 resources 目录下创建一个 Spring 配置文件 Bean.xml,配置内容如下。
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="orderDao" class="com.yujian.dao.impl.OrderDaoImpl">bean>
<bean id="myOrderAspect" class="com.yujian.MyOrderAspect">bean>
<aop:config>
<aop:pointcut id="beforePointCut" expression="execution(* com.yujian.dao.OrderDao.add(..))"/>
<aop:pointcut id="throwPointCut" expression="execution(* com.yujian.dao.OrderDao.get(..))"/>
<aop:pointcut id="afterReturnPointCut" expression="execution(* com.yujian.dao.OrderDao.modify(..))"/>
<aop:pointcut id="afterPointCut" expression="execution(* com.yujian.dao.OrderDao.*(..))"/>
<aop:aspect ref="myOrderAspect">
<aop:before method="before" pointcut-ref="beforePointCut">aop:before>
<aop:after-returning method="afterReturning" pointcut-ref="afterReturnPointCut"
returning="returnValue">aop:after-returning>
<aop:after-throwing method="afterThrow" pointcut-ref="throwPointCut"
throwing="exception">aop:after-throwing>
<aop:after method="after" pointcut-ref="afterPointCut">aop:after>
<aop:around method="around" pointcut-ref="beforePointCut">aop:around>
aop:aspect>
aop:config>
beans>
在com.yujian包下,创建一个名为MainApp的类,代码如下:
package com.yujian;
import com.yujian.dao.OrderDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
OrderDao orderDao = context.getBean("orderDao", OrderDao.class);
orderDao.add();
orderDao.delete();
orderDao.modify();
orderDao.get();
}
}
执行MainApp中的main方法,控制台输出如下.
前置增强……
环绕增强—前……
正在执行 OrderDao 中的 add() 方法
环绕增强—后……
最终增强……
正在执行 OrderDao 中的 delete() 方法
最终增强……
正在执行 OrderDao 中的 modify() 方法
最终增强……
后置返回增强…… 方法返回值为:1
最终增强……
异常增强…… 异常信息为:/ by zero
其中,id用于指定切入点的唯一标识名称,execution用于指定切入点关联的切入点表达式
在 Spring 中,虽然我们可以使用 XML 配置文件可以实现 AOP 开发,但如果所有的配置都集中在 XML 配置文件中,就势必会造成 XML 配置文件过于臃肿,从而给维护和升级带来一定困难。
为此,AspectJ 框架为 AOP 开发提供了一套 @AspectJ 注解。它允许我们直接在 Java 类中通过注解的方式对切面(Aspect)、切入点(Pointcut)和增强(Advice)进行定义,Spring 框架可以根据这些注解生成 AOP 代理。
关于注解的介绍如表 1 所示。
表 1 Annotation 注解介绍
名称 | 说明 |
---|---|
@Aspect | 用于定义一个切面。 |
@Pointcut | 用于定义一个切入点。 |
@Before | 用于定义前置通知,相当于 BeforeAdvice。 |
@AfterReturning | 用于定义后置通知,相当于 AfterReturningAdvice。 |
@Around | 用于定义环绕通知,相当于 MethodInterceptor。 |
@AfterThrowing | 用于定义抛出通知,相当于 ThrowAdvice。 |
@After | 用于定义最终通知,不管是否异常,该通知都会执行。 |
@DeclareParents | 用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。 |
在使用 @AspectJ 注解进行 AOP 开发前,首先我们要先启用 @AspectJ 注解支持。
我们可以通过以下 2 种方式来启用 @AspectJ 注解。
我们可以在 Java 配置类(标注了 @Configuration 注解的类)中,使用 @EnableAspectJAutoProxy 和 @ComponentScan 注解启用 @AspectJ 注解支持。
package com.yujian.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = "com.yujian") //注解扫描
@EnableAspectJAutoProxy //开启AspectJ 的自动代理
public class AppConfig {
}
在Spring的 XML 配置文件,添加以下内容启用 @AspectJ 注解支持
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/c"
xmlns:aop="http://www.springframework.org/schema/aop"
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">
<context:component-scan base-package="com.yujian">context:component-scan>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
beans>
我们可以通过 @Aspect 注解将一个 Bean 定义为切面
在启用 @AspectJ 注解支持情况下,Spring会自动将IoC容器(ApplicationContext) 中的所有使用了 @Aspect 注解的Bean识别为一个切面
我们可以在XML 配置通过一些配置将这个类定义为一个Bea,如下:
<bean id="myAspect" class="com.yujian.MyAspect">
...
bean>
在定义完Bean后,我们只需要在Bean对应的java类中使用一个@Aspect注解,将这个Bean定义为一个切面,代码如下.
package com.yujian;
import org.aspectj.lang.annotation.Aspect;
@Aspect //定义为切面
public class MyAspect {
}
全注解方式定义切面
我们可以在Java类上使用下面2个注解,使用全注解方式定义切面
package com.yujian;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component //定义成Bean
@Aspect //定义为切面
public class MyAspect {
}
在上述代码中共使用两个注解:
在Aspectl中,我们可以使用@Pointcut 注解用来定义一个切点。需要注意的是,定义为切点的方法,它的返回值类型必须为void,示例代码如下。
//要求:方法必须是private,返回值类型为void,名称自定义,没有参数
@Pointcut("execution(* com.yujian..*.*(..))")
private void myPointCut(){
}
@Pointcut 注解中有一个value属性,这个属性的值就是切入点表达式。有关切入点表达式的具体介绍请参考《使用AspectJ 实现AOP(基于XML)》中的execution 语法格式介绍。
值得注意的是,我除了可以通过切入点表达式(execution)直接对切点进行定义外,还可以通过切入点方法的名称来引用其他的切入点。在使用方法名引用其他切入点时,还可以使用“&&”、"||"和“!”等表示“与”、“或”、“非”的含义,示例代码如下。
package com.yujian;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component //定义成Bean
@Aspect //定义为切面
public class MyAspect {
//要求:方法必须是private,返回值类型为void,名称自定义,没有参数
@Pointcut("execution(* com.yujian..*.*(..))")
private void myPointCut(){
}
/**
* 将com.yujian.dao包下OrderDao类中的get()方法定义为一个切点
*/
@Pointcut(value = "execution(* com.yujian.dao.OrderDao.get(..))")
public void pointCut1(){
}
/**
* 将com.yujian.dao包下OrderDao类中的delete()方法定义为一个切点
*/
@Pointcut(value = "execution(* com.yujian.dao.OrderDao.delete(..))")
public void pointCut2(){
}
/**
* 除了com.yujian.dao包下UserDao类中get()方法和delete()方法外,其他方法都定义为切点
*
* !表示非,即不是的含义,求补集
* * &&表示与,即”并且“,求交集
* ||表示或,即“或者”,求并集
*/
@Pointcut(value = "!pointCut1() && pointCut2()")
public void pointCut3(){
}
}
AspectJ 为我们提供了以下 6 个注解,来定义 6 种不同类型的通知(Advice),如下表。
注解 | 说明 |
---|---|
@Before | 用于定义前置通知,相当于 BeforeAdvice。 |
@AfterReturning | 用于定义后置通知,相当于 AfterReturningAdvice。 |
@Around | 用于定义环绕通知,相当于 MethodInterceptor。 |
@AfterThrowing | 用于定义抛出通知,相当于 ThrowAdvice。 |
@After | 用于定义最终通知,不管是否异常,该通知都会执行。 |
@DeclareParents | 用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。 |
以上这些通知注解中都有一个 value 属性,这个 value 属性的取值就是这些通知(Advice)作用的切点(PointCut),它既可以是切入点表达式,也可以是切入点的引用(切入点对应的方法名称),示例代码如下。
package com.yujian;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component //定义成Bean
@Aspect //定义为切面
public class MyAspect {
//要求:方法必须是private,返回值类型为void,名称自定义,没有参数
@Pointcut("execution(* com.yujian..*.*(..))")
private void myPointCut(){
}
/**
* 将com.yujian.dao包下OrderDao类中的get()方法定义为一个切点
*/
@Pointcut(value = "execution(* com.yujian.dao.OrderDao.get(..))")
public void pointCut1(){
}
/**
* 将com.yujian.dao包下OrderDao类中的delete()方法定义为一个切点
*/
@Pointcut(value = "execution(* com.yujian.dao.OrderDao.delete(..))")
public void pointCut2(){
}
/**
* 除了com.yujian.dao包下UserDao类中get()方法和delete()方法外,其他方法都定义为切点
*
* !表示非,即不是的含义,求补集
* * &&表示与,即”并且“,求交集
* ||表示或,即“或者”,求并集
*/
@Pointcut(value = "!pointCut1() && pointCut2()")
public void pointCut3(){
}
//使用切入点引用
@Before("MyAspect.pointCut3()")
public void around() throws Throwable{
System.out.println("环绕增强....");
}
//使用切入点表达式
@AfterReturning(value = "execution(* com.yujian.dao.OrderDao.get(..))",returning = "returnValue")
public void afterReturning(Object returnValue){
System.out.println("方法返回值为:"+returnValue);
}
}
下面,我们就通过一个完整的实例,来演示下如何通过注解的方式实现 AspectJ AOP 开发。
新建一个名为 my-spring-asepctj-demo2 的 Java 项目,并将以下依赖 Jar 包导入到该项目中。
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.11version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.3.7version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.3.7version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.7version>
dependency>
在 net.biancheng.c.dao 包下,创建一个名为 UserDao 的接口,代码如下。
package net.biancheng.c.dao;
public interface UserDao {
public void add();
public void delete();
public int modify();
public void get();
}
在 net.biancheng.c.dao.impl 包下,创建UserDao的实现类UserDaoImpl,代码如下。
package net.biancheng.c.dao.impl;
import net.biancheng.c.dao.UserDao;
import org.springframework.stereotype.Component;
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("正在执行 UserDao 的add方法");
}
@Override
public void delete() {
System.out.println("正在执行 UserDao 的delete方法");
}
@Override
public int modify() {
System.out.println("正在执行 UserDao 的modify方法");
return 1;
}
@Override
public void get() {
System.out.println("正在执行 UserDao 的get方法");
}
}
在 net.biancheng.c.config包下,创建一个名为AppConfig的配置类,代码如下.
package net.biancheng.c.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = "net.biancheng.c") //注解扫描
@EnableAspectJAutoProxy //开启 AspectJ 的自动代理
public class AppConfig {
}
在 net.biancheng.c 包下,创建一个名为 MyAspect 的切面类,代码如下。
package net.biancheng.c;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component // 定义成 Bean
@Aspect //定义为切面
public class MyAspect {
@Before("execution(* net.biancheng.c.dao.UserDao.add(..))")
public void before(JoinPoint joinPoint) {
System.out.println("前置增强……" + joinPoint);
}
@After("execution(* net.biancheng.c.dao.UserDao.get(..))")
public void after(JoinPoint joinPoint) {
System.out.println("最终增强……" + joinPoint);
}
/**
* 将 net.biancheng.c.dao包下的 UserDao 类中的 get() 方法 定义为一个切点
*/
@Pointcut(value = "execution(* net.biancheng.c.dao.UserDao.get(..))")
public void pointCut1() {
}
/**
* 将 net.biancheng.c.dao包下的 UserDao 类中的 delete() 方法 定义为一个切点
*/
@Pointcut(value = "execution(* net.biancheng.c.dao.UserDao.delete(..))")
public void pointCut2() {
}
//使用切入点引用
@Around("MyAspect.pointCut2()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕增强……1");
proceedingJoinPoint.proceed();
System.out.println("环绕增强……2");
}
//使用切入点表达式
@AfterReturning(value = "execution(* net.biancheng.c.dao.UserDao.modify(..))", returning = "returnValue")
public void afterReturning(Object returnValue) {
System.out.println("后置返回增强……,方法返回值为:" + returnValue);
}
}
在 net.biancheng.c 包下,创建一个MainApp的类,代码如下:
package net.biancheng.c;
import net.biancheng.c.config.AppConfig;
import net.biancheng.c.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext content = new AnnotationConfigApplicationContext(AppConfig.class);
UserDao userDao = content.getBean("userDao", UserDao.class);
userDao.add();
userDao.delete();
userDao.modify();
userDao.get();
}
}
执行MainApp中的main()方法,控制台输出如下:
前置增强……execution(void net.biancheng.c.dao.UserDao.add())
正在执行 UserDao 的add方法
环绕增强……1
正在执行 UserDao 的delete方法
环绕增强……2
正在执行 UserDao 的modify方法
后置返回增强……,方法返回值为:1
正在执行 UserDao 的get方法
最终增强……execution(void net.biancheng.c.dao.UserDao.get())
我们知道,JDBC 是 Java 提供的一种用于执行 SQL 语句的 API,可以对多种关系型数据库(例如 MySQL、Oracle 等)进行访问。
但在实际的企业级应用开发中,却很少有人直接使用原生的 JDBC API 进行开发,这是因为使用 JDBC API 对数据库进行操作十分繁琐,需要我们对每一步都做到“步步把控,处处关心”,例如我们需要手动控制数据库连接的开启,异常处理、事务处理、最后还要手动关闭连接释放资源等等。
Spring 提供了一个 Spring JDBC 模块,它对 JDBC API 进行了封装,其的主要目的降低 JDBC API 的使用难度,以一种更直接、更简洁的方式使用 JDBC API。
使用 Spring JDBC,开发人员只需要定义必要的参数、指定需要执行的 SQL 语句,即可轻松的进行 JDBC 编程,对数据库进行访问。
至于驱动的加载、数据库连接的开启与关闭、SQL 语句的创建与执行、异常处理以及事务处理等繁杂乏味的工作,则都是由 Spring JDBC 完成的。这样就可以使开发人员从繁琐的 JDBC API 中解脱出来,有更多的精力专注于业务的开发。
Spring JDBC 提供了多个实用的数据库访问工具,以简化 JDBC 的开发,其中使用最多就是 JdbcTemplate。
JdbcTemplate 是 Spring JDBC 核心包(core)中的核心类,它可以通过配置文件、注解、Java 配置类等形式获取数据库的相关信息,实现了对 JDBC 开发过程中的驱动加载、连接的开启和关闭、SQL 语句的创建与执行、异常处理、事务处理、数据类型转换等操作的封装。我们只要对其传入SQL 语句和必要的参数即可轻松进行 JDBC 编程。
JdbcTemplate 的全限定命名为 org.springframework.jdbc.core.JdbcTemplate,它提供了大量的查询和更新数据库的方法,如下表所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-neaVNYLM-1677319308605)(photo/image-20221028102637200.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XzrqDEEq-1677319308605)(photo/image-20221028102653871.png)]
下面我们就结合实例,对使用 JdbcTemplate 进行 JDBC 编程进行讲解,步骤如下。
在 MySQL 数据库中创建一个 spring_jdbc_db 数据库实例,并执行以下 SQL 语句创建一个用户信息(user)表。
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`user_id` int NOT NULL AUTO_INCREMENT COMMENT '用户 ID',
`user_name` varchar(255) DEFAULT NULL COMMENT '用户名',
`status` varchar(255) DEFAULT NULL COMMENT '用户状态',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
创建一个名为 my-spring-jdbc-demo 的项目,并在将以下依赖导入到工程中。
除了 Spring 的核心依赖以及 commons-logging 日志包外,我们还需要在项目中导入以下依赖。
依赖 | 说明 |
---|---|
spring-jdbc-xxx.jar | Spring JDBC 的核心依赖包 |
spring-tx-xxx.jar | 用来处理事务和异常的依赖包 |
mysql-connector-java-xxx.jar | MySQL 提供的 JDBC 驱动包 |
在 src 目录下创建一个 jdbc.properties,并在该配置文件中对数据库连接信息进行配置。
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_jdbc_db
jdbc.username=root
jdbc.password=123456
在 src 目录下创建一个 XML 配置文件 Beans.xml,配置内容如下。
<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-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="net.biancheng.c">context:component-scan>
<context:property-placeholder location="classpath:jdbc.properties">context:property-placeholder>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource">property>
bean>
beans>
在以上配置中,我们共定义了两个 Bean,
Spring 默认使用 DriverManagerDataSource 对数据库连接池进行管理,我们可以在 Spring 的 XML 配置文件中定义 DriverManagerDataSource 的 Bean,并注入到 JdbcTempate 的 Bean 中。
在 dataSource 中,定义了 4 个连接数据库的属性,如下表所示。
属性名 | 说明 |
---|---|
driverClassName | 所使用的驱动名称,对应驱动 JAR 包中的 Driver 类 |
url | 数据源所在地址 |
username | 访问数据库的用户名 |
password | 访问数据库的密码 |
上表中的属性值需要根据数据库类型或者机器配置的不同进行相应设置。如果数据库类型不同,则需要更改驱动名称;如果数据库不在本地,则需要将 localhost 替换成相应的主机 IP。
在 net.biancheng.c.entity 包下,创建名为 User 的实体类,代码如下。
package net.biancheng.c.entity;
public class User {
private Integer userId;
private String userName;
private String status;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", status='" + status + '\'' +
'}';
}
}
在 net.biancheng.c.dao 包下,创建一个名为 UserDao 的 Dao 接口,代码如下。
package net.biancheng.c.dao;
import net.biancheng.c.entity.User;
import java.util.List;
public interface UserDao {
/**
* 新增一条用户
*
* @param user
* @return
*/
int addUer(User user);
/**
* 更新指定的用户信息
*
* @param user
* @return
*/
int update(User user);
/**
* 删除指定的用户信息
*
* @param user
* @return
*/
int delete(User user);
/**
* 统计用户个数
*
* @param user
* @return
*/
int count(User user);
/**
* 查询用户列表
*
* @param user
* @return
*/
List<User> getList(User user);
/**
* 查询单个用户信息
*
* @param user
* @return
*/
User getUser(User user);
/**
* 批量增加用户
*
* @param batchArgs
*/
void batchAddUser(List<Object[]> batchArgs);
}
在 net.biancheng.c.dao.impl 包下,创建 UserDao 的实现类 UserDaoImpl,代码如下。
package net.biancheng.c.dao.impl;
import net.biancheng.c.dao.UserDao;
import net.biancheng.c.entity.User;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.util.List;
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Resource
private JdbcTemplate jdbcTemplate;
//@Resource
//private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Override
public int addUer(User user) {
String sql = "INSERT into `user` (`user`.user_name,`user`.`status`) VALUES(?,?);";
int update = jdbcTemplate.update(sql, user.getUserName(), user.getStatus());
return update;
}
@Override
public int update(User user) {
String sql = "UPDATE `user` SET status=? WHERE user_name=?;";
return jdbcTemplate.update(sql, user.getStatus(), user.getUserName());
}
@Override
public int delete(User user) {
String sql = "DELETE FROM `user` where user_name=?;";
return jdbcTemplate.update(sql, user.getUserName());
}
@Override
public int count(User user) {
String sql = "SELECT COUNT(*) FROM `user` where `status`=?;";
return jdbcTemplate.queryForObject(sql, Integer.class, user.getStatus());
}
@Override
public List<User> getList(User user) {
String sql = "SELECT * FROM `user` where `status`=?;";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class), user.getStatus());
}
@Override
public User getUser(User user) {
String sql = "SELECT * FROM `user` where `user_id`=?;";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), user.getUserId());
}
@Override
public void batchAddUser(List<Object[]> batchArgs) {
String sql = "INSERT into `user` (`user`.user_name,`user`.`status`) VALUES(?,?);";
jdbcTemplate.batchUpdate(sql, batchArgs);
}
}
在 net.biancheng.c.service 包下,创建一个名为 UserService 的 Service 接口,代码如下。
package net.biancheng.c.service;
import net.biancheng.c.entity.User;
import java.util.List;
public interface UserService {
/**
* 新增用户数据
*
* @param user
* @return
*/
public int addUser(User user);
/**
* 更新用户数据
*
* @param user
* @return
*/
public int updateUser(User user);
/**
* 删除用户数据
*
* @param user
* @return
*/
public int deleteUser(User user);
/**
* 统计用户数量
*
* @param user
* @return
*/
public int countUser(User user);
/**
* 查询用户数据
*
* @param user
* @return
*/
public List<User> getUserList(User user);
/**
* 查询单个用户信息
*
* @param user
* @return
*/
public User getUser(User user);
/**
* 批量添加用户
*
* @param batchArgs
*/
public void batchAddUser(List<Object[]> batchArgs);
}
在 net.biancheng.c.service.impl 包下,创建 UserService 的实现类 UserServiceImpl,代码如下。
package net.biancheng.c.service.impl;
import net.biancheng.c.dao.UserDao;
import net.biancheng.c.entity.User;
import net.biancheng.c.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service("userService")
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDao;
@Override
public int addUser(User user) {
return userDao.addUer(user);
}
@Override
public int updateUser(User user) {
return userDao.update(user);
}
@Override
public int deleteUser(User user) {
return userDao.delete(user);
}
@Override
public int countUser(User user) {
return userDao.count(user);
}
@Override
public List<User> getUserList(User user) {
return userDao.getList(user);
}
@Override
public User getUser(User user) {
return userDao.getUser(user);
}
@Override
public void batchAddUser(List<Object[]> batchArgs) {
userDao.batchAddUser(batchArgs);
}
//@Override
//public int countOfUserByName(User user) {
// return userDao.countOfUserByName(user);
//}
//@Override
//public User getUserByUserId(User user) {
// return userDao.getUserByUserId(user);
//}
}
在 net.biancheng.c 包下,创建一个名为 MainApp 的类,代码如下。
package net.biancheng.c;
import net.biancheng.c.entity.User;
import net.biancheng.c.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.ArrayList;
import java.util.List;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context2 = new ClassPathXmlApplicationContext("Beans.xml");
UserService userService = context2.getBean("userService", UserService.class);
User user = new User();
user.setUserName("小张");
user.setStatus("离线线");
//新增一个用户
int i = userService.addUser(user);
System.out.println("新增用户成功!");
User user1 = new User();
user1.setUserName("小张");
user1.setStatus("在线");
int u = userService.updateUser(user1);
System.out.println("修改用户成功");
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"小明", "在线"};
Object[] o2 = {"小龙", "离线"};
Object[] o3 = {"小林", "在线"};
Object[] o4 = {"小李", "在线"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
batchArgs.add(o4);
userService.batchAddUser(batchArgs);
System.out.println("批量增加完毕");
User user2 = new User();
user2.setStatus("在线");
int i1 = userService.countUser(user2);
System.out.println("在线用户的个数为:" + i1);
List<User> userList = userService.getUserList(user2);
System.out.println("在线用户列表查询成功!");
for (User user4 : userList) {
System.out.println("用户 ID:" + user4.getUserId() + ",用户名:" + user4.getUserName() + ",状态:" + user4.getStatus());
}
}
}
执行 MainApp 中的 main 方法,控制台输出如下。
新增用户成功!
修改用户成功
批量增加完毕
在线用户的个数为:4
在线用户列表查询成功!
用户 ID:6,用户名:小张,状态:在线
用户 ID:7,用户名:小明,状态:在线
用户 ID:9,用户名:小林,状态:在线
用户 ID:10,用户名:小李,状态:在线
步骤:
导入相关的jar包
编写配置文件
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring_studyartifactId>
<groupId>com.yujiangroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>spring_10_mybatisartifactId>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.7version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.26version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>5.3.12version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.3.12version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.6version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>2.0.6version>
dependency>
dependencies>
project>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource">property>
<property name="configLocation" value="classpath:mybatis_config.xml"/>
<property name="mapperLocations" value="classpath:com/yujian/mapper/*.xml"/>
bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
bean>
package com.yujian.mapper;
import com.yujian.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
public class UserMapperImpl implements UserMapper{
//在原来,我们的所有操作,都使用sqlSession来执行
//现在,我们都使用SqlSessionTemplate
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
public List<User> selectUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<import resource="spring-dao.xml"/>
<bean id="userMapper" class="com.yujian.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
bean>
beans>
import com.yujian.mapper.UserMapper;
import com.yujian.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
public class MyTest {
@Test
public void selectUser() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
}
事务的ACID原则:
回顾一下:Mybatis-Spring
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource">property>
<property name="configLocation" value="classpath:mybatis_config.xml"/>
<property name="mapperLocations" value="classpath:com/yujian/mapper/*.xml"/>
bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
bean>
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.yujian.pojo"/>
typeAliases>
configuration>
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yujian.mapper.UserMapper">
<select id="selectUser" parameterType="User" resultType="User">
select * from user;
select>
<insert id="addUser" parameterType="User">
insert into user (id,name,pwd) values (#{id},#{name},#{name});
insert>
<delete id="deleteUser" parameterType="int">
deletes from user where id = #{id}
delete>
mapper>
package com.yujian.mapper;
import com.yujian.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
public List<User> selectUser() {
User user = new User(7, "健儿", "123456");
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
mapper.addUser(user);
mapper.deleteUser(7);
return mapper.selectUser();
}
public int addUser(User user) {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.addUser(user);
}
public int deleteUser(int id) {
return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
}
}
<bean id="userMapper" class="com.yujian.mapper.UserMapperImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
bean>
import com.yujian.mapper.UserMapper;
import com.yujian.pojo.User;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class MyTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationcontext.xml");
UserMapper mapper = context.getBean("userMapper", UserMapper.class);
List<User> users = mapper.selectUser();
for (User user : users) {
System.out.println(user);
}
}
}
思考
为什么需要事务?
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.yujian.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
aop:config>