持续学习&持续更新中…
守破离
【Java从零到架构师第③季】【16】Spring-代理
一、生活中的一些问题
二、业务层的一些问题
三、静态代理
假设现在有一个用户登录的业务:
-
UserService(业务层接口):
public interface UserService { boolean login(String username, String password); }
-
UserServiceImpl(核心业务的实现):
public class UserServiceImpl implements UserService { // 核心业务 @Override public boolean login(String username, String password) { // ... // dao等操作 // ... System.out.println("UserService的核心业务"); return "lpruoyu".equals(username) && "123456".equals(password); } }
-
控制层使用(单元测试模拟):
<?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="userService" class="programmer.lp.service.impl.UserServiceImpl"/> </beans>
@Test public void userServiceTest() { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = ctx.getBean("userService", UserService.class); if (userService.login("lpruoyu", "lp_like_programming")) { System.out.println("登陆成功!"); } else { System.out.println("登录失败!"); } }
现在想要在不修改业务层和控制层代码的前提下,为登录业务增加日志记录功能,如何做?使用静态代理:
-
UserServiceProxy:
public class UserServiceProxy implements UserService { private final UserService target; public UserServiceProxy(UserService target) { this.target = target; } @Override public boolean login(String username, String password) { System.out.println("日志-----------------------------------start"); boolean result = target.login(username, password); System.out.println("日志-----------------------------------end"); return result; } }
-
模拟控制层使用UserService的登录业务
只需要修改applicationContext.xml的配置文件即可:
<?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="userService" class="programmer.lp.service.proxy.UserServiceProxy"> <constructor-arg> <bean class="programmer.lp.service.impl.UserServiceImpl"/> </constructor-arg> </bean> </beans>
四、代理
五、代理的两种实现方式
方式一:继承类
SkillService:
public class SkillService {
public void delete(Integer id) {
System.out.println("SkillService-delete-核心业务");
}
}
SkillServiceProxy:
public class SkillServiceProxy extends SkillService {
private SkillService target;
public SkillServiceProxy(SkillService target) {
this.target = target;
}
@Override
public void delete(Integer id) {
System.out.println("日志/事务--------------start");
target.delete(id);
System.out.println("日志/事务--------------end");
}
}
使用:
<bean id="skillService" class="programmer.lp.service.proxy.SkillServiceProxy">
<constructor-arg>
<bean class="programmer.lp.service.SkillService"/>
</constructor-arg>
</bean>
@Test
public void skillService() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
SkillService skillService = ctx.getBean("skillService", SkillService.class);
skillService.delete(10);
}
方式二:实现接口
SkillService:
public interface SkillService {
void delete(Integer id);
}
SkillServiceImpl:
public class SkillServiceImpl implements SkillService {
@Override
public void delete(Integer id) {
System.out.println("SkillService-delete-核心业务");
}
}
SkillServiceProxy:
public class SkillServiceProxy implements SkillService {
private SkillService target;
public SkillServiceProxy(SkillService target) {
this.target = target;
}
@Override
public void delete(Integer id) {
System.out.println("日志/事务--------------start");
target.delete(id);
System.out.println("日志/事务--------------end");
}
}
使用:
<bean id="skillService" class="programmer.lp.service.proxy.SkillServiceProxy">
<constructor-arg>
<bean class="programmer.lp.service.impl.SkillServiceImpl"/>
</constructor-arg>
</bean>
@Test
public void skillService() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
SkillService skillService = ctx.getBean("skillService", SkillService.class);
skillService.delete(10);
}
六、动态代理
注意:
- 采用接口的方式(JDK)来实现动态代理有局限性,因为并不是每个需要代理的目标对象都会实现某个或某些接口
- 采用继承的方式(CGLib)实现动态代理则是通用的,不管目标对象是否实现了接口,都可以使用这种方式。
七、动态代理—JDK(接口)
局限性:目标对象必须实现了一个或多个接口。
基本使用:
// 目标对象
UserService target = ctx.getBean("userService", UserServiceImpl.class);
// 代理对象
UserService userService = (UserService) Proxy.newProxyInstance(
this.getClass().getClassLoader(), // 类加载器,用来通过字节码创建出Class对象
target.getClass().getInterfaces(), // 目标对象实现了的所有接口,生成的代理对象也会实现这些接口
new InvocationHandler() { // 代理对象的附加代码
// 代理方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy-------------------before");
// 调用目标对象的目标方法
Object result = method.invoke(target, args);
System.out.println("proxy----------------------end");
return result; // result:目标对象的目标方法执行后的返回值
}
});
userService.login("lpruoyu", "123456");
userService.register("lpruoyu", "123456", "123");
LogProcessor:
// BeanPostProcessor会拦截每一个bean的生命周期
public class LogProcessor implements BeanPostProcessor {
@Override // 这个bean就是目标对象
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 为目标对象生成代理对象,并将生成的代理对象返回,将该代理对象return后
// 控制层通过getBean方法拿到的就是目标对象的代理对象
return Proxy.newProxyInstance(
this.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
new InvocationHandler() {
// 代理方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy----------日志/事务...---------before");
// 调用目标对象的目标方法
Object result = method.invoke(bean, args);
System.out.println("proxy----------日志/事务...------------end");
return result;
}
});
}
}
改进一下LogProcessor:
public class LogProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return Proxy.newProxyInstance(
getClass().getClassLoader(),
bean.getClass().getInterfaces(),
new LogProcessorInvocationHandler(bean));
}
// 这个类就可以专心写好代理方法(附加代码)了,当然,也可以将该类单独抽取出一个.java文件
private static class LogProcessorInvocationHandler implements InvocationHandler {
Object target;
LogProcessorInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy----------日志/事务...---------before");
Object result = method.invoke(target, args);
System.out.println("proxy----------日志/事务...------------end");
return result;
}
}
}
八、动态代理—CGLib(类)
public class LogProcessor2 implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(getClass().getClassLoader());
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(new LogMethodInterceptor(bean));
return enhancer.create();
}
private static class LogMethodInterceptor implements MethodInterceptor {
Object target;
public LogMethodInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("代理-----------start-----------日志/事务...");
Object result = method.invoke(target, args);
System.out.println("代理-----------end-----------日志/事务...");
return result;
}
}
}
注意
-
使用
BeanPostProcessor
不要忘了在applicationContext.xml中注册 -
BeanPostProcessor会统一处理所有bean的生命周期,所以如果使用BeanPostProcessor来为bean设置代理对象的话,那么需要考虑清楚哪些bean需要代理,哪些bean无需代理。
-
如何考虑?在BeanPostProcessor的postProcessAfterInitialization中判断一下即可:
@Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // 考虑清楚哪些bean需要代理,哪些bean不需要代理 if (!bean.getClass().getName().startsWith("programmer.lp.service.")) { // 不需要代理的bean就会直接被返回 return bean; } // 需要代理的就好执行下面的代码 Enhancer enhancer = new Enhancer(); enhancer.setClassLoader(getClass().getClassLoader()); enhancer.setSuperclass(bean.getClass()); enhancer.setCallback(new LogMethodInterceptor(bean)); return enhancer.create(); }
参考
小码哥-李明杰: Java从0到架构师③进阶互联网架构师.
本文完,感谢您的关注支持!