SSM框架学习-3
- AOP的概述
- AOP的底层实现
-
Spring的传统AOP 不带切入点的切面 带切入点的切面
- Spring的传统AOP的自动代理 基于Bean名称的自动代理 基于切面信息的自动代理
1.什么是AOP
- AOP为Aspect Oriented Progtamming的缩写,意为:面向切面编程,通过预编译方式,和运行期动态代理实现程序功能的统一维护的一种技术,是面向对象的增强。
- AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视,事务管理,安全检查,缓存)。
- Spring AOP使用纯Jaca实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类组织入增强代码。
2.AOP相关术语
- Joinpoint(连接点):指的是被拦截的点,在Spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
- Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
- Advice(增强/通知):所谓通知是指拦截到Joinpoint后要做的事情就是通知,通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
- Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或者Field。
- Target(目标对象):代理的目标对象
- Weaving(织入):是把增强应用到目标对象来创建新的代理过程。
- Proxy(代理):一个类被AOP织入增强后,就产生一个代理类。
- Aspect(切面):是切入点和通知(引介)的结合。
3.JDK动态代理
下面看一个例子来了解什么是JDK动态代理,通常我们在开发时通常会先创建接口类似这样的:
package com.snake_lvyonghao.aop.demo1;
public interface UserDao {
public void save();
public void update();
public void delete();
public void find();
}
假设它有增删改查的功能,接下来就要用实例类来实现接口功能,这个实现需要反射包里的proxy代理,首先要先创建一个接口实例,构造器构造增强类,生成代理类(Proxy.newProxyInstance方法),在方法的incovationHandler参数内可以使用匿名内部类来实现增强,或者在外部继承InvocationHandler接口,在invoke方法中重写要增强的方法:
package com.snake_lvyonghao.aop.demo1;
public class UserDaoImpl implements UserDao{
public void save() {
System.out.println("save");
}
public void update() {
System.out.println("update");
}
public void delete() {
System.out.println("delete");
}
public void find() {
System.out.println("find");
}
}
下面我们需要写一个代理类来实现对类的增强:
package com.snake_lvyonghao.aop.demo1;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyJdkProxy implements InvocationHandler {
private UserDao userDao;
public MyJdkProxy(UserDao userDao){
this.userDao = userDao;
}
//生成代理类
public Object createProxy(){
return Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
if("save".equals(method.getName())){
System.out.println("权限校验===========");
return method.invoke(userDao,objects);
}
return method.invoke(userDao,objects);
}
}
好了功能的实现我们用输出文本作为代替,接下来写一个测试文件,记得在配置文件中加上junit:
package com.snake_lvyonghao.aop.demo1;
import org.junit.Test;
public class SpringDemo1 {
@Test
public void demo1(){
UserDao userDao = new UserDaoImpl();
UserDao proxy = (UserDao)new MyJdkProxy(userDao).createProxy();
proxy.delete();
proxy.save();
proxy.update();
proxy.find();
}
}
这部分只需要了解如何使用代理实现对类的增强,在Spring中我们将会使用配置的方式来实现代理
4.CGLIB生成代理
- 对于不使用接口的业务类,无法使用JDK动态代理
- CFGlib采用非常底层字节码技术,可以为一个类创建子类,解决无接口代理问题
同样这是代理的底层原理只需要了解就好了,下面放上实现的代码,和JDK类似,这次使用了Spring,所以要在配置文件加上Spring基础的4个文件。
业务类:
package com.snake_lvyonghao.demo2;
public class ProductDao {
public void save() {
System.out.println("save");
}
public void update() {
System.out.println("update");
}
public void delete() {
System.out.println("delete");
}
public void find() {
System.out.println("find");
}
}
MyCglib:
package com.snake_lvyonghao.demo2;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyCglib implements MethodInterceptor {
private ProductDao productDao;
public MyCglib(ProductDao productDao){
this.productDao = productDao;
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if("save".equals(method.getName())){
System.out.println("方法校验=================");
return methodProxy.invokeSuper(o,objects);
}
return methodProxy.invokeSuper(o,objects);
}
public Object createProxy(){
//1.创建核心类
Enhancer enhancer = new Enhancer();
//2。设置父类
enhancer.setSuperclass(productDao.getClass());
//3.设置代理
enhancer.setCallback(this);
//4.生成代理
return enhancer.create();
}
}
Text:
package com.snake_lvyonghao.demo2;
import org.junit.Test;
public class SpringDemo2 {
@Test
public void demo1(){
ProductDao productDao = new ProductDao();
ProductDao proxy = (ProductDao)new MyCglib(productDao).createProxy();
proxy.delete();
proxy.find();
proxy.save();
proxy.update();
}
}
5.代理总结
- Spring在运行期间,生成动态代理,不需要特殊的编译器。
- Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术,为Bean执行横向织入。
- 程序中应有限对接口创建代理,便于程序维护
- 标记为final的方法,不能被代理,因为无法覆盖
- Spring只支持方法连接点,不提供属性连接点。
6.Spring AOP增强类型(通知类型)
- AOP联盟为通知Advice定义了org.aopalliance.aop.Interface.Advice
- Spring按通知Advice在目标类方法的连接点位置,可以分为五类:
前置通知org.springframework.aop.MethodBeforeAdvice(在目标方法执行前实施增强,比如权限验证) 后置通知org.springframework.aop.AfterReturningAdvice(在目标方法执行后实施增强,比如日志记录) 环绕通知org.aopalliance.intercept.MethodInterceptor(在目标方法执行前后实施增强) 异常跑出通知org.springframework.aop.ThrowsAdvice(在方法抛出异常后实施增强) *引介通知org.springframework.aop.IntroductionInterceptor(咋目标类中添加一些新的方法和属性)
7.Spring AOP切面类型
- Advisor:代表一般切面,Advice本身就是一个切面,对目标类所有方法进行拦截
- PointcutAdvisor:代表具有切点的切面,可以指定拦截目标类哪些方法
- IntriductionAdcisor:代表引介切面,针对引介通知而使用切面(不需要掌握)
8.Advisor切面案例
Advisor增强使用Spring自动代理的方式,不需要手动去写JDK或者CGLib代理。增强可以在创建之前,创建之后,这里我们选择前置通知作为演示:
- ProFactoryBean常用可配置属性:
target:代理目标对象 proxyInterfaces:代理要实现的接口
//多个接口可以使用以下格式赋值
<list>
<value></value>
...
<list>
初始接口和目标类
package com.snake_lvyonghao.demo3;
public interface StudentDao {
public void find();
public void save();
public void update();
public void delete();
}
---------------------------------------
package com.snake_lvyonghao.demo3;
public class StudentDaoImp implements StudentDao {
public void find() {
System.out.println("学生查询。。");
}
public void save() {
System.out.println("学生保存");
}
public void update() {
System.out.println("学生修改");
}
public void delete() {
System.out.println("学生删除");
}
}
设置增强内容
package com.snake_lvyonghao.demo3;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class MyBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("前置增强===========通知");
}
}
*配置文件,先设置接口以及增强类Bean,使用ProFactoryBean配置代理
<bean id="studentDao" class="com.snake_lvyonghao.demo3.StudentDaoImp"/>
<!--前置通知-->
<bean class="com.snake_lvyonghao.demo3.MyBeforeAdvice" id="myBeforeAdvice"/>
<!--Spring的AOP产生代理对象-->
<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="studentDaoProxy">
<!--目标类-->
<property name="target" ref="studentDao"></property>
<!--实现接口-->
<property name="interfaces" value="com.snake_lvyonghao.demo3.StudentDao"></property>
<!--拦截名称-->
<property name="interceptorNames" value="myBeforeAdvice"></property>
</bean>
测试类
package com.snake_lvyonghao.demo3;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringDemo3 {
@Test
public void demo1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentDao studentDao = (StudentDao) applicationContext.getBean("studentDaoProxy");
studentDao.find();
studentDao.delete();
studentDao.save();
studentDao.update();
}
}
- proxyTargetClass:是否对类代理而不是接口,设置为ture,使用CGLib代理
- interceptorNames:需要织入目标的Advice
- singleton:返回代理是否为单实例,默认单例
- optimize:当设置为true时,强制使用CGLib(怎么感觉没卵用)
9.PointcutAdvisor 切点切面
使用普通Advice作为切面,将对目标类所有方法进行拦截,不够灵活在实际开发中常采用带有切点的切面
- 常用PointcutAdvisor 实现类:
DefaultPointcutAdvisor最常用的切面类型,它可以通过任意Pointcut和Advice 组合定义切面 *JdkRegexpMethodPointcut 构造正则表达式切点(重点)
配置文件通过class=”org.springframework.aop.support.RegexpMethodPointcutAdvisor 构造正则表达式,具体看代码注释写的很清楚了:
<!--目标类-->
<bean class="com.snake_lvyonghao.demo4.CustomerDao" id="customerDao"/>
<!--配置通知-->
<bean id="MyAroundAdvice" class="com.snake_lvyonghao.demo4.MyAroundAdvice"/>
<!--一般的切面使用通知作为切面,因为要对目标类的某些方法进行增强,配置带有切入点的切面-->
<bean id="MyAdvice" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!--patten正则表达式匹配save-->
<property name="pattern" value=".*save.*"/>
<property name="advice" ref="MyAroundAdvice"/>
</bean>
<!--配置产生代理-->
<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="customerDao2">
<property name="target" ref="customerDao"/>
<property name="proxyTargetClass" value="true"/>
<property name="interceptorNames" value="MyAdvice"/>
</bean>
环绕增强内容:
package com.snake_lvyonghao.demo4;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕方法-前");
//执行目标方法(可以不执行)
Object obj = methodInvocation.proceed();
System.out.println("环绕方法-后");
return obj;
}
}
目标类(没什么区别)
package com.snake_lvyonghao.demo4;
public class CustomerDao {
public void find(){
System.out.println("查询客户");
}
public void save(){
System.out.println("保存客户");
}
public void update(){
System.out.println("修改客户");
}
public void delete(){
System.out.println("删除客户");
}
}
测试类
package com.snake_lvyonghao.demo4;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringDemo4 {
@Test
public void demo1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext2.xml");
CustomerDao customerDao = (CustomerDao) applicationContext.getBean("customerDao2");
customerDao.delete();
customerDao.save();
customerDao.find();
customerDao.update();
}
}
10.创建自动代理
简化配置,不需要每次增强都配置代理
- 前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,在实际 开发中,非常多的Bean每个都配置ProxyFactoryBean开发维护量巨大
- 解决方案:自动创建代理
BeanNameAutoProxyCreator根据Bean名称创建代理 DefaultAdvisorAutoProxyCreator根据Advisor本身包含信息创建代理 AnnotationAwareAspectJAutoProxyCreator 基于Bean中的AspectJ 注解进行自动代理
BeanNameAutoProxyCreator举例
- 对所有以DAO结尾Bean所有方法使用代理
代码太多了,这里只放上配置文件的内容(其他的已经写过很多次了),运行后发现,他确实可以自动的生成代理,具体代码还是参考Spring_AOP的demo5吧
<?xml version="1.0" encoding="UTF-8"?>
<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 class="com.snake_lvyonghao.demo5.StudentDaoImp" id="studentDao"/>
<bean class="com.snake_lvyonghao.demo5.CustomerDao" id="customerDao"/>
<!--配置增强-->
<bean id="myBeforeAdvice" class="com.snake_lvyonghao.demo5.MyBeforeAdvice"></bean>
<bean id="myAroundAdvice" class="com.snake_lvyonghao.demo5.MyAroundAdvice"/>
<!--配置代理-->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*Dao"/>
<property name="interceptorNames" value="myAroundAdvice"/>
</bean>
</beans>
DefaultAdvisorAutoProxyCreator举例
之前可以自动的设置代理类,但是无法实现切面代理,这里我们使用 DefaultAdvisorAutoProxyCreator来实现切面代理
同样就不放上完整代码了,详情请看Spring——AOP的Demo6把,这里只放上一段配置文件的内容: ```
<!--配置增强-->
<bean id="myBeforeAdvice" class="com.snake_lvyonghao.demo6.MyBeforeAdvice"></bean>
<bean id="myAroundAdvice" class="com.snake_lvyonghao.demo6.MyAroundAdvice"/>
<!--配置切面-->
<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="myBeforeAdvice"/>
<property name="pattern" value=".*save.*"/>
</bean>
<!--基于切面信息自动配置代理-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> ```