zoukankan      html  css  js  c++  java
  • Spring之面向切面编程AOP(二)

    简介

    当积累的知识点到一定量的时候,学新知识就变得容易多了。希望再接下来的学习顺利进行下去。今天知识也是挺简单的,主要就是AOP面向切面编程。其中牵涉到了JDKProxy和CGLIB两个代理类,如何使用好,加以深刻理解。学起Spring切面编程也就简单多了

    代理模式

    1. 代理模式介绍
    	代理模式的英文叫做Proxy或Surrogate,中文都可译为”代理“,所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用
    2. 	代理分了三个角色
    	抽象主题角色:就是代理类和被代理的类共同的接口
    	代理主题角色:就是代理类中持有了被代理类的引用,代理类通常在将客户端调用传递给现在创建的对象之前或之后,都要执行某个操作,而不是单纯地将调用传递给现有的对象
    	真实主题:就是代理类产生的真正的对象
    3. JDK动态代理
    public static <T> T getBean(final Class<T> clazz) {
    	IUserService proxyService = (IUserService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new InvocationHandler() {
    		Object retVal = null;
    		@Override
    		public Object invoke(Object proxy, Method method, Object[] args)
    				throws Throwable {
    			if("saveUser".equals(method.getName()) || "updateUser".equals(method.getName())) {
    				security();
    				retVal = method.invoke(clazz.newInstance(), args);	//调用目标对象
    			}
    			return retVal;
    		}
    	});
    }
    总结:
    	1、因为利用JDKProxy生成的代理类实现了接口,所以目标类中所有的方法在代理类中都有。
    	2、生成的代理类的所有的方法都拦截了目标类的所有的方法。而拦截器中invoke方法的内容正好就是代理类的各个方法的组成体。
    	3、利用JDKProxy方式必须有接口的存在。
    	4、invoke方法中的三个参数可以访问目标类的被调用方法的API、被调用方法的参数、被调用方法的返回类型
    	
    4. CGLIB代理
    private Class clazz;
    public <T> T getEnhancer(Class<T> clazz) {
    	this.clazz = clazz; 
    	Enhancer enhancer = new Enhancer();
    	enhancer.setClassLoader(clazz.getClassLoader());
    	enhancer.setSuperclass(clazz);
    
    	enhancer.setCallback(this);
    
    	return (T) enhancer.create();
    }
    @Override
    public Object intercept(Object proxy, Method method, Object[] args,
    		MethodProxy proxyMethod) throws Throwable {
    	Object retVal = null;
    	if("saveUser".equals(method.getName()) || "updateUser".equals(method.getName())) {
    		security();
    		retVal = method.invoke(clazz.newInstance(), args);
    		System.out.println(proxyMethod.getSignature().getName()+">>>>>>>>>");
    	}
    	return retVal;
    }
    总结: cglib代理
    	1. 首先要实现MethodInterceptor,重写intercept方法
    	2. 创建Enhancer对象,类加载器,加载类,回调方法setCallback,创建对象

    Spring面向切面编程

    1. Spring面向切面编程介绍
    	Spring提供2个代理模式,一个是jdk代理,另一个cglib代理
    		1.若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
    		2.若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
    
    	注意:开发时尽量使用接口的编程,
    		(1)对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统。
    		(2)标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要被通知的方法都被复写,将通知织入。final方法是不允许重写的。
    		(3) spring只支持方法连接点,不支持属性的连接点
    		
    2. AOP概念
    	JDKProxy代理			SpringAop
    	目标对象				目标对象(Target)
    	拦截器类				切面(Aspect)
    	拦截器类中的方法		通知(Advice)
    	被拦截到的目标类中方法的集合	切入点(Pointcut)
    	在客户端调用的方法(目标类目标方法)	连接点(joinpoint)
    	代理类					AOP代理(JDK&CGLIB)
    	代理类的代理方法生成的过程		织入(Weaving)
    	
    	***通知根据拦截目标类中的目标方法的位置不一样可以分为:前置通知、后置通知、最终通知、环绕通知、异常通知

    AOP编程实现方式

    1. XML形式实现
    	1). SpringAOP编程,需要引入的jar包
    	2). 配置applicationContext.xml文件
    		a. 引入aop的约束文件
    		b. 声明切面(其实就是定义一个bean节点,class为切面类)
    		c. 声明切面配置<aop:config>
    		d. 定义切面,相当于给切面注入灵魂<aop:aspect id="" ref="">
    		e. 声明切入点<aop:pointcut id="" expression="">	指定项目中哪个作为切入点
    		f. 定义通知<aop:before pointcut-ref="" method="">	注入切入点,声明哪个方法要进行通知
    2. 注解方式实现
    	1). SpringAOP编程,需要引入的jar包
    	2). 配置applicationContext.xml文件
    		a. 引入aop的约束文件
    		b. 声明切面对象/声明接口实现类对象<bean>
    		c. /**在类上面加入切面*/@Aspect		//表示在spring容器中定义:<aop:aspect id="aa" ref="security">
    		d. 声明切入点
    			@Pointcut(value="execution(* cn.itcast.f_aspectJ.a_before.UserServiceImpl.saveUser(..))")
    			public void save(){};
    		e. 加入通知@AfterReturning(value="save() || find()",returning="returnValue")
    3. 通知详解
    	1). 任何通知方法可以将第一个参数定义为org.aspectj.lang.JoinPoint类型 (
    	 * 环绕通知需要定义第一个参数为ProceedingJoinPoint类型, 它是 JoinPoint 的一个子类)
    	2). 前置通知(before):在访问目标对象方法之前,先执行通知定义的方法
        	特点:如果代理对象(切面)中的方法(通知)抛出异常,此时不会执行目标对象
    	3). 后置通知(after-returning):在访问目标对象方法之后,再执行通知定义的方法
    		特点:1:如果在目标对象中抛出异常,此时不会执行通知
    			  2:因为是先执行目标对象中的方法,再执行通知,所以能不能在通知中获取目标对象的方法的返回值?能
    				第一步:在spring容器中定义:returning="returnValue"
    				第二步:在通知的方法中的第二个参数,可以指定Object类型
    	4). 异常通知:在访问目标对象方法之后,前提是目标对象方法中抛出异常,此时才会执行通知定义的方法
    	特点:1:只有目标对象方法中抛出异常,此时才会执行通知
    		  2:在通知的方法中捕获异常
    			第一步:在spring容器中定义
    			第二步:在通知的方法中的第二个参数的位置,可以指定,例如public void checkSecurity(JoinPoint joinPoint,Throwable throwingValue){
    				* 要求一:获取目标对象抛出的异常的参数要放置在第二个参数的位置
    				* 要求二:类型必须指定Throwable类型
    				* 要求三:Throwable对应的属性值要和spring容器中定义的throwing="throwingValue"值要相匹配
    	5). 最终通知:在访问目标对象方法之后,不管是否抛出异常,此时都会执行通知定义的方法
    		特点:1:不管目标对象是否抛出异常,都会执行通知的方法			
    	6). 环绕通知:
    		a. 最后一种通知是环绕通知。环绕通知在一个目标对象方法执行之前和之后执行。它使得通知有机会
    		b. 在一个方法执行之前和执行之后运行。而且它可以决定这个方法在什么时候执行,如何执行,甚至是否执行。
    		环绕通知经常在某线程安全的环境下,你需要在一个方法执行之前和之后共享某种状态的时候使用。 
    		请尽量使用最简单的满足你需求的通知。(比如如果简单的前置通知也可以适用的情况下不要使用环绕通知)

    切入点表达式(Expression)

    1). 其实execution中方法全名,5个参数:带?号表示非必填项
    	execution( modifiers-pattern? 
    	ret-type-pattern 
    	declaring-type-pattern? 
    	name-pattern(param-pattern)
    	throws-pattern?
    	)
    2). 例子:
    	任意公共方法的执行:
    	execution(public * *(..))
    
    	任何一个名字以“set”开始的方法的执行:
    	execution(* set*(..))
    
    	AccountService接口定义的任意方法的执行:
    	execution(* com.xyz.service.AccountService.*(..))
    
    	在service包中定义的任意方法的执行:
    	execution(* com.xyz.service.*.*(..))
    
    	重点:
    	在service包或其子包中定义的任意方法的执行:
    	execution(* com.xyz.service..*.*(..))
    总结:
    	* 代表所有,可以理解成通配符
    	*(..) 代表所有方法
    	save*(*,String) 代表以save开头的方法,第一个参数为任意类型,第二个参数为string类型
    

    Spring+JDBC

    Jdbc编程特点
    	静态代码+动态变量 = jdbc编程。在spring中动态变量可以用注入的形式给予。这样的编程方式适合包装成模板。静态代码构成了模板,而动态变量则是需要传入的参数
    
    JDBCTemplate编码
    1. 导包在spring中依赖包和核心包中找
    	com.springsource.org.apache.commons.dbcp-1.2.2.osgi.jar
    	com.springsource.org.apache.commons.pool-1.5.3.jar
    	com.springsource.org.junit-4.7.0.jar
    	mysql-connector-java-5.0.8-bin.jar
    	spring-jdbc-3.2.0.RELEASE.jar
    	spring-tx-3.2.0.RELEASE.jar
    2. 在applicationContext.xml中配置DBCP连接池
    	<!-- 配置DBCP连接池 -->
        <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    	<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    	<property name="url" value="jdbc:mysql://localhost:3306/testspring?useUnicode=true&amp;characterEncoding=utf8"></property>
    	<property name="username" value="root"></property>
    	<property name="password" value="root"></property>
    	
    	<!-- 连接池启动时的初始值 -->
    	<property name="initialSize" value="1"/>
    	<!-- 连接池的最大值 -->
    	<property name="maxActive" value="500"/>
    	<!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
    	<property name="maxIdle" value="2"/>
    	<!--  最小空闲值.当空闲的连接数少于该值时,连接池就会预申请一些连接,以避免洪峰来时再申请而造成的性能开销 -->
    	<property name="minIdle" value="1"/>
    3. 	注入accountDao属性
    	<!-- 创建Dao的对象 -->
        <bean id="accountDao" class="cn.itcast.b_crud.AccountDaoImpl">
            <property name="jdbcTemplate" ref="jdbcTemplate"></property>
        </bean>
    	<!-- 创建spring提供的Jdbc模板,用来操作数据库 -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        	    <property name="dataSource" ref="dataSource"></property>
        </bean>
    4. 在DAO中操作数据库
    	private JdbcTemplate jdbcTemplate;
    	CUD使用udpate方法,查询使用query方法
    	
    #Spring整合junit,完成测试
    	1. 导入•org.springframework.test-3.0.2.RELEASE.jar
    	2. 加入注解,@RunWith(value=SpringJUnit4ClassRunner.class)
    	@ContextConfiguration(value="classpath:cn/itcast/b_crud/beans.xml")
    	3. @Resource(name="accountDao")
  • 相关阅读:
    【gtest/gmock】警告与报错集合
    【gtest/gmock】gmock:Mock的常用方法
    【C++容器】vector 和 list 的区别
    【C++百科】C++标准库到底是什么?
    【C++】设置、改变、获取系统环境变量:setenv & putenv & getenv
    【C++调试】error: 编译报错合集
    【C++调试】 warning: 编译警告合集
    Linux常用命令
    tcpdump及wireshark组合使用
    Vim快捷键
  • 原文地址:https://www.cnblogs.com/codingpark/p/4342961.html
Copyright © 2011-2022 走看看