目录
一. 什么是AOP
二.Spring底层的AOP实现原理
2.1 JDK动态代理
有以下接口和实现类
public interface UserDao {
public void delete();
public void save();
public void update();
public void select();
}
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("dao中保存用户的方法执行了。。。");
}
@Override
public void delete() {
System.out.println("dao中delete用户的方法执行了。。。");
}
@Override
public void update() {
System.out.println("dao中更新用户的方法执行了。。。");
}
@Override
public void select() {
System.out.println("dao中查询用户的方法执行了。。。");
}
}
需求:需要在调用该类的 save 方法时进行权限校验
解决方案:使用jdk动态代理对UserDao产生代理
/**
* 使用JDK动态代理对UserDao产生代理
*/
public class JdkProxy implements InvocationHandler{
//将被增强的对象传到代理当中
private UserDao userDao;
public JdkProxy(UserDao userDao){
this.userDao = userDao;
}
/**
* 产生UserDao代理对象的方法
* @return
*/
public UserDao createProxy(){
UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(), this);
return userDaoProxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判断方法名是否为save()
if("save".equals(method.getName())){
//增强
System.out.println("权限校验");
return method.invoke(userDao, args);
}
return method.invoke(userDao, args);
}
}
2.2 Cglib产生动态代理
public class CarDao {
public void save() {
System.out.println("dao中保存汽车的方法执行了。。。");
}
public void delete() {
System.out.println("dao中删除汽车的方法执行了。。。");
}
public void update() {
System.out.println("dao中更新汽车的方法执行了。。。");
}
public void select() {
System.out.println("dao中查询汽车的方法执行了。。。");
}
}
/**
* Cglib动态代理
*
*/
public class CglibCarDao implements MethodInterceptor {
private CarDao carDao;
public CglibCarDao(CarDao carDao){
this.carDao = carDao;
}
/**
* 使用Cglib产生代理的方法
*/
public CarDao createProxy(){
//创建Cglib核心类
Enhancer enhancer = new Enhancer();
//设置父类(cglib采用继承的方式产生的代理对象)
enhancer.setSuperclass(carDao.getClass());
//设置回调:类似于InvocationHandler对象,callback相当于invoke
enhancer.setCallback(this);
//创建代理对象
CarDao proxy = (CarDao) enhancer.create();
return proxy;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//判断方法是否为save,是的话对save方法进行增强
if("save".equals(method.getName())){
//增强
System.out.println("增强了CarDao中的保存方法。。。");
}
//若果不是save则执行父类的原方法
return methodProxy.invokeSuper(proxy,args );
}
}
三.Spring的AOP开发
3.1AspectJ的XML方式
3.1.1 AOP开发中的术语
- Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只
- 支持方法类型的连接点.
- Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义.
- Advice(通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知.通知分为前置通知,后置
- 通知,异常通知,最终通知,环绕通知(切面要完成的功能)
- Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类
- 动态地添加一些方法或 Field.
- Target(目标对象):代理的目标对象
- Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程.
- spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装在期织入
- Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类
- Aspect(切面): 是切入点和通知(引介)的结合
3.1.2 AOP开发入门案例
①引入jar包
- spring 的传统 AOP 的开发的包
spring-aop-4.2.4.RELEASE.jar
com.springsource.org.aopalliance-1.0.0.jar
- aspectJ 的开发包:
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
spring-aspects-4.2.4.RELEASE.jar
②配置xml文件
引入在线约束:
<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">
</beans>
若无法上网则配置本地约束
③编写目标类并配置xml
public interface ProductDao {
public void delete();
public void save();
public void update();
public void select();
}
public class ProductDaoImpl implements ProductDao{
@Override
public void delete() {
System.out.println("删除商品。。。");
}
@Override
public void save() {
System.out.println("保存商品。。。");
}
@Override
public void update() {
System.out.println("更新商品。。。");
}
@Override
public void select() {
System.out.println("查询商品。。。");
}
}
<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="productDao" class="spring.dao.impl.ProductDaoImpl"></bean>
</beans>
④Spring整合Junit
引入 spring-test.jar,编写测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test01 {
@Resource(name="productDao")
private ProductDao productDao;
@Test
public void test01(){
productDao.save();
productDao.delete();
productDao.update();
productDao.select();
}
}
⑤编写一个切面类并交给Spring管理
public class MyAspectXml {
// 前置增强
public void before(){
System.out.println("前置增强===========");
}
}
<!-- 配置切面类 -->
<bean id="myAspectXml" class="spring.test.MyAspectXml"></bean>
⑥配置AOP
<!-- 进行 aop 的配置 -->
<aop:config>
<!-- 配置切入点表达式:哪些类的哪些方法需要进行增强 -->
<aop:pointcut expression="execution(* spring.dao.impl.ProductDaoImpl.save(..))" id="pointcut1"/>
<!-- 配置切面 -->
<aop:aspect ref="myAspectXml">
<aop:before method="before" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>
3.1.3 AOP中的通知类型
- 前置通知 :在目标方法执行之前执行,可以获得切入点信息
- 后置通知 :在目标方法执行之后执行
- 环绕通知 :在目标方法执行前和执行后执行
- 异常抛出通知:在目标方法执行出现 异常的时候 执行
- 最终通知 :无论目标方法是否出现异常 最终通知都会 执行.
编写切面类
public class MyAspectXml {
// 前置通知
public void before(JoinPoint jp){
System.out.println("前置增强==========="+jp);
}
//后置通知
public void afterReturning(Object result){
System.out.println("后置通知。。。。。。。。。。。"+result);
}
//环绕通知
public void around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕前通知。。。");
Object obj = pjp.proceed();
System.out.println("环绕后通知。。。"+obj);
}
//异常抛出通知
public void afterThrowing(Throwable ex){
System.out.println("异常抛出通知。。。"+ex.getMessage());
}
//最终通知 相当于 finally 代码块中的内容
public void after(){
System.out.println("最终通知。。。");
}
配置切面、切入点
<!-- 配置切面类 -->
<bean id="myAspectXml" class="spring.test.MyAspectXml"></bean>
<!-- 进行 aop 的配置 -->
<aop:config>
<!-- 配置切入点表达式:哪些类的哪些方法需要进行增强 -->
<aop:pointcut expression="execution(* spring.dao.impl.ProductDaoImpl.save(..))" id="pointcut1"/>
<aop:pointcut expression="execution(* spring.dao.impl.ProductDaoImpl.delete(..))" id="pointcut2"/>
<aop:pointcut expression="execution(* spring.dao.impl.ProductDaoImpl.update(..))" id="pointcut3"/>
<aop:pointcut expression="execution(* spring.dao.impl.ProductDaoImpl.select(..))" id="pointcut4"/>
<aop:pointcut expression="execution(* spring.dao.impl.ProductDaoImpl.testAfter(..))" id="pointcut5"/>
<!-- 配置切面 -->
<aop:aspect ref="myAspectXml">
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="pointcut1"/>
<!-- 后置通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="pointcut2" returning="result"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pointcut3"/>
<!-- 异常抛出通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex"/>
<!-- 最终通知 -->
<aop:after method="after" pointcut-ref="pointcut5"/>
</aop:aspect>
</aop:config>
运行结果
3.1.4 切入点表达式
- execution(表达式)
- [方法访问修饰符] 方法返回值 包名.类名.方法名(方法的参数)
- 其中访问权限修饰符可以省略,方法返回值可以写具体类型或者 * 表示任意返回值类型
- * *.*.*Dao.save(..)
- 指向所有Dao类中的save()
- * spring.dao.UserDao+.save(..)
- 指向当前类UserDao及其子类中的save()
- * spring.dao.UserDao+.*(..)
- 指向当前类及其子类下的所有方法
- * spring.dao..*.*(..)
- 指向当前包及子包下的所有类的所有方法
- public * spring.dao.*.*(..)
- 指向当前包下的所有类的所有方法
- [方法访问修饰符] 方法返回值 包名.类名.方法名(方法的参数)
3.2 SpringAOP 注解开发
3.2.1 注解开发入门案例
① 创建项目,引入jar包
② 配置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"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
</beans>
③ 目标类的编写及配置
public class OrderDao {
public void save(){
System.out.println("保存订单...");
}
public void update(){
System.out.println("修改订单...");
}
public String delete(){
System.out.println("删除订单...");
return "赵冠希";
}
public void find(){
System.out.println("查询订单...");
// int d = 1/0;
}
<!-- 配置目标类================ -->
<bean id="orderDao" class="spring.demo1.OrderDao"></bean>
④ 编写切面类添加注解并配置
/**
* 切面类:注解的切面类
*/
@Aspect
public class MyAspectAnno {
@Before(value="execution(* spring.demo1.OrderDao.save(..))")
public void before(){
System.out.println("前置增强===========");
}
}
<!-- 配置切面类================ -->
<bean id="myAspect" class="spring.demo1.MyAspectAnno"></bean>
⑤在配置文件中打开注解的AOP开发
<!-- 在配置文件中开启注解的AOP的开发============ -->
<aop:aspectj-autoproxy/>
⑥编写测试类
/**
* Spring的AOP的注解开发
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo1 {
@Resource(name="orderDao")
private OrderDao orderDao;
@Test
public void demo1(){
orderDao.save();
orderDao.update();
orderDao.delete();
orderDao.find();
}
}
3.2.2 其它通知类型注解案例
在上述案例的基础上使用其它通知类型注解只需修改切面类即可
@Before :前置通知
@Before(value="execution(* spring.demo1.OrderDao.save(..))")
public void before(){
System.out.println("前置增强===========");
}
@AfterReturning :后置通知
@AfterReturning(value="execution(* spring.demo1.OrderDao.delete(..))",returning="result")
public void afterReturning(Object result){
System.out.println("后置增强==========="+result);
}
@Around :环绕通知
// 环绕通知:
@Around(value="execution(* spring.demo1.OrderDao.update(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("环绕前增强==========");
Object obj = joinPoint.proceed();
System.out.println("环绕后增强==========");
return obj;
}
@AfterThrowing :异常抛出通知
// 异常抛出通知:
@AfterThrowing(value="execution(* spring.demo1.OrderDao.find(..))",throwing="e")
public void afterThrowing(Throwable e){
System.out.println("异常抛出增强========="+e.getMessage());
}
@After :最终通知
// 最终通知
@After(value="execution(* spring.demo1.OrderDao.find(..))")
public void after(){
System.out.println("最终增强============");
}
3.2.3 AOP的切入点注解
为了方便对切入点进行管理我们使用切入点注解
// 切入点注解:
@Pointcut(value="execution(* spring.demo1.OrderDao.find(..))")
private void pointcut1(){}
@Pointcut(value="execution(* spring.demo1.OrderDao.save(..))")
private void pointcut2(){}
@Pointcut(value="execution(* spring.demo1.OrderDao.update(..))")
private void pointcut3(){}
@Pointcut(value="execution(* spring.demo1.OrderDao.delete(..))")
private void pointcut4(){}
3.2.4 切面类最终代码展示
/**
* 切面类:注解的切面类
*/
@Aspect
public class MyAspectAnno {
@Before(value="MyAspectAnno.pointcut2()")
public void before(){
System.out.println("前置增强===========");
}
// 后置通知:
@AfterReturning(value="MyAspectAnno.pointcut4()",returning="result")
public void afterReturning(Object result){
System.out.println("后置增强==========="+result);
}
// 环绕通知:
@Around(value="MyAspectAnno.pointcut3()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("环绕前增强==========");
Object obj = joinPoint.proceed();
System.out.println("环绕后增强==========");
return obj;
}
// 异常抛出通知:
@AfterThrowing(value="MyAspectAnno.pointcut1()",throwing="e")
public void afterThrowing(Throwable e){
System.out.println("异常抛出增强========="+e.getMessage());
}
// 最终通知
@After(value="MyAspectAnno.pointcut1()")
public void after(){
System.out.println("最终增强============");
}
// 切入点注解:
@Pointcut(value="execution(* spring.demo1.OrderDao.find(..))")
private void pointcut1(){}
@Pointcut(value="execution(* spring.demo1.OrderDao.save(..))")
private void pointcut2(){}
@Pointcut(value="execution(* spring.demo1.OrderDao.update(..))")
private void pointcut3(){}
@Pointcut(value="execution(* spring.demo1.OrderDao.delete(..))")
private void pointcut4(){}
}