AOP的概念
AOP–(Aspect Orient Programming):面向切面编程,是一种编程思想,是对OOP(面向切面编程)的补充。AOP将分散在各个不同业务逻辑中的相同代码抽取出来,重构成一个模块,通过切面的方式将这个模块织入到需要该模块的业务逻辑中。
《Spring实战(第4版)》书中有一张图就十分的形象解释了该思想
为什么需要AOP这种思想呢?
在实际的开发中,会对每个业务代码添加上日志,以便对业务代码的运行进行监控记录。这个时候我们就需要对每一个需要使用到日志的地方进行添加,如果只有一两个、三四个地方需要添加日志,那么手动添加没什么问题,但是上白上千呢?显然手动添加并不合适,这个时候就需要通过某种技术来进行简化,AOP就是通过横向切面的方式来对这些业务代码进行日志功能添加。
AOP的术语
- 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
- 连接点(Join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
- 切点(PointCut): 可以插入增强处理的连接点。使用一些表达式来表示要增强处理的连接点
- 切面(Aspect): 切面是通知和切点的结合。即Aspect=Advice+PointCut
- 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
- 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入
AspectJ和SpringAOP
AOP是一种编程思想,它是一个目标,具体的实现则是由SpringAOP或者AspectJ来进行实现
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。AspectJ采用静态代理方式进行织入。静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强。静态代理有三个织入时机:编译期(使用ajc编译)、后编译期(即生成class文件或者打成jar包之后织入)、类加载后织入(使用WeavingURLClassLoader类加载器)。因为在编译期间就已经确定了,所以性能好。AspectJ能够支持字段、方法、构造器等地方织入。
SpringAOP是Spring参考AspectJ所编写的AOP框架。它基于动态代理实现,如果使用的是接口则使用JDK动态代理技术来实现,如果是普通类则就通过CGLIB实现。它还需要依赖于SpringIOC进行管理,所以只能够对SpringBean来进行AOP。SpringAOP只能在运行时进行织入,所以在运行的时候动态生成代理实例导致性能较低。SpringAOP只能够支持方法的织入,功能较弱。
动态代理技术
什么是动态代理技术呢?动态代理技术就是创建目标对象的代理对象,对目标对象的方法进行一种功能性增强的技术。创建出来的代理对象并不是一开始就在class文件中生成的,而是在运行期间动态的生成,等到程序运行结束之后,也随之消失。因此使用动态代理技术时,你所得到的对象并不是原始对象,而是由动态代理代理出来的对象。该技术可以在不修改源码的情况下,对原有代码中的方法进行一些功能性增强,降低了代码的耦合。
普通对象创建过程
通过动态代理创建对象的过程
实现动态代理的两种方法
第一个JDK动态代理,用于实现接口的动态代理。JDK动态代理是Java自带的动态代理技术,主要涉及到java.lang.reflect包下边的两个类:Proxy和InvocationHandler。可以通过实现InvocationHandler接口来定义横切逻辑,并通过反射机制调用目标类的代码,动态地将业务代码逻辑和横切逻辑组织在一起。
第二个为CGLIB(Code Generation Library),用于实现普通类的动态代理。它是基于ASM的字节码生成库,通过继承的方式来进行动态代理,在子类采用方法拦截的技术拦截所有的父类方法的调用并顺势织入横切逻辑。由于CGLIB是通过继承的方式来进行动态代理的,所以被代理的类不能够被final修饰
接下来通过代码来对这两种技术进一步的理解。
JDK动态代理
//定义接口
public interface UserService {
public void getone();
public void gettwo();
}
//实现该接口
public class UserServiceImpl implements UserService {
@Override
public void getone() {
System.out.println("我是第一个");
}
@Override
public void gettwo() {
System.out.println("我是第二个");
}
}
//实现InvocationHandler接口。
//InvocationHandler接口就是通过反射的机制来加载接口实现类的方法,从而对代理对象的方法进行横切逻辑的织入
public class ServiceProxy implements InvocationHandler {
public Object target;
public ServiceProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("????");
Object invoke = method.invoke(target, args);
System.out.println("!!!!");
return invoke;
}
}
//测试类
public class ProxyTest {
public static void main(String[] args) {
UserService userService=new UserServiceImpl();
ServiceProxy serviceProxy=new ServiceProxy(userService);
UserService userService1= (UserService)Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserService.class}, serviceProxy);
userService1.getone();
userService1.gettwo();
}
}
/*
结果:
???
我是第一个
!!!!
???
我是第二个
!!!!
*/
复制代码
通过上面的代码发现,实现JDK动态代理主要依赖于Proxy类下的newProxyInstance()方法,该方法有三个参数,分别是:ClassLoader loader、Class<?>[] interfaces、InvocationHandler h。
loader:类加载器,用于加载接口
interfaces:要进行动态代理的接口class数组
InvocationHandler:调用处理器。代理对象的方法被调用的时候就会执行该参数中的invoke方法。会对所有的代理对象中的方法进行拦截。
CGLIB动态代理
实现该功能首先需要导入两个jar包,分别是asm和cglib
//需要实现代理的普通类
public class OtherClass {
public void say(){
System.out.println("我正在处理业务");
}
}
//测试类
public class CGLibTest {
public static void main(String[] args) {
OtherClass otherClass=new OtherClass();
OtherClass o = (OtherClass)Enhancer.create(otherClass.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("实现CGLIB动态代理");
Object invoke = method.invoke(otherClass, objects);
return invoke;
}
});
o.say();
}
}
/*
结果
实现CGLIB动态代理
我正在处理业务
*/
复制代码
通过上面的代码得知,通过Enhancer类下的create方法进行动态代理。该方法中有两个参数Class type、Callback callback。
type:需要实现动态代理的类
Callback:一个接口,可以理解成生成的代理类的方法被调用时会执行的逻辑。
它的实现类有以下几种
MethodInterceptor
NoOp
LazyLoader
Dispatcher
InvocationHandler
FixedValue
想要深入了解的可以自行实现。
SpringAOP代码实现
SpringAOP有两种方式可以进行代码实现,一种是XML文件,一直就是基于注解的方式。因为只要学会了XML方式的编写,注解方式就很简单了,所以接下来都会通过XML文件进行代码的编写。
代码编写前需要引入
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
复制代码
首先开启AOP的支持,这也有两种方式,一是通过aop:aspectj-autoproxy/标签进行开启,一种是通过注解@EnableAspectJAutoProxy方式进行开启。
接下来进行一个简单的测试
//定义接口
public interface UserService {
public void info();
}
//定义接口的实现类
public class UserServiceImpl implements UserService{
@Override
public void info() {
System.out.println("Info");
}
}
//定义切面
public class AJOne {
public void say(){
System.out.println("我在第一位哦");
}
}
复制代码
<!--进行xml代码的编写-->
<!--开启AOP支持-->
<aop:aspectj-autoproxy/>
<!--定义切面类-->
<bean id="Ajone" class="com.itheima.aop.AJOne"/>
<!--对接口的实现类进行IOC。前面说过SpringAOP代理的对象必须是SpringBean,所以要先注册Bean-->
<bean id="userService" class="com.itheima.service.UserServiceImpl"/>
<!--进行AOP的配置-->
<aop:config>
<!--配置AOP的pointcut编写,用于查找需要进行代理的方法-->
<aop:pointcut id="pointcutOne" expression="execution(* com.itheima.service.UserServiceImpl.info(..))"/>
<!--声明切面类-->
<aop:aspect ref="Ajone">
<!--进行Advice通知的配置,此处用了前置通知-->
<aop:before method="say" pointcut-ref="pointcutOne"></aop:before>
</aop:aspect>
</aop:config>
复制代码
//编写测试类
public class Test01 {
public static void main(String[] args) {
ClassPathXmlApplicationContext cp = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
UserService userService = cp.getBean("userService", UserService.class);
userService.info();
}
}
/*
结果:
我在第一位哦
Info
*/
复制代码
既然SpringAOP是通过JDK代理或者CGLIB代理来实现的,那么默认的是使用的什么呢?
通过DEBUG来进行解答
发现默认使用的是JDK代理技术,那么怎么修改成CGLIB呢?可以通过aop:config标签中的proxy-target-class属性进行修改,将其设置为true,就变成了CGLIB实现。
以上就是一个SpringAOP最简单的实现,接下来将会对这里面的一些功能进行仔细的讲解。
Advice-通知
前面说过,通知描述了切面何时执行以及如何执行增强处理。在SpringAOP中通知有五种,分别是前置通知(Before)、后置通知(AfterReturning)、环绕通知(Around)、异常通知(AfterThrowing)、最终通知(After)。
通过一段代码来对这五个通知的位置和这五个通知的执行顺序进行描述
try{
AroundBefore();//环绕通知头
Before();//前置通知
method();//业务代码
AroundAfter();//环绕通知尾
After();//后置通知
}catch(Throwable t){
AfterThrowing();//异常通知
}finally{
AfterReturning();//最终通知
}
复制代码
在进行代码实现之前,首先接受两个类,一个是JoinPoint,另一个就是JoinPoint的子类ProceedingJoinPoint。
JoinPoint封装了SpringAop中的切面信息,在切面方法中添加该参数,可以获取封装了该方法的信息的JoinPoint对象.
JoinPoint中的方法如下
方法名 | 功能 |
---|---|
Signature getSignature(); | 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息 |
Object[] getArgs(); | 获取传入目标方法的参数对象 |
Object getTarget(); | 获取被代理的对象 |
Object getThis(); | 获取代理对象 |
因为ProceedingJoinPoint是JoinPoint的子类,所以它可以使用JoinPoint的所有方法,它还添加了两个方法分别是
Object proceed() throws Throwable //执行目标方法
Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法
复制代码
ProceedingJoinPoint只能够在环绕通知的时候进行使用
接下来分别对这五个通知进行代码实现
前置通知(Before)
在方法实现前进行织入
//定义接口
public interface UserService {
public void info();
}
//定义接口的实现类
public class UserServiceImpl implements UserService{
@Override
public void info() {
System.out.println("Info");
}
}
//定义切面
public class AJOne {
public void before(JoinPoint joinPoint){
System.out.println("获取代理对象的方法名"+joinPoint.getSignature().getName());
System.out.println("获取代理对象中方法的参数"+ Arrays.toString(joinPoint.getArgs()));
System.out.println("获取被代理的对象"+joinPoint.getTarget().toString());
System.out.println("获取代理对象"+joinPoint.getThis().toString());
System.out.println("前置通知");
}
}
复制代码
<bean id="Ajone" class="com.itheima.aop.AJOne"/>
<bean id="userService" class="com.itheima.service.UserServiceImpl"/>
<aop:config>
<aop:pointcut id="pointcutOne" expression="execution(* com.itheima.service.UserServiceImpl.info(..))"/>
<aop:aspect ref="Ajone">
<aop:before method="before" pointcut-ref="pointcutOne"></aop:before>
</aop:aspect>
</aop:config>
复制代码
//测试类
public class Test02 {
public static void main(String[] args) {
ClassPathXmlApplicationContext cp = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
UserService userService = cp.getBean("userService", UserService.class);
userService.info();
}
}
/*
结果:
获取代理对象的方法名info
获取代理对象中方法的参数[]
获取被代理的对象com.itheima.service.UserServiceImpl@20d28811
获取代理对象com.itheima.service.UserServiceImpl@20d28811
前置通知
Info
*/
复制代码
后置通知(AfterReturning)
在方法实现之后进行织入,该通知可以获取到方法的返回值
//定义接口
public interface UserService {
public int add(int a,int b);
}
//定义接口的实现类
public class UserServiceImpl implements UserService{
@Override
public int add(int a,int b) {
return a+b;
}
}
//定义切面
public class AJOne {
public void afterreturning(Object val){
System.out.println("后置通知获取到的方法返回值为"+val);
}
}
复制代码
<bean id="Ajone" class="com.itheima.aop.AJOne"/>
<bean id="userService" class="com.itheima.service.UserServiceImpl"/>
<aop:config>
<aop:pointcut id="pointcutTwo" expression="execution(* com.itheima.service.UserServiceImpl.add(..))"/>
<aop:aspect ref="Ajone">
<!--returning属性就是将返回值赋值到方法参数中-->
<aop:after-returning method="afterreturning" pointcut-ref="pointcutTwo" returning="val"/>
</aop:aspect>
</aop:config>
复制代码
//测试类
public class Test03 {
public static void main(String[] args) {
ClassPathXmlApplicationContext cp = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
UserService userService = cp.getBean("userService", UserService.class);
int i = userService.add(5, 2);
System.out.println(i);
}
}
/*
结果:
后置通知获取到的方法返回值为7
7
*/
复制代码
环绕通知(Around)
在方法实现的前后进行织入,方法的实现依赖于proceedingJoinPoint中的proceed()方法
//定义接口
public interface UserService {
public int add(int a,int b);
}
//定义接口的实现类
public class UserServiceImpl implements UserService{
@Override
public int add(int a,int b) {
return a+b;
}
}
//定义切面
public class AJOne {
public int around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕前置通知");
Integer proceed = (Integer)proceedingJoinPoint.proceed();//环绕通知必须要使用proceed方法,目标对象的方法才能够使用,我们可以获取到目标对象的方法的返回值,对其进行修改
System.out.println(proceed);
System.out.println("环绕后置通知");
return 10;
}
}
复制代码
<bean id="Ajone" class="com.itheima.aop.AJOne"/>
<bean id="userService" class="com.itheima.service.UserServiceImpl"/>
<aop:config>
<aop:pointcut id="pointcutThree" expression="execution(* com.itheima.service.UserServiceImpl.add(..))"/>
<aop:aspect ref="Ajone">
<aop:around method="around" pointcut-ref="pointcutThree" />
</aop:aspect>
</aop:config>
复制代码
//测试类
public class Test04 {
public static void main(String[] args) {
ClassPathXmlApplicationContext cp = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
UserService userService = cp.getBean("userService", UserService.class);
int i = userService.add(5, 2);
System.out.println(i);
}
}
/*
结果:
环绕前置通知
7
环绕后置通知
10
*/
复制代码
通过结果发现,我们确实去修改代理对象的方法返回值。
异常通知(AfterThrowing)
在代理对象的方法抛出异常的时候执行
//定义接口
public interface UserService {
public int division(int a,int b);
}
public class UserServiceImpl implements UserService{
@Override
public int division(int a, int b) {
return a/b;
}
}
//定义切面
public class AJOne {
public void throwing(Throwable t){
System.out.println("异常通知");
System.out.println(t.getMessage());
}
}
复制代码
<bean id="Ajone" class="com.itheima.aop.AJOne"/>
<bean id="userService" class="com.itheima.service.UserServiceImpl"/>
<aop:config>
<aop:pointcut id="pointcutFour"expression="execution(*com.itheima.service.UserServiceImpl.division(..))"/>
<aop:aspect ref="Ajone">
<!--throwing属性为方法中的异常类的参数名-->
<aop:after-throwing method="throwing" pointcut-ref="pointcutFour" throwing="t"/>
</aop:aspect>
</aop:config>
复制代码
//测试类
public class Test05 {
public static void main(String[] args) {
ClassPathXmlApplicationContext cp = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
UserService userService = cp.getBean("userService", UserService.class);
int i = userService.division(5, 0);
System.out.println(i);
}
}
/*
结果:
异常通知
/ by zero
*/
复制代码
最终通知(After)
无论代码是否有异常都会执行
//定义接口
public interface UserService {
public int division(int a,int b);
}
public class UserServiceImpl implements UserService{
@Override
public int division(int a, int b) {
return a/b;
}
}
//定义切面
public class AJOne {
public void after(){
System.out.println("最终通知");
}
}
复制代码
<bean id="Ajone" class="com.itheima.aop.AJOne"/>
<bean id="userService" class="com.itheima.service.UserServiceImpl"
<aop:config>
<aop:pointcut id="pointcutFive" expression="execution(* com.itheima.service.UserServiceImpl.division(..))"/>
<aop:aspect ref="Ajone">
<aop:after method="after" pointcut-ref="pointcutFive"/>
</aop:aspect>
</aop:config>
复制代码
//测试类
public class Test06 {
public static void main(String[] args) {
ClassPathXmlApplicationContext cp = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
UserService userService = cp.getBean("userService", UserService.class);
int i = userService.division(5, 0);
System.out.println(i);
}
}
/*
结果:
最终通知
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.itheima.service.UserServiceImpl.division(UserServiceImpl.java:22)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:49)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at com.sun.proxy.$Proxy4.division(Unknown Source)
at com.itheima.test.Test06.main(Test06.java:14)
Process finished with exit code 1
*/
复制代码
通过结果发现即使有异常最终通知也会执行,所以最终通知一般用于进行连接的关闭或者资源的清除等任务.
Pointcut-切入点
可以插入增强处理的连接点。使用一些表达式来表示要增强处理的连接点。表达式有以下这几种
execution():用于匹配是连接点的执行方法
arg():限制来连接点匹配参数为指定类型的执行方法
@args():限制连接点匹配参数由指定注解标注的执行方法
target():限定连接点匹配目标对象为指定类型的类
@target():限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型的注解
within():限制连接点匹配指定的类型
@within():限制连接点匹配指定注解所标注的类型
this:限制连接点匹配AOP代理的Bean引用为指定类型的类
@annotation:限制匹配带有指定注解连接点
这几种表达式可以进行与或非逻辑运算,在xml由于&&、||、!有特殊的含义,所以在xml文件中使用and、or、not来代替
由于execution()表达式情况比较多,所以对其他的表达式的用法进行讲解,最后再讲解execution()表达式
arg()
//定义接口
public interface UserService {
public void info(String s1,String s2);
public void info(int num);
}
//定义实现类
public class UserServiceImpl implements UserService{
@Override
public void info(String s1, String s2) {
System.out.println(s1+"=="+s2);
}
@Override
public void info(int num) {
System.out.println(num);
}
}
//定义切面类
public class BeforeAdvice {
public void problem(){
System.out.println("前置通知");
}
}
复制代码
<bean id="beforeAdvice" class="com.itheima.AJ.BeforeAdvice"/>
<bean id="userService" class="com.itheima.service.UserServiceImpl"/>
<aop:config>
<!--是用arg表达式,用于匹配方法参数为两个String的方法-->
<aop:pointcut id="argTest" expression="args(java.lang.String,java.lang.String)"/>
<aop:aspect ref="beforeAdvice">
<aop:before method="problem" pointcut-ref="argTest"/>
</aop:aspect>
</aop:config>
复制代码
//测试类
public class Test07 {
public static void main(String[] args) {
ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
UserService userService = cc.getBean("userService", UserService.class);
userService.info("One","Two");
userService.info(50);
}
}
/*
结果:
前置通知
One==Two
50
*/
复制代码
通过结果发现,只对符合arg表达式的连接点进行切入。
@args()
@args表达式只接收一个参数,该参数必须是对象并且必须要有被@args表达式中定义的注解,才能够被切入
由于表达式需要使用到注解,所以先自定义一个注解,也可以用别人的注解
//注解,由于参数只能是对象,所以作用于类上
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Args {
}
//定义使用了自定义注解的类
@Args
public class Car {
@Override
public String toString() {
return "car!!!";
}
}
//定义接口
public interface UserService {
public void info(String s1, String s2);
public void info(int num);
public void info(Car car);
}
//定义接口实现类
public class UserServiceImpl implements UserService{
@Override
public void info(String s1, String s2) {
System.out.println(s1+"=="+s2);
}
@Override
public void info(int num) {
System.out.println(num);
}
@Override
public void info(Car car) {
System.out.println(car);
}
}
//定义切面
public class BeforeAdvice {
public void problem(){
System.out.println("前置通知");
}
}
复制代码
<bean id="beforeAdvice" class="com.itheima.AJ.BeforeAdvice"/>
<bean id="userService" class="com.itheima.service.UserServiceImpl"/>
<aop:config>
<!--该表达式表示只对有且只有一个被Args注解注解的类对象的连接点进行切入-->
<aop:pointcut id="Args" expression="@args(com.itheima.annotated.Args)"/>
<aop:aspect ref="beforeAdvice">
<aop:before method="problem" pointcut-ref="Args"/>
</aop:aspect>
</aop:config>
复制代码
//测试类
public class Test08 {
public static void main(String[] args) {
ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
UserService userService = cc.getBean("userService", UserService.class);
userService.info("One","Two");
userService.info(50);
userService.info(new Car());
}
}
/*
结果:
One==Two
50
前置通知
car!!!
*/
复制代码
target()
//定义两个接口,用于比较
public interface PeopleService {
public void say(int num);
}
public interface UserService {
public void info(String s1, String s2);
public void info(int num);
public void info(Car car);
}
//接口实现类
public class PeopleServiceImpl implements PeopleService{
@Override
public void say(int num) {
System.out.println(num);
}
}
public class UserServiceImpl implements UserService{
@Override
public void info(String s1, String s2) {
System.out.println(s1+"=="+s2);
}
@Override
public void info(int num) {
System.out.println(num);
}
@Override
public void info(Car car) {
System.out.println(car);
}
}
//切面类
public class BeforeAdvice {
public void problem(){
System.out.println("前置通知");
}
}
复制代码
<bean id="beforeAdvice" class="com.itheima.AJ.BeforeAdvice"/>
<bean id="userService" class="com.itheima.service.UserServiceImpl"/>
<bean id="peopleService" class="com.itheima.service.PeopleServiceImpl"/>
<aop:config>
<!--target中的全限定名称可以是接口也可以是接口的实现类-->
<aop:pointcut id="target" expression="target(com.itheima.service.UserService)"/>
<aop:aspect ref="beforeAdvice">
<aop:before method="problem" pointcut-ref="target"/>
</aop:aspect>
</aop:config>
复制代码
//测试类
public class Tets09 {
public static void main(String[] args) {
ClassPathXmlApplicationContext cp = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
UserService userService = cp.getBean("userService", UserService.class);
PeopleService peopleService = cp.getBean("peopleService", PeopleService.class);
peopleService.say(20);
System.out.println("===============");
userService.info(20);
userService.info(new Car());
userService.info("first","second");
}
}
/*
结果:
20
===============
前置通知
20
前置通知
car!!!
前置通知
first==second
*/
复制代码
通过结果返现,target表达式只对指定的类进行织入,没有指定的类没有进行织入。
@target
该表达式用于对使用了指定注解的类进行织入,首先自定义一个注解
//自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Targets {
}
//定义两个接口,用于比较
public interface PeopleService {
public void say(int num);
}
public interface UserService {
public void info(String s1, String s2);
public void info(int num);
public void info(Car car);
}
//接口实现类
@Targets
public class PeopleServiceImpl implements PeopleService{
@Override
public void say(int num) {
System.out.println(num);
}
}
public class UserServiceImpl implements UserService{
@Override
public void info(String s1, String s2) {
System.out.println(s1+"=="+s2);
}
@Override
public void info(int num) {
System.out.println(num);
}
@Override
public void info(Car car) {
System.out.println(car);
}
}
//切面类
public class BeforeAdvice {
public void problem(){
System.out.println("前置通知");
}
}
复制代码
<bean id="beforeAdvice" class="com.itheima.AJ.BeforeAdvice"/>
<bean id="userService" class="com.itheima.service.UserServiceImpl"/>
<bean id="peopleService" class="com.itheima.service.PeopleServiceImpl"/>
<aop:config>
<!--@taget指定的注解只能作用于接口的实现类,作用月注解无效-->
<aop:pointcut id="targets" expression="@target(com.itheima.annotated.Targets)"/>
<aop:aspect ref="beforeAdvice">
<aop:before method="problem" pointcut-ref="targets"/>
</aop:aspect>
</aop:config>
复制代码
//测试类
public class Tets10 {
public static void main(String[] args) {
ClassPathXmlApplicationContext cp = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
UserService userService = cp.getBean("userService", UserService.class);
PeopleService peopleService = cp.getBean("peopleService", PeopleService.class);
peopleService.say(20);
System.out.println("===============");
userService.info(20);
userService.info(new Car());
userService.info("first","second");
}
}
/*
结果:
前置通知
20
===============
20
car!!!
first==second
*/
复制代码
within()
该表达式也是作用于类上,但是范围比target更广,within可以对某个包下的所有类进行织入,而target只对某个具体类进行织入
//定义两个接口,在一个包下
public interface PeopleService {
public void say(int num);
}
public interface UserService {
public void info(String s1, String s2);
public void info(int num);
public void info(Car car);
}
//两个接口的实现类
public class PeopleServiceImpl implements PeopleService{
@Override
public void say(int num) {
System.out.println(num);
}
}
public class UserServiceImpl implements UserService{
@Override
public void info(String s1, String s2) {
System.out.println(s1+"=="+s2);
}
@Override
public void info(int num) {
System.out.println(num);
}
@Override
public void info(Car car) {
System.out.println(car);
}
}
//切面类
public class BeforeAdvice {
public void problem(){
System.out.println("前置通知");
}
}
复制代码
<bean id="beforeAdvice" class="com.itheima.AJ.BeforeAdvice"/>
<bean id="userService" class="com.itheima.service.UserServiceImpl"/>
<bean id="peopleService" class="com.itheima.service.PeopleServiceImpl"/>
<aop:config>
<!--该表达式表示会对com.itheima.service包下的所有类进行织入,但不包括子类,要包括子类的话需要写成com.itheima.service..*-->
<aop:pointcut id="within" expression="within(com.itheima.service.*)"/>
<aop:aspect ref="beforeAdvice">
<aop:before method="problem" pointcut-ref="within"/>
</aop:aspect>
</aop:config>
复制代码
//测试
public class Test11 {
public static void main(String[] args) {
ClassPathXmlApplicationContext cp = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
UserService userService = cp.getBean("userService", UserService.class);
PeopleService peopleService = cp.getBean("peopleService", PeopleService.class);
peopleService.say(20);
System.out.println("===============");
userService.info(20);
userService.info(new Car());
userService.info("first","second");
}
}
/*
结果:
前置通知
20
===============
前置通知
20
前置通知
car!!!
前置通知
first==second
*/
复制代码
@within()
//自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Within {
}
//定义两个接口,在一个包下
public interface PeopleService {
public void say(int num);
}
public interface UserService {
public void info(String s1, String s2);
public void info(int num);
public void info(Car car);
}
//两个接口的实现类
public class PeopleServiceImpl implements PeopleService{
@Override
public void say(int num) {
System.out.println(num);
}
}
public class UserServiceImpl implements UserService{
@Override
public void info(String s1, String s2) {
System.out.println(s1+"=="+s2);
}
@Override
public void info(int num) {
System.out.println(num);
}
@Override
public void info(Car car) {
System.out.println(car);
}
}
//切面类
public class BeforeAdvice {
public void problem(){
System.out.println("前置通知");
}
}
复制代码
<bean id="beforeAdvice" class="com.itheima.AJ.BeforeAdvice"/>
<bean id="userService" class="com.itheima.service.UserServiceImpl"/>
<bean id="peopleService" class="com.itheima.service.PeopleServiceImpl"/>
<aop:config>
<aop:pointcut id="within" expression="@within(com.itheima.annotated.Within)"/>
<aop:aspect ref="beforeAdvice">
<aop:before method="problem" pointcut-ref="within"/>
</aop:aspect>
</aop:config>
复制代码
//测试类
public class Test12{
public static void main(String[] args) {
ClassPathXmlApplicationContext cp = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
UserService userService = cp.getBean("userService", UserService.class);
PeopleService peopleService = cp.getBean("peopleService", PeopleService.class);
peopleService.say(20);
System.out.println("===============");
userService.info(20);
userService.info(new Car());
userService.info("first","second");
}
}
/*
结果:
前置通知
20
===============
20
car!!!
first==second
*/
复制代码
到这里会发现@target和@within好像没有什么区别,都是寻找含有指定注解的类进行织入,但是其实还有区别的,我们通过代码来找区别!
@target和@within的区别
//定义两个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Targets {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Within {
}
//定义一个父类,两个子类
@Within
@Targets
public class Man {
public void speak(){
System.out.println("Manspeak!!!");
}
public void say(){
System.out.println("say!!!");
}
}
public class NoMan extends Man{
@Override
public void say() {
System.out.println("NoManSay");
}
}
public class Human extends Man{
@Override
public void speak() {
System.out.println("Humanspeak");
}
}
//定义一个切面
public class BeforeAdvice {
public void target(){
System.out.println("使用@target");
}
public void within(){
System.out.println("使用@with");
}
}
复制代码
<bean id="huMan" class="com.itheima.service.Human"/>
<bean id="man" class="com.itheima.service.Man"/>
<bean id="noMan" class="com.itheima.service.NoMan"/>
<aop:config>
<aop:pointcut id="targets" expression="@target(com.itheima.annotated.Targets)"/>
<aop:pointcut id="within" expression="@within(com.itheima.annotated.Within)"/>
<aop:aspect ref="beforeAdvice">
<aop:before method="target" pointcut-ref="targets"/>
<aop:before method="within" pointcut-ref="within"/>
</aop:aspect>
</aop:config>
复制代码
//测试类
public class Test13 {
public static void main(String[] args) {
ClassPathXmlApplicationContext cp = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
Man man = cp.getBean("man", Man.class);
NoMan noMan = cp.getBean("noMan", NoMan.class);
HuMan huMan = cp.getBean("huMan", Human.class);
man.say();
man.speak();
System.out.println("===================");
huMan.say();
huMan.speak();
System.out.println("==============");
noMan.say();
noMan.speak();
}
}
/*
结果:
使用@target
使用@with
say!!!
使用@target
使用@with
Manspeak!!!
===================
使用@with
say!!!
Humanspeak
==============
NoManSay
使用@with
Manspeak!!!
*/
复制代码
通过上面的代码以及测试,发现@targe只会对被指定注解注解的类进行织入,而@within会对被指定注解注解的织入之外,它的子类也能够被织入,只不过它只能够织入那些没有被重写的方法。
this()
//定义两个接口,在一个包下
public interface PeopleService {
public void say(int num);
}
public interface UserService {
public void info(String s1, String s2);
public void info(int num);
public void info(Car car);
}
//两个接口的实现类
public class PeopleServiceImpl implements PeopleService{
@Override
public void say(int num) {
System.out.println(num);
}
}
public class UserServiceImpl implements UserService{
@Override
public void info(String s1, String s2) {
System.out.println(s1+"=="+s2);
}
@Override
public void info(int num) {
System.out.println(num);
}
@Override
public void info(Car car) {
System.out.println(car);
}
}
//切面类
public class BeforeAdvice {
public void problem(){
System.out.println("前置通知");
}
}
复制代码
<bean id="beforeAdvice" class="com.itheima.AJ.BeforeAdvice"/>
<bean id="userService" class="com.itheima.service.UserServiceImpl"/>
<bean id="peopleService" class="com.itheima.service.PeopleServiceImpl"/>
<aop:config>
<!--如果该表达式是一个接口,那么就会对该接口的实现类进行织入,但是该接口只能有一个实现类。如果是一个类的话,就会对该类进行织入,使用CGLIB-->
<aop:pointcut id="this" expression="this(com.itheima.service.UserService)"/>
<aop:aspect ref="beforeAdvice">
<aop:before method="problem" pointcut-ref="this"/>
</aop:aspect>
</aop:config>
复制代码
@annotation()
//自定义一个注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Method {
}
//定义一个接口
public interface UserService {
public void info(String s1, String s2);
public void info( int num);
public void info(Car car);
}
//定义接口实现类
public class UserServiceImpl implements UserService{
@Override
public void info(String s1, String s2) {
System.out.println(s1+"=="+s2);
}
@Override
@Method
public void info(int num) {
System.out.println(num);
}
@Override
public void info(Car car) {
System.out.println(car);
}
}
//定义切面类
public class BeforeAdvice {
public void problem(){
System.out.println("前置通知");
}
}
复制代码
<bean id="beforeAdvice" class="com.itheima.AJ.BeforeAdvice"/>
<bean id="userService" class="com.itheima.service.UserServiceImpl"/>
<aop:config>
<!--对方法上含有指定注解的进行织入-->
<aop:pointcut id="annotation" expression="@annotation(com.itheima.annotated.Method)"/>
<aop:aspect ref="beforeAdvice">
<aop:before method="problem" pointcut-ref="annotation"/>
</aop:aspect>
</aop:config>
复制代码
//测试类
public class Test16 {
public static void main(String[] args) {
ClassPathXmlApplicationContext cp = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
UserService userService = cp.getBean("userService", UserService.class);
userService.info(20);
userService.info(new Car());
userService.info("first","second");
}
}
/*
结果:
前置通知
20
car!!!
first==second
*/
复制代码
execution()
该表达式是最常用的一种表达式,它有着许多的织入方式,对它需要着重的进行学习。
“execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)“,这个就是execution表达式的公式,它由许多的部分组成,接下来对它依次进行解释
modifiers-pattern?:修饰符匹配,用于匹配指定的修饰符连接点,?表达式可用可无,若不写,则会对所有的修饰符进行匹配,*** **也表示对所有的修饰符匹配
ret-type-pattern:返回值匹配,用于匹配指定的返回值类型连接点,该匹配必须要存在,不存在报错,可以用 ***** 表示任意的返回值
declaring-type-pattern?:声明类型匹配,用于匹配指定包名下类的连接点,该匹配也可以不存在,不存在的话就回去匹配所有包下的所有类下的连接点
name-pattern:名字匹配,用于匹配指定名称的连接点,该匹配必须存在,不存在报错。可以用*表示任意名称
param-pattern:参数匹配,用于匹配指定参数的连接点,该匹配必须存在,不存在报错。可以用 .. 表示任意多任意类型的参数,***** 表示一个任意类型的参数
throws-pattern:异常匹配,用于匹配指定异常的连接点,该匹配可有可无,不怎么使用
由于execution()表达式有太多种情况,所以就不进行代码的编写,直接对表达式的内容进行讲解分析
execution(* *(..))
复制代码
该表达式表示匹配任意修饰符任意返回值类型任意包名下所有类中任意名称的任意参数的连接点。第一个表示返回值类型,第二个表示任意名称,因为修饰符和包可以不写,并且不写的话表示任意,所以这个表达式就可以匹配所有的连接点。
execution(public * *(..))
复制代码
该表达式表示匹配public修饰符任意返回值类型任意包名下所有类中任意名称的任意参数的连接点。
execution(public int *(..))
复制代码
该表达式表示匹配public修饰符返回值类型为int的任意包名下所有类中任意名称的任意参数的连接点。
execution(* com.itheima.service.UserService.*(..))
复制代码
该表达式表示匹配com.itheima.service.UserService类下的所有方法
execution(* com.itheima.service.*.*(..))
复制代码
该表达式表示匹配com.itheima.service包下所有类中的连接点
execution(* *.service.*.*(..))
复制代码
该表达式表示匹配com.service/cn.service这种包下所有类
execution(* *..service.*.*(..))
复制代码
该表达式表示匹配的service包下的所有类中的连接点
execution(* *..service..*(..))
复制代码
该表达式表示匹配service包下及其子包下的所有类中的连接点
execution(* com.itheima.service.*.say(..))
复制代码
该表达式表示匹配com.itheima.service包下所有类中名称为say的连接点
execution(* com.itheima.service.*.*(String))
复制代码
该表达式表示匹配com.itheima.service包下所有类中方法参数类型为String的连接点
execution(* com.itheima.service.*.*(String,*))
复制代码
该表达式表示匹配com.itheima.service包下所有类中方法参数类型第一个为String,第二个为任意类型的连接点
execution(* com.itheima.service.*.*(String,..))
复制代码
该表达式表示匹配com.itheima.service包下所有类中方法参数类型第一个为String,后面参数为任意多个任意类型的连接点.
其实可以发现execution是within、target这些的一个综合,within、target可以表示的execution也都可以表示
executio()表达式还有很多种写法,可以自己再去进行探索。
总结:
1、AOP是一种编程的思想,是对OOP的一种补充.主要应用于处理一些具有横切性质的系统级服务,如日志收集、事务管理、安全检查、缓存、对象池管理等
2、实现AOP思想有两种方法,一种是AspectJ,一种是SpringAOP。这两种都是基于代理,AspectJ是基于静态代理,SpringAOP是基于动态代理
3、SpringAOP的动态代理分为两种,一种是基于接口,一种是基于类
4、因为SpringAOP是基于动态代理,所以在运行的时候会对性能有一定的影响
5、切面是SpringAOP很重要的一个方面,切面是由pointcut和advice组成,需要掌握这两个知识点