AOP(Aspect Oriented Programming)面向切面编程
采用横向抽取机制,取代了传统纵向继承体系重复性代码 Spring AOP使用纯java实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类织入增强代码
1、jdk的动态代理 2、cglib的动态代理
1、新建接口UserDao
public interface UserDao { public void save(); public void update(); public void delete(); public void find(); }2、新建UserDao实现类UserDaoImpl。这里不做过多操作,只打印输出相关信息
public class UserDaoImpl implements UserDao{ @Override public void save() { System.out.println("保存用户"); } @Override public void update() { System.out.println("修改用户"); } @Override public void delete() { System.out.println("删除用户"); } @Override public void find() { System.out.println("查询用户"); } }3、新建一个代理类做增强操作
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(){ Object proxy = Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(),this); return proxy; } 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); } }4、测试
@Test public void demo1(){ UserDao userDao = new UserDaoImpl(); UserDao proxy = (UserDao) new MyJdkProxy(userDao).createProxy(); proxy.save(); proxy.update(); }1、新建ProductDao类
public class ProductDao { public void save(){ System.out.println("保存商品"); } public void update(){ System.out.println("修改商品"); } public void delete(){ System.out.println("删除商品"); } public void find(){ System.out.println("查询商品"); } }2、创建代理类MyCglibProxy
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 MyCglibProxy implements MethodInterceptor { public ProductDao productDao; public MyCglibProxy(ProductDao productDao){ this.productDao = productDao; } public Object createProxy(){ //1、创建核心类 Enhancer enhancer = new Enhancer(); //2、设置父类 enhancer.setSuperclass(productDao.getClass()); //3、设置回调 enhancer.setCallback(this); //4、生成代理 Object proxy = enhancer.create(); return proxy; } @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { if("save".equals(method.getName())){ System.out.println("权限校验"); return methodProxy.invokeSuper(proxy,args); } return methodProxy.invokeSuper(proxy,args); } }3、测试
@Test public void demo1(){ ProductDao productDao = new ProductDao(); ProductDao proxy = (ProductDao) new MyCglibProxy(productDao).createProxy(); proxy.save(); proxy.update(); proxy.delete(); proxy.find(); }代理知识总结:
spring再运行期,生成动态代理对象,不需要特殊的编译器Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术,为目标Bean执行横向织入 1、若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理 2、若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。程序中应优先对接口创建代理,便于程序解耦维护标记为final的方法,不能被代理,因为无法进行覆盖 1、JDK动态代理,是针对接口生成子类,接口中方法不能使用final修饰 2、CGLib是针对目标类生产子类,因此类或方法不能使用final的Spring只支持方法连接点,不提供属性连接点Spring AOP增强类型
AOP联盟为通知Advice定义了org.aopalliance.aop.Interface.AdviceSpring 按照通知Advice在目标类方法的连接点位置,可以分为5类 前置通知org.springframework.aop.MethodBeforeAdvice 在目标方法执行前实施增强后置通知org.springframework.aop.AfterReturningAdvice 在目标方法执行后实施增强环绕通知org.aopalliance.intercept.MethodInterceptor 在目标方法执行前后实施增强异常抛出通知 org.springframework.aop.ThrowAdvice 在方法抛出异常后实施通知引介通知 org.springframework.aop.IntroductionInterceptor 在目标类中添加一些新的方法和属性首先导入jar
spring-aopaopalliance 1、新建StudentDao接口 public interface StudentDao { public void find(); public void update(); public void save(); public void delete(); }2、新建StudentDao实现类StudentDaoImpl
public class StudentDaoImpl implements StudentDao { public void find() { System.out.println("查询学生"); } public void update() { System.out.println("修改学生"); } public void save() { System.out.println("保存学生"); } public void delete() { System.out.println("删除学生"); } }3、新建增强类MybeforeAdvice并实现MethBeforeAdvice
import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class MybeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("这是前置增强(通知)"); } }4、配置文件配置
<!--配置目标类--> <bean id="studentDao" class="com.aop.demo3.StudentDaoImpl"/> <!--前置通知类型--> <bean id="myBeforeAdvice" class="com.aop.demo3.MybeforeAdvice"/> <!--Spring的AOP产生代理对象--> <bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!--配置目标类--> <property name="target" ref="studentDao"/> <!--实现的接口--> <property name="interfaces" value="com.aop.demo3.StudentDao"/> <!--采用拦截的名称--> <property name="interceptorNames" value="myBeforeAdvice"/> </bean>5、测试
@Test public void demo1(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext2.xml"); //StudentDao studentDao = (StudentDao) applicationContext.getBean("studentDao"); StudentDao studentDao = (StudentDao) applicationContext.getBean("studentDaoProxy"); studentDao.save(); studentDao.update(); studentDao.delete(); studentDao.find(); }配置文件中的属性配置
<!--配置目标类--> <property name="target" ref="studentDao"/> target :目标类interfaces :实现的接口proxyTargetClass :是否对类代理而不是接口,设置为true时,使用CGLib代理interceptorNames :需要织入目标的Advicesingleton:返回代理是否为单例,默认为单例optimize:当设置为true时,强制使用CGLib总结 使用普通Advice 作为切面,将对目标类所有方法进行拦截,不够灵活,在实际开发中常采用 带有切点的切面
常用PointcutAdvisor实现类
DefaultPointcutAdvisor最常用的切面类型,他可以通过任意Pointcut和Advice组合定义切面JdkRegexpMethodPointcut构造正则表达式切点1、新建CustomerDao
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("删除客户"); } }2、新建MyAroundAdvice类继承MethodInterceptor,环绕增强
public class MyAroundAdvice implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("环绕前增强"); //执行目标方法 Object obj = invocation.proceed(); System.out.println("环绕后增强"); return obj; } }3、配置文件
<?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 id="customerDao" class="com.aop.demo4.CustomerDao"/> <!--配置通知--> <bean id="myAroundAdvice" class="com.aop.demo4.MyAroundAdvice"/> <!-- 一般的切面是使用通知作为切面的,因为要对目标类的某个方法进行增强就需要配置一个带有切入点的切面--> <bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <!--pattern中配置正则表达式, .任意字符,*任意次数--> <!--对所有方法增强--> <!-- <property name="pattern" value=".*"/>--> <!--只对save方法增强--> <!-- <property name="pattern" value=".*save.*"/>--> <!--对多个方法增强--> <property name="patterns" value=".*save.*,.*delete.*"/> <property name="advice" ref="myAroundAdvice"/> </bean> <!--配置产生代理--> <bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="customerDao"/> <property name="proxyTargetClass" value="true"/> <property name="interceptorNames" value="myAdvisor"/> </bean> </beans>4、测试
@Test public void demo1(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext3.xml"); //CustomerDao customerDao = (CustomerDao) applicationContext.getBean("customerDao"); CustomerDao customerDao = (CustomerDao) applicationContext.getBean("customerDaoProxy"); customerDao.save(); customerDao.update(); customerDao.delete(); customerDao.find(); }前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,在实际开发中,非常多的Bean每个都配置ProxyFactoryBean开发维护量巨大
解决办法:自动创建代理
详见下篇
