设计模式
一、代理模式
使用代理类对真实对象进行代理,包括真实对象方法的调用、功能的扩展等。访问的时候也只能访问到代理对象,既保护了真实对象同时可以在原始对象上进行扩展。类似于中介在卖家和买家之间的角色。
代理模式的角色主要有:抽象角色、真实角色、代理角色
1.静态代理
以张三到二手平台售卖二手电脑为例,张三为真实角色,二手平台为代理角色
抽象角色:
public interface User {
void sell();//购买方法
}
真实角色:
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserImpl implements User{
private String name="张三";
private String thing="二手电脑";
@Override
public void sell() {
System.out.println(name+"要卖掉"+thing);
}
}
代理角色:
@Component
public class UserProxy implements User {
UserImpl user;
@Override
public void sell() {
before();
user.sell();
after();
}
//扩展
public void before(){
System.out.println("包装了"+user.getThing());
}
public void after(){
System.out.println("售后服务");
}
}
测试:
@Component
public class StaticProxyTest {
public static void main(String[] args) {
new UserProxy(new UserImpl()).sell();
}
}
看起来似乎很简单,只是加了一层代理类就实现了扩展功能,但实际上维护时非常困难的。如果用户此时新增了需求,要在平台上买东西。那么直接带来了大量的代码工作,效率很低。也就产生了动态代理。
2.动态代理
和静态代理不同的是,动态代理的代理类是spring为我们生成的。相当于mybatisplus和mybatis的区别。
代理类生成工具类
public class DynamicProxy {
/*jdk动态代理
代理类需要是接口实现类impl.getClass().getInterfaces(),接收代理实例也是用接口来接收
通过反射Instance+拦截器Handler实现
jdk自带的代理支持
*/
public static Object jdkProxy(final Object impl){
Object proxyInstance = Proxy.newProxyInstance(impl.getClass().getClassLoader(), impl.getClass()
.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke=null;
System.out.println("商品包装");
invoke = method.invoke(impl, args);
System.out.println("售后服务");
return invoke;
}
});
return proxyInstance;
}
/*CGlib动态代理
通过对类继承来实现,无需接口实现
第三方工具,基于ASM实现
*/
public static Object CGlibProxy(final Object impl){
Object proxyInstance = Enhancer.create(impl.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object invoke = null;
System.out.println("商品包装");
invoke = method.invoke(impl, objects);
System.out.println("售后服务");
return invoke;
}
});
return proxyInstance;
}
}
又或者可以实现对应的接口,以InvocationHandler举例
public class DynamicProxy implements InvocationHandler {
private User user;
public Object getProxyInstance(User user){
this.user=user;
return Proxy.newProxyInstance(user.getClass().getClassLoader(), user.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = null;
System.out.println("商品包装");
invoke = method.invoke(user, args);
System.out.println("售后服务");
return invoke;
}
}//实际使用中只需要调用getProxyInstance方法,丢入一个原始类即可
使用:
public class DynamicProxyTest {
public static void main(String[] args) {
UserImpl user = new UserImpl();
User o = (User)DynamicProxy.jdkProxy(user);
o.sell();
// UserImpl o1 = (UserImpl)DynamicProxy.CGlibProxy(user);
// o1.sell();
}
}
3.Spring AOP
springaop便是动态代理实践的一个典例,不改变方法原有的代码,实现对方法功能的增强,使用aop之前,对aop相关的概念是一定要了解清楚的。
(1)aop相关概念
- 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
- 接入点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
- 切入点(PointCut): 可以插入增强处理的连接点。
- 切面(Aspect): 切面是通知和切点的抽象集合,一般以类的形式呈现。
- 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。
(2)springaop注解开发
由于目前来说,注解开发是最简单快捷的,这里只介绍注解开发,我们只需要知道底层是用动态代理实现的即可。
-
创建切面类和通知
只需要在类上添加@Aspect注解
@Aspect public class MyAspect { public void before(){ System.out.println("方法执行前"); } public void after(){ System.out.println("方法执行后"); } }
-
创建并注入切点
@Aspect public class MyAspect { //表示将com.amlia.service包下的所有的类的所有方法(任何参数)定义为切点 @Pointcut("execution(* com.amlia.service.*.*(..))") public void pointCut(){} @Before("pointCut()") public void before(){ System.out.println("方法执行前"); } //也可以直接在通知上面定义切点 @After("execution(* com.amlia.service.*.*(..))") public void after(){ System.out.println("方法执行后"); } }
-
除了before和after类型的通知外,还有其他类型
@Before:方法执行前通知
@After:方法执行后通知
@Around:方法环绕通知
@AfterReturning:方法返回后通知
@AfterThrowing:方法错误抛出之后
可以测试他们的执行顺序:
(3)aop的应用
-
打印日志(方法执行前后打印参数方法名返回值或者调用关系等信息)
日志级别:
- OFF 关闭日志
- FATAL 较严重的问题,高于ERROR
- ERROR 打印错误信息
- WARN 打印告警信息
- INFO 打印日常信息
- DEBUG 打印调试信息
- TRACE 打印追踪信息
- ALL 打印所有信息
-
性能检测(方法执行前和方法执行后分别进行时间截取求差值)
-
事务控制(抛出错误后进行事务回滚)
-
权限控制(方法执行前检测用户是否有权限)