Spring学习总结二
Spring学习总结二
今天的主要内容就是AOP:
- 什么是AOP
- AOP的重要概念
- 代理模式
- cglib代理
- 使用spring.xml配置实现动态代理
- 使用spring.xml实现AOP编程
- 使用AspectJ配置spring.xml实现AOP编程
- 使用注解的AspectJ
0、在理解什么是AOP之前的一些话
下面1、2两点都是我百度上抄的,说实话,我是不太能说出这么精准的术语来。
AOP,通俗来说就是在执行特定的方法前或者后,去做"某些事情"(比如:转账前需要将设置事务,转账"成功"后需要提交事务)。
我们将尺度放大!有一些业务经常需要换一些功能。而我们不想再去改项目的硬编码,从而有了AOP,或者是说,我想通过xml配置来动态的增加以及减少一些功能,这时候AOP就可以大显身手了。
如何学好AOP?
先训练,再看概念!理清楚思路,你将对AOP的概念读上几遍。
1、什么是AOP
AOP: (Aspect Oriented Programming) 面向切面编程。是目前软件开发中的一个热点,也是Spring框架中容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。
AOP、OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。 而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
举个简单的例子,对于“雇员”这样一个业务实体进行封装,自然是OOP的任务,我们可以为其建立一个“Employee”类,并将“雇员”相关的属性和行为封装其中。而用AOP设计思想对“雇员”进行封装将无从谈起。
同样,对于“权限检查”这一动作片断进行划分,则是AOP的目标领域。而通过OOP对一个动作进行封装,则有点不伦不类。 换而言之,OOP面向名词领域,AOP面向动词领域。
总之,AOP可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。
2、AOP的重要概念
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。然而殊途同归,实现AOP的技术特性却是相同的,分别为:
- join point(连接点):是程序执行中的一个精确执行点,例如类中的一个方法。它是一个抽象的概念,在实现AOP时,并不需要去定义一个join point。
- point cut(切入点):本质上是一个捕获连接点的结构。在AOP中,可以定义一个point cut,来捕获相关方法的调用。
- advice(通知):是point cut的执行代码,是执行“方面”的具体逻辑。
- aspect(方面):point cut和advice结合起来就是aspect,它类似于OOP中定义的一个类,但它代表的更多是对象间横向的关系。
- introduce(引入):为对象引入附加的方法或属性,从而达到修改对象结构的目的。有的AOP工具又将其称为mixin。
上述的技术特性组成了基本的AOP技术,大多数AOP工具均实现了这些技术。它们也可以是研究AOP技术的基本术语。
3、代理模式
说到代理模式,我们就需要说到下面两点:
- 静态代理
- 动态代理
3.1、静态代理
这里就不写静态代理的代码了。自己百度去看,主要的是以下3个点:
- 目标类
- 代理类
- 上面两个类继承同一个接口
3.2、动态代理
需要做的事情就5步:
- 创建接口,名为UserService
- 创建一个实现UserService接口的实现类,名为UserServiceImp
- 创建一个方面类,名为MyAspect
- 使用JDK自带的动态代理类来生成UserService的动态代理,这个类是一个工具类,名为MyBeanFactory
- 测试
3.2.1、创建UserService接口
package com.csa.service;
public interface UserService {
public void addUser();
public void updateUser();
public void deleteUser();
}
3.2.2、创建UserServiceImp类
package com.csa.service.Imp;
import com.csa.service.UserService;
public class UserServiceImp implements UserService{
@Override
public void addUser() {
// TODO Auto-generated method stub
System.out.println("add User to ...");
}
@Override
public void updateUser() {
// TODO Auto-generated method stub
System.out.println("update User to ...");
}
@Override
public void deleteUser() {
// TODO Auto-generated method stub
System.out.println("delete User to ...");
}
}
3.2.3、创建MyAspect方面类
package com.csa.aspect;
public class MyAspect {
public void before(){
System.out.println("之前");
}
public void after(){
System.out.println("之后");
}
}
3.2.4、动态代理UserService的工具类
动态代理步骤:
- 创建一个实现InvocationHandler接口的类,它必须实现invoke()方法
- 创建被代理的类及接口创建被代理的类及接口
- 调用Proxy的静态方法,创建一个代理类调用Proxy的静态方法,创建一个代理类
- 通过代理调用方法
想了解更多的可以看这篇代理模式深入理解
package com.csa.factory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.csa.aspect.MyAspect;
import com.csa.service.UserService;
import com.csa.service.Imp.UserServiceImp;
public class MyBeanFactory {
public static UserService createService() {
// 1. 目标类
UserService userService = new UserServiceImp();
// 2. 切面类
MyAspect myAspect = new MyAspect();
// 3. 代理类
UserService proxyService = (UserService) Proxy.newProxyInstance(MyBeanFactory.class.getClassLoader(),
userService.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
myAspect.before();
Object obj = method.invoke(userService,args);
myAspect.after();
return obj;
}
});
return proxyService;
}
}
3.2.5、测试
package com.csa.Test;
import org.junit.Test;
import com.csa.factory.MyBeanFactory;
import com.csa.service.UserService;
public class TestJDK {
@Test
public void demo(){
UserService userService = MyBeanFactory.createService();
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
测试结果:
4、cglib代理
分6步完成:
- 导jar包
- 创建接口,名为UserService(与上面动态代理的一样)
- 创建一个实现UserService接口的实现类,名为UserServiceImp(与上面动态代理的一样)
- 创建一个方面类,名为MyAspect(与上面动态代理的一样)
- 使用cglib的工具类来生成UserService的动态代理,这个类是一个工具类,名为MyBeanFactory
- 测试
4.1、导jar包
4.2、创建接口
(与上面动态代理的一样)
4.3、创建实现类
(与上面动态代理的一样)
4.4、创建方面类
(与上面动态代理的一样)
4.5、用cglib的工具类来生成目标类
package com.csa.factory;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import com.csa.aspect.MyAspect;
import com.csa.service.UserService;
import com.csa.service.Imp.UserServiceImp;
public class MyBeanFactory {
public static UserService createService() {
// 1. 目标类
UserService userService = new UserServiceImp();
// 2. 切面类
MyAspect myAspect = new MyAspect();
// 3. 代理类
// 3.1 核心类
Enhancer enhancer = new Enhancer();
// 3.2 确定父类
enhancer.setSuperclass(userService.getClass());
// 3.3 设置回调函数
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
// TODO Auto-generated method stub
myAspect.before();
Object obj = method.invoke(userService, args);
myAspect.after();
return obj;
}
});
UserServiceImp proxyService = (UserServiceImp) enhancer.create();
return proxyService;
}
}
4.6、测试
package com.csa.Test;
import org.junit.Test;
import com.csa.factory.MyBeanFactory;
import com.csa.service.UserService;
public class TestCGLIB {
@Test
public void demo(){
UserService userService = MyBeanFactory.createService();
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
测试结果:
5、使用spring.xml配置实现动态代理
分下面6个步骤:
- 导jar包
- 创建接口,名为UserService(与动态代理的一样)
- 创建一个实现UserService接口的实现类,名为UserServiceImp(与动态代理的一样)
- 创建一个方面类,名为MyAspect
- 创建spring.xml并配置
- 测试
5.1、导jar包
Spring下载地址
commons logging下载地址
aopalliance下载地址
最后导入的包是如下(简称为:4+1、aop联盟):
5.2、创建接口
(与动态代理的一样)
5.3、创建目标类
(与动态代理的一样)
5.4、创建方面类
需要做两件事:
- 继承某个接口。
- 实现接口的方法。
所有的接口:
- 前置通知:接口MethodBeforeAdvice
- 后置通知:接口AfterReturningAdvice
- 环绕通知:接口MethodInterceptor(掌握)
- 异常通知:接口ThrowsAdvice
- 带返回值的后置通知:接口AfterReturningAdvice
package com.csa.aspect;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAspect implements MethodInterceptor{
/**
* 很明显,这是一个环绕通知
*/
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("之前");
Object obj = mi.proceed();
System.out.println("之后");
return obj;
}
}
5.5、配置spring.xml
注意,我的spring.xml在src的config包下:
spring.xml配置代码:
<?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"
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">
<!-- 1 创建目标类 -->
<bean id="userServiceId" class="com.csa.service.Imp.UserServiceImp"></bean>
<!-- 2 创建切面类 -->
<bean id="myAspectId" class="com.csa.aspect.MyAspect"></bean>
<!--
3 创建代理类
* 使用工厂bean FactoryBean ,底层调用 getObject() 返回特殊bean
* ProxyFactoryBean 用于创建代理工厂bean,生成特殊代理对象
interfaces : 确定接口
通过<array>可以设置多个值
只有一个值时,value=""
target : 确定目标类
interceptorNames : 通知 切面类的名称,类型String[],如果设置一个值 value=""
optimize :强制使用cglib
<property name="optimize" value="true"></property>
底层机制
如果目标类有接口,采用jdk动态代理
如果没有接口,采用cglib 字节码增强
如果声明 optimize = true ,无论是否有接口,都采用cglib
-->
<bean id="proxyServiceId" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces" value="com.csa.service.UserService"></property>
<property name="target" ref="userServiceId"></property>
<property name="interceptorNames" value="myAspectId"></property>
</bean>
</beans>
5.6、测试
package com.csa.Test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.csa.service.UserService;
public class TestAOP {
@Test
public void demo(){
String xmlPath = "config/spring.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserService userService = (UserService) applicationContext.getBean("proxyServiceId");
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
测试结果:
6、使用spring.xml实现AOP编程
下面的几个步骤:
- 在上面一个例子的基础上加一个包
- 创建接口,名为UserService(与动态代理的一样)
- 创建一个实现UserService接口的实现类,名为UserServiceImp(与动态代理的一样)
- 创建一个方面类,名为MyAspect(与上一个例子的一样)
- 创建spring.xml并配置
- 测试
6.1、在上面一个例子的基础上加一个包
AspectJ Weaver包下载地址
最后的包是:
6.2、创建接口
(与动态代理的一样)
6.3、创建实现类
(与动态代理的一样)
6.4、创建方面类
(与上一个例子的一样)
6.5、配置spring.xml
<?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"
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">
<!-- 1. 目标类 -->
<bean id="userServiceId" class="com.csa.service.Imp.UserServiceImp"></bean>
<!-- 2. 切面类 -->
<bean id="myAspectId" class="com.csa.aspect.MyAspect"></bean>
<!-- 3. aop编程 -->
<!--
简单来说呢,就是
连接点:是"所有需要增加某一个特定功能的方法集合"
织入:就是将某个有特定处理功能的类,加到"连接点"!
-->
<aop:config proxy-target-class="true">
<!-- 这里涉及得到一个表达式的问题,我给个超链接,自己看吧 -->
<!-- 下面是一个连接点,这里的表达式就是为了告诉spring,哪些方法需要被加以处理! -->
<aop:pointcut expression="execution(* com.csa.service..*(..))" id="myPointcut"/>
<!-- 下面就是我们的切面类,切面类会被"织入"到连接点 -->
<aop:advisor advice-ref="myAspectId" pointcut-ref="myPointcut"/>
</aop:config>
</beans>
6.6、测试
package com.csa.Test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.csa.service.UserService;
public class TestAOP {
@Test
public void demo(){
String xmlPath = "config/spring.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserService userService = (UserService) applicationContext.getBean("proxyServiceId");
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
测试结果:
7、使用AspectJ配置spring.xml实现AOP编程
分6步:
- 导包
- 创建接口
- 创建实现类
- 创建MyAspect
- 编写spring.xml
- 测试
7.1、导包
在上一个例子的基础上导入下面这个包!在spring的压缩包下可以找到!
7.2、创建接口
不变
7.3、创建实现类
要加一个除零异常
package com.csa.service.Imp;
import com.csa.service.UserService;
public class UserServiceImp implements UserService {
@Override
public void addUser() {
System.out.println("addUser to ...");
}
@Override
public void updateUser() {
int i = 1/0;
System.out.println("updateUser to ...");
}
@Override
public void deleteUser() {
System.out.println("deleteUser to ...");
}
}
7.4、创建MyAspect类
以下是所有AOP的通知了!
package com.csa.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
/**
* 前置
*
* @param joinPoint
* 连接点
*/
public void myBefore(JoinPoint joinPoint) {
System.out.println("前置通知: " + joinPoint.getSignature().getName());
}
/**
* 后置
*
* @param joinPoint
* 连接点
*/
public void myAfter(JoinPoint joinPoint) {
System.out.println("后置通知: " + joinPoint.getSignature().getName());
}
/**
* 环绕通知
*
* @param proceedingJoinPoint
* 连接点
* @return 返回被代理方法的具体返回值
* @throws Throwable
* proceedingJoinPoint.proceed()方法执行抛出的异常
*/
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("前:" + proceedingJoinPoint.getSignature().getName());
Object obj = proceedingJoinPoint.proceed();
System.out.println("后:" + proceedingJoinPoint.getSignature().getName());
return obj;
}
/**
* 异常通知
*
* @param joinPoint
* 连接点
* @param e
* 被代理方法执行出现异常
*/
public void myThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知:" + joinPoint.getSignature().getName() + "\r\n异常类型:" + e.getMessage());
}
/**
* 后置通知
*
* @param joinPoint
* 连接点
* @param ret被代理方法的具体返回值
*/
public void myReturning(JoinPoint joinPoint, Object ret) {
System.out.println("后置通知:" + joinPoint.getSignature().getName() + "\r\n返回值:" + ret);
}
}
7.5、编写spring.xml
<?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"
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">
<!-- 目标类 -->
<bean id="userServiceId" class="com.csa.service.Imp.UserServiceImp"></bean>
<!-- 切面类 -->
<bean id="myAspectId" class="com.csa.aspect.MyAspect"></bean>
<aop:config>
<!-- 注意是在这个标签下,直接引用了某个切面类 -->
<aop:aspect ref="myAspectId">
<!-- 在这个标签的体中间写"通知"和"被通知的方法集合" -->
<!--
连接点
-->
<aop:pointcut expression="execution(* com.csa.service..*(..) )" id="myPointcut"/>
<!--
前置通知
<aop:before method="myBefore" pointcut-ref="myPointcut"/>
-->
<!--
后置通知
<aop:after method="myAfter" pointcut-ref="myPointcut"/>
-->
<!--
环绕通知
<aop:around method="myAround" pointcut-ref="myPointcut"/>
-->
<!--
异常通知-->
<aop:after-throwing method="myThrowing" pointcut-ref="myPointcut" throwing="e"/>
<!--
后置通知
<aop:after-returning method="myReturning" pointcut-ref="myPointcut" returning="ret"/>
-->
<!-- jdk1.8 最好别使用 spring3.*.*系列 -->
</aop:aspect>
</aop:config>
</beans>
7.6、测试
package com.csa.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.csa.service.UserService;
public class TestAspectJ {
@Test
public void demo() {
String xmlPath = "config/spring.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserService userService = (UserService) applicationContext.getBean("userServiceId");
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
测试结果:
8、使用注解的AspectJ
分6个步骤:
- 导包
- 创建接口
- 创建实现类
- 创建MyAcpect
- 编写spring.xml
- 测试
8.1、导包
与上一个例子的包一样,没有改变!
8.2、创建接口
没变
8.3、创建实现类
与上一个AspectJ的例子一样!
8.4、创建MyAcpect
package com.csa.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAspect {
/**
* 连接点
*/
@Pointcut(value="execution(* com.csa.service..*(..) )")
private void myPointcut() {}
/**
* 前置
*
* @param joinPoint
* 连接点
*/
@Before(value="myPointcut()")
public void myBefore(JoinPoint joinPoint) {
System.out.println("前置通知: " + joinPoint.getSignature().getName());
}
/**
* 后置
*
* @param joinPoint
* 连接点
*/
@After(value="myPointcut()")
public void myAfter(JoinPoint joinPoint) {
System.out.println("后置通知: " + joinPoint.getSignature().getName());
}
/**
* 环绕通知
*
* @param proceedingJoinPoint
* 连接点
* @return 返回被代理方法的具体返回值
* @throws Throwable
* proceedingJoinPoint.proceed()方法执行抛出的异常
*/
@Around(value="myPointcut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("前:" + proceedingJoinPoint.getSignature().getName());
Object obj = proceedingJoinPoint.proceed();
System.out.println("后:" + proceedingJoinPoint.getSignature().getName());
return obj;
}
/**
* 异常通知
*
* @param joinPoint
* 连接点
* @param e
* 被代理方法执行出现异常
*/
@AfterThrowing(value="myPointcut()",throwing="e")
public void myThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知:" + joinPoint.getSignature().getName() + "\r\n异常类型:" + e.getMessage());
}
/**
* 后置通知
*
* @param joinPoint
* 连接点
* @param ret被代理方法的具体返回值
*/
@AfterReturning(value="myPointcut()",returning="ret")
public void myReturning(JoinPoint joinPoint, Object ret) {
System.out.println("后置通知:" + joinPoint.getSignature().getName() + "\r\n返回值:" + ret);
}
}
8.5、编写spring.xml
<?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"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 扫描 注解类 -->
<context:component-scan base-package="com.csa"></context:component-scan>
<!-- 确定 aop注解生效 -->
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
</beans>
8.6、测试
package com.csa.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.csa.service.UserService;
public class TestAspectJ {
@Test
public void demo() {
String xmlPath = "config/spring.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserService userService = (UserService) applicationContext.getBean("userServiceId");
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
测试结果: