反射
-
用另外的手段来创建对象、调用对象方法、使用对象的属性,常用在框架编程
-
获取类对象(*)
Class<?> c = Class.forName(“包.类”); // 不需要事先import
Class<?> c = 类.class; // 需要事先import -
创建对象实例(*)
c.newInstance(); // 调用无参构造,创建对象
c.getConstructor(构造方法的参数类型).newInstance(构造方法参数) -
获取方法(*)
c.getMethods(); // 得到的是本类以及继承的所有public的方法
c.getDeclaredMethods(); // 得到本类的所有方法(不考虑访问修饰符)
c.getMethod(方法名字, 方法的参数类型…)
c.getDeclaredMethod(方法名字, 方法的参数类型…) -
获取属性
c.getFields(); // 得到的是本类以及继承的所有public的属性
c.getDeclaredFields(); // 得到本类的所有属性
c.getField(“属性名”);
c.getDeclaredField(“属性名”); -
方法对象 Method(*)
对象.方法名(参数); // 正常调用方法
方法.invoke(对象, 参数); // 反射调用方法 -
属性对象 Field
对象.属性 = 值; // 正常赋值
对象.属性; // 正常取值属性.set(对象, 值); // 反射赋值
属性.get(对象); // 反射取值 -
突破private的限制
方法对象 Method.setAccessible(true); // 允许私有方法可以访问
属性对象 Field.setAccessible(true); // 允许私有属性可以访问
spring的aop
1. pom.xml 新增依赖
<!-- spring aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.17.RELEASE</version>
</dependency>
<!-- 第三方 aop依赖 aspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
2 .在spring的配置文件中加入aop的相关标签
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 目标类 -->
<bean id="userService" class="spring.service.UserServiceTarget">
</bean>
<!-- 通知类 -->
<bean id="transactionAdvice" class="spring.advice.TransactionAdvice">
</bean>
<!-- 让相关注解生效, 还能够帮我们生成底层的代理对象 -->
<aop:aspectj-autoproxy/>
</beans>
3 .实现通知类
切点表达式匹配方式:
@Around("within(spring.service.UserServiceTarget)")
匹配到某一个目标类中的所有方法
@Around("execution(public * spring.service.UserServiceTarget.biz*(..) )")
匹配到某个类中的具体方法,可以根据方法名和参数列表进行匹配,此处可以匹配目标类下任意的biz开头的方法。
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
通过注解匹配,此处可以匹配所有加了Transactional注解的方法。
// 切面
@Aspect
public class TransactionAdvice {
@Around("within(spring.service.UserServiceTarget)")
// 通过切点表达式,把通知和目标结合在一起
// 作用当UserServiceTarget类中任意一个方法执行时(biz1,biz2) 就会和下面的transaction方法结合在一起了
// 通知类型:环绕通知,决定了这个通知方法会和哪些目标方法结合
// 这个方法称为通知方法,它其中包含了一些重复逻辑,另外它负责调用目标方法
public Object transaction(ProceedingJoinPoint pjp) { // pjp 内部去调用目标的方法
System.out.println("开始事务");
Object r = null;
try {
// 调用目标
r = pjp.proceed(); // 目标 biz1, biz2...
System.out.println("提交事务");
} catch (Throwable e) {
System.out.println("回滚事务");
}
return r;
}
}
4. 使用
使用上,尽量实现了无侵入的效果,原来的代码不受影响
ClassPathXmlApplicationContext context
= new ClassPathXmlApplicationContext("spring.xml");
// getBean方法返回的不是target对象,而是由spring容器生成的代理对象,底层产生代理的技术就是:jdk动态代理技术
UserService service = context.getBean(UserService.class);
service.biz1();
// 代理对象内部调用了通知TransactionAdvice对象, 检查是否和通知方法上的切点表达式符合
// 如果符合,就执行该通知方法, 通知方法内部再调用目标
System.out.println("service真正类型是:"+service.getClass());
aop 中的概念
代理 proxy
目标 target 被代理的对象
通知 advice 包含了可重用代码(例如事务代码),并调用目标
切点 pointcut 把通知和目标结合一起,它定义的是一种匹配规则
切面 aspect 切面=通知+切点
aop (aspect切面 oriented 面向 programming 编程) 面向切面编程
oop 面向对象编程
切点 pointcut
aspectj中定义的一些切点表达式
- within(包名.类名) 这个类中所有方法执行时,都会应用通知方法
- execution(访问修饰符 返回值类型 包名.类名.方法名( 参数类型… ))
注意*
可以匹配任意类型, 可以出现在方法返回值,方法名,类名当中
注意..
可以匹配方法参数,表示参数的个数和类型都是任意的 - @annotation(包名.注解名)
它是根据方法上的注解进行匹配, 如果有注解则匹配
通知类型
环绕通知: @Around(切点表达式) 加在通知方法上(功能最全的), 通知方法环绕着目标方法
前置通知: @Before 通知代码只会出现在目标方法之前
正常结束通知: @AfterReturning 在目标方法正常结束后,调用通知方法
异常通知: @AfterThrowing 在目标方法出现异常后,调用通知方法
结束通知: @After 在目标方法结束后(正常或异常),总会调用的通知
统计每个业务方法的执行时间
调用流程
容器启动时
- spring会检查容器,看是否需要为这些bean创建代理对象
- 检查所有的切点表达式,如果匹配到了目标,就为该目标创建代理对象
获取对象时(getBean, 依赖注入)
会检查这个类型是否有代理对象,如果有,优先返回代理对象
调用方法时
调用了代理对象的方法,代理对象会首先经过通知类(多个)
检查切点,如果切点匹配,再进行下面的通知调用
根据不同的通知类型进行调用:
例如前置通知先调通知,再调目标
如果是环绕通知,先调用通知,通知内部调用目标
…
aop理解
面向切面编程:就是把重复的逻辑抽取为一个通知方法,
然后通过切点匹配来决定哪些目标要应用这些通知。
其中利用了代理的技术,在代理中检查切点是否匹配,调用通知(多个),最后调用目标
生成代理的常见技术
jdk的动态代理, 只能针对接口实现代理, jdk动态代理在高版本中性能优于cglib
cglib动态代理, 既可以针对接口实现代理,也可以生成子类充当目标父类的代理
spring默认使用jdk动态代理,如果没有实现接口,就使用cglib代理