一、AOP中的术语简介
说明:
1.这里我们拿安全性检查来进行说明。对于安全性检查来说,这和我们真正的业务是没有任何关系的,只是我们将其横向插入进去,在spring中这叫横切性关注点(cross cutting concern)。
2.对于横切性关注点来说,我们可以实现各种模块化的类,即切面类(aspect),而我们实现安全性检查的类SecurityHandler类就叫Advice。同时Advice类根据其切入点的不同可以分为Before Advice、After Advice。
3.所谓切入点(pointcut),表示advice能应用咋什么地方,即应用在哪些jointpoint,在spring中指能应用在哪些方法上,因为方法只支持方法的连接点(jointpoint),而应用的过程叫植入(weave)。
4.另外两个术语proxy表示代理,introduction表示动态的给一个类加上一些方法。
二、spring对AOP的支持(采用注解方式)(工程spring_aop_1
)
1.依赖库
这里需要在之前依赖包基础上再加上一些依赖包SPRING_HOME/lib/aspectj/*.jar
,同时注意:如果JDK版本较高,则低版本的aspectj包可能会出现问题,此时请使用目录中1.8版本的aspectj包。2.采用aspect注解定义切面;
3.在aspect定义pointcut和advice。
4.启用aspect对注解的支持,同时将切面和目标对象配置到IOC容器中。
动态代理类:SecurityHandler.java
package com.bjsxt.spring;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* 定义Aspect
*/
@Aspect
public class SecurityHandler {
/*下面的注解表示应用范围,使用Pointcut注解,而其名称是allAddMethod,
不能有返回值和参数,该方法只是一个Pointcut标识,没有其他意义。
*Pointcut的内容是一个表达式,描述应用到哪些方法上(Joinpoint),括
号里面第一个*号表示
*返回值(即返回值可有可没有),而add*表示所有的add方法,(..)表示
任意参数。
*/
@Pointcut("execution(* add*(..)) || execution(* del*(..))")
private void allAddMethod(){};
/*检查安全性,注解表示在方法之前调用,而方法使用allAddMethod()标识
*定义Advice,标识在哪个切入点植入此方法
* */
@Before("allAddMethod()")
private void checkSecurity() {
System.out.println("----------checkSecurity()---------------");
}
}
说明:这里一定要注意,切入点方法是不会执行的,存在的目的仅仅是作为一个标识,我们可以在advice中通过此方法名引用此切入点。
配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<aop:aspectj-autoproxy/>
<bean id="securityHandler" class="com.bjsxt.spring.SecurityHandler"/>
<bean id="userManager" class="com.bjsxt.spring.UserManagerImpl"/>
</beans>
测试:Client.java
package com.bjsxt.spring;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
public static void main(String[] args) {
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
UserManager userManager = (UserManager)factory.getBean("userManager");
userManager.addUser("张三", "123");
userManager.deleteUser(1);
}
}
三、spring对AOP的支持(采用配置方式)(工程spring_aop_2
)
首先将之前的注解全部取消,然后在配置文件中进行配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<bean id="securityHandler" class="com.bjsxt.spring.SecurityHandler"/>
<bean id="userManager" class="com.bjsxt.spring.UserManagerImpl"/>
<aop:config>
<aop:aspect id="security" ref="securityHandler">
<aop:pointcut id="allAddMethod" expression="execution(* com.bjsxt.spring.UserManagerImpl.add*(..))"/>
<aop:before method="checkSecurity" pointcut-ref="allAddMethod"/>
</aop:aspect>
</aop:config>
</beans>
SecurityHandler.java
package com.bjsxt.spring;
public class SecurityHandler {
private void checkSecurity() {
System.out.println("----------checkSecurity()---------------");
}
}
说明:两种配置方式本质上是一样的,我们推荐使用配置文件的方式进行配置。
我们还可以通过advice中添加一个jointpoint参数,这个值会由spring自动传入,从jointpoint中可以取到要检查的方法的参数和方法名(工程spring_aop_3
)
SecurityHandler.java
package com.bjsxt.spring;
import org.aspectj.lang.JoinPoint;
public class SecurityHandler {
private void checkSecurity(JoinPoint joinPoint){
//此时我们就可以拿到对某个方法进行安全性检查的参数和方法名字
//比如addUser("Tom", "123");中的参数"Tom"和"123"和方法名
//addUser
Object[] args = joinPoint.getArgs();
for(int i = 0; i < args.length; i++){
System.out.println(args[i]);
}
System.out.println(joinPoint.getSignature().getName());//拿到方法签名
System.out.println("----UserManagerImpl.checkSecurity()---");
}
}
四、使用CGLIB库实现动态代理(工程spring_aop_4
)
1.如果目标对象实现了接口(比如这里的业务类),默认情况下会采用JDK的动态代理实现AOP;
2.如果目标对象实现了接口,也可以强制使用CGLIB实现AOP;
3.没有实现接口那就必须使用CGLIB实现AOP了,spring会自动在JDK动态代理和CGLIB之间转换,就是实现了接口的使用JDK动态代理,没有实现接口的使用CGLIB实现,spring自动完成此转换。
4.1 强制使用CGLIB实现动态代理
- 添加CGLIB库
- 在spring的配置文件中进行配置:
<aop:aspectj-autoproxy proxy-target-class="true"/>
,此时我们会发现实现的代理类就是由CGLIB实现的。当然我们还是推荐使用JDK的动态代理,这就要求我们实现接口。
4.2 JDK动态代理和CGLIB字节码生成的区别:
- JDK动态代理只能对实现了接口的类生成代理,不能针对类生成代理
- CGLIB是针对类实现代理,实现原理是生成一个子类作为一个代理。因为是继承,所以该类或方法最好不要声明成final类型。实际中我们建议使用JDK的动态代理。