动态代理技术
原始编写java代码方式
.java -> javac ->.class -> 类加载
**代理的意义:**通过生成代理,来避免访问真实的目标类,实现无入侵式的代码扩展,在方法前后做一些事情,并且不修改源码;
**静态代理:**需要程序员自己创建,在静态代理时要知道代理的是哪个类或接口;
**动态代理:**通过反射机制完成,动态代理把代理类中对目标类的逻辑处理抽象成函数式内部类InvocationHandler,其中的invoke方法进行逻辑处理;代理通过InvocationHandler实现对目标类的工作,不需要像静态代理那样,对每个方法分别的处理;动态代理的调用处理程序必须事先InvocationHandler接口,及使用Proxy类中的newProxyInstance方法动态的创建代理类
Proxy生成代理类的底层实现
接口
public interface UserService {
void a(int x,int y);
void b();
void c();
}
被代理的目标类
public class UserServiceTarget implements UserService {
public void a(int x,int y) {
System.out.println("方法a");
}
public void b() {
System.out.println("方法b");
}
public void c() {
System.out.println("方法c");
}
}
public class TestDynamicProxy {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//1. 生成实现接口的代理类的字节码
// 参数1 String 表示代理类的名字;参数2 Class[] 表示代理类要实现的接口
final byte[] proxyBytes = ProxyGenerator.generateProxyClass("UserServiceProxy", new Class[]{UserService.class});
//2. 执行类加载,将生成的代理类字节码加载为类对象
//生成加载器
ClassLoader cloader=new ClassLoader(){
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return defineClass(name,proxyBytes,0,proxyBytes.length);
}
};
//类加载后生成的代理类
Class c=cloader.loadClass("UserServiceProxy");
//3. 创建代理类实例对象,不能直接用new 创建
//获取代理类的构造方法
Constructor constructor = c.getConstructor(InvocationHandler.class);
//目标类
final UserServiceTarget target = new UserServiceTarget();
//创建代理类实例
UserService proxy=(UserService) constructor.newInstance(new InvocationHandler() {
//指定代理类所干的事情,即测量目标类方法运行时间
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.nanoTime();
//执行目标类的方法
method.invoke(target, args);
long endTime = System.nanoTime();
System.out.println("方法运行时间:" + (endTime - startTime));
return null;
}
});
proxy.a(10,20);
}
}
直接使用Proxy来生成代理类
public class TestDyanmicProxy2 {
public static void main(String[] args) {
//直接创建代理类的实例
//1. 获取类加载器
ClassLoader cl = UserService.class.getClassLoader();
//2. 规定代理类要实现的接口
Class[] interfaces = {UserService.class};
//3. 给定一个InvocationHandler对象,包含要执行的重复逻辑
UserServiceTarget target = new UserServiceTarget();
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.nanoTime();
//执行目标类的方法
method.invoke(target, args);
long endTime = System.nanoTime();
System.out.println("方法运行时间:" + (endTime - startTime));
return null;
}
};
//直接生成代理类对象
UserService u =(UserService) Proxy.newProxyInstance(cl, interfaces, h);
System.out.println(u.getClass());//class com.sun.proxy.$Proxy0
u.b();
u.c();
}
}
AOP (aspect oriented programming 面向切面编程)
aspect 切面,oriented 面向,programming 编程
切面 aspect = 通知 adivce + 切点 pointcut
通知:是一个方法,其中包含了重复的逻辑(计时,事务)
切点:是一种匹配条件,与切点条件符合的目标方法,才会通知方法;
使用步骤
- 添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.22.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
</dependencies>
- 编写切面
@Aspect
public class 切面类{
@Around("切点表达式") //切点表达式用来匹配目标和通知
public Object 通知方法(ProceedingJoinpoint pjp){
//重复逻辑 计时,事务控制,权限控制
//调用目标 result是目标方法返回的结果
Object result=pjp.proceed();
retrun result;
}
}
//表示此类是一个切面类
@Aspect
@Component
public class TimeAspect {
//表示匹配service包下的UserserviceTarget类中的所有方法
// @Around("within(service.UserServiceTarget)")
//表示匹配service包下的UserServiceTarget中的修饰符为public 无返回值的a方法,参数任意个数、任意值
@Around("execution(public void service.UserServiceTarget.a(..))")
//组成部分1:通知方法
//ProceedingJoinPoint 用来调用目标方法
public Object time(ProceedingJoinPoint pjp) throws Throwable {
long start = System.nanoTime();
//调用目标方法
Object proceed = pjp.proceed();
long end = System.nanoTime();
System.out.println("方法运行时间:"+(end-start));
return proceed;
}
}
- 把切面类和目标类都交给Spring容器管理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
>
<!--启用切面编程相关注解 如:@Aspect,@Around 还提供自动产生代理类的功能-->
<aop:aspectj-autoproxy/>
<context:component-scan base-package="service,aspect"/>
</beans>
- 使用切面
从容器getBean根据接口获取,容器返回的是代理对象,代理对象中进行切点的匹配,匹配到了进行通知,通知内部调用目标;
public class TestProxy {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
//实际上调用的是代理类
UserService bean = context.getBean(UserService.class);
System.out.println(bean.getClass());//class com.sun.proxy.$Proxy8
bean.a(10,20);
}
}
切点表达式
- within 匹配类中的所有方法(粒度粗)
语法:
within(包名.类名)
within(service.*ServiceTarget)
- execution 表达式 可以精确到类中的每个方法(粒度细)
语法:
execution(访问修饰符 返回值 包名.类名.方法名字(参数信息))
- 返回值的位置写 * 表示返回值类型是任意的;
- 类名写 * 表示任意的类;
- *写在参数位置只能表示一个参数;
..
写在参数位置,匹配任意个数和任意类型的参数
- @annotation注解方式
@annotation(包名.注解名)
//表示方法上有@Time注解的就通知
@Around("@annotation(aspect.Time)")
通知类型
- @Around 环绕通知 (功能最强大)
- @Before 前置通知:在目标方法调用之前通知
- @After 后置通知
- @AfterReturning 正常返回通知
- @AfterThrowing 异常返回通知
所有的修饰都是针对目标方法;
面向切面的应用
-
spring中的事务管理
代理对象中与@Transactional进行匹配,如果方法上有这个注解,就会调用事务通知,继续调用目标方法TransactionIntercepter跟事务相关的通知代码; -
统一的权限控制
-
缓存控制
-
统一的日志记录
spring中如果目标类没有实现接口
如果目标对象实现了接口:底层spring会调用jdk的动态代理技术来生成代理;要求代理对象和目标对象实现共同的接口;
如果不使用接口,spring通过CGLIB代理技术来生成代理对象,并不要求目标对象和代理对象实现共同接口,底层代理类继承自目标类;
spring中的单元测试
- 添加单元测试的依赖
<!--单元测试的依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.22.RELEASE</version>
<scope>test</scope>
</dependency>
如果有多个类需要测试,为了减少多册注解,建议添加父类,只需注解父类,即可以测试子类;
@RunWith(SpringJUnit4ClassRunner.class) //让单元测试支持test
//指定spring配置文件的位置,让测试类运行时根据此文件创建spring容器
@ContextConfiguration("classpath:spring.xml")
public class AbstractTest {
}
public class Test1 extends AbstractTest{
@Test
public void test(){
System.out.println("123");
}
}