spring几个有趣的小甜点

文章简介

aop日志,保存登录用户后用自定义注解取出用户,事件编程,事务失效,异步失效,spring中怎么使用callable提高数百倍效率。。。。

aop日志

在你的项目加上下面的代码,com.demo.controller包下的所有类在执行前都会打一条日志
别忘了 <aop:aspectj-autoproxy />

@Component
@Aspect
public class LogAspect {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Pointcut("execution(* com.demo.controller.*.*(..))")
    public void pointCut(){}

    @Before("pointCut()")
    public void before(JoinPoint joinPoint){
        logger.info(joinPoint.getSignature().toString());
    }
}

保存登录用户后用自定义注解取出用户

自定义@User

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface User {
}

UserHandlerMethodArgumentResolver

@Component
public class UserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().isAssignableFrom(UserEntity.class)
                && parameter.hasParameterAnnotation(User.class);
    }

    public Object resolveArgument(MethodParameter methodParameter,
                                  ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest req,
                                  WebDataBinderFactory webDataBinderFactory) throws Exception {
        UserEntity user = (UserEntity)req.getAttribute("user", RequestAttributes.SCOPE_SESSION);
        return user;
    }
}

登录时放入session,可以换成拦截器

@RequestMapping("login")
    public void login(HttpServletRequest req){
        UserEntity user = new UserEntity(1,"lry","123");
        req.getSession().setAttribute("user",user)}

正常使用user的时候,只需要@User

@RequestMapping("user")
    public void user(@User UserEntity user){
        //切面编程注入user
        System.out.println(user);
    }

事件编程

事件

public class Event extends ApplicationEvent {
    public Event(Object source) {
        super(source);
    }
}

发布器

@Component
public class Publisher implements ApplicationEventPublisher {
    @Autowired
    public ApplicationEventPublisher publisher;

    public void publishEvent(ApplicationEvent applicationEvent) {
        publisher.publishEvent(applicationEvent);
    }

    public void publishEvent(Object obj) {
        publisher.publishEvent(obj);
    }
}

监听器/处理器

@Component
public class Listener implements ApplicationListener<Event> {
    protected Logger logger = LoggerFactory.getLogger(getClass());
    public void onApplicationEvent(Event event) {
        //在这里处理买票成功后相关业务逻辑 例如发邮件 发短信
        logger.info("listener:"+event.getSource());
    }
}

如何发布事件

	@Autowired
    public Publisher publisher;
    
	 @RequestMapping("login")
    public void buyTicket(){
        //事件编程 如果买票成功后 相关的发短信发邮件逻辑可以到listener里面写 实现代码解耦
        Event event = new Event("buyTicket success");
        publisher.publishEvent(event);
    }

事务失效

讲事务失效之前先看一个例子

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tran {
}

一个普通接口加上自定义@Tran注解

public interface UserService {
    @Tran
    void anno();
    @Tran
    void anno1();
}

接口实现类

public class UserServiceImpl implements UserService {
    public void anno() {
        System.out.println("anno");
        anno1();//等价 this.anno1(),this压根就不是增强的后的代理对象,只是个普通对象
    }
    public void anno1() {
        System.out.println("anno1");
    }
}

测试类

public class TestTran {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        getService().anno();.//增强anno方法
    }

    public static <T extends UserService> T getService() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        @SuppressWarnings("unchecked")
        final T service = (T) Class.forName("com.demo.service.impl.UserServiceImpl").newInstance();
        @SuppressWarnings("unchecked")
        T proxy = (T) Proxy.newProxyInstance(TestTran.class.getClassLoader(), new Class[]{UserService.class}, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if(method.isAnnotationPresent(Tran.class)){
                    System.out.println("开启事务");
                    Object invoke = method.invoke(service,args);
                    System.out.println("关闭事务");
                    return invoke;
                }else{
                    return method.invoke(service,args);
                }
            }
        });
        return proxy;
    }

}

输出结果

开启事务
anno
anno1
关闭事务

可以看出来anno1压根没有被增强,这是因为anno通过getService()返回代理对象,而anno1只是this,没有获得代理对象。
下面是一个spring事务失效的案例及解决方案
controller 保存订单,发送消息

@RequestMapping("tran")
    public void tran(){
        Order order = new Order();
        order.setName("order1");
        userService.createOrder(order);
    }

service

	@Transactional
    public void createOrder(Order order ) {
        orderMapper.save(order);
        try {
            sendMsg();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
	
    @Transactional(propagation= Propagation.NESTED)
    public void sendMsg(){
        msgMapper.save(new Msg("你已经下单了"));
        //调用短信网关
        throw new RuntimeException();//运行时异常,编译时上层可以不捕捉
    }

浏览器输入tran后会发现订单表和消息表都多了一条数据,订单表多了很正常,因为显示捕捉异常了,但是msg表抛异常了,不应该add进去的啊。这是因为createOrder这样调用sendMsg相当于this.sendMsg,spring根本就没有对sendMsg方法做事务增强。所以事务不会回滚。解决办法如下:
xml 加上 <aop:aspectj-autoproxy expose-proxy=“true”/>

@Transactional
    public void createOrder(Order order ) {
        orderMapper.save(order);
        try {
        	//注意一个加了事务的方法直接调用另一个加了事务的方法会让被调用者事务失效
            UserService proxy= (UserService)AopContext.currentProxy();
            proxy.sendMsg();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

处理结果是order表有数据,msg被回滚,合情合理,我们可以catch再发一遍信息就可以了。

此外要非常注意sendMsg上@Transactional(propagation= Propagation.NESTED)这个注解
如果不指定内层事务的传播方式为NESTED,而是使用默认的REQUIRED,
可能出现org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only异常,原因如下:
多层嵌套事务中,如果使用了默认的事务传播方式,当内层事务抛出异常,外层事务捕捉并正常执行完毕时,就会报出rollback-only异常。
spring框架是使用AOP的方式来管理事务,如果一个被事务管理的方法正常执行完毕,方法结束时spring会将方法中的sql进行提交。如果方法执行过程中出现异常,则回滚。spring框架的默认事务传播方式是PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
在项目中,一般我们都会使用默认的传播方式,这样无论外层事务和内层事务任何一个出现异常,那么所有的sql都不会执行。在嵌套事务场景中,内层事务的sql和外层事务的sql会在外层事务结束时进行提交或回滚。如果内层事务抛出异常e,在内层事务结束时,spring会把事务标记为“rollback-only”。这时如果外层事务捕捉了异常e,那么外层事务方法还会继续执行代码,直到外层事务也结束时,spring发现事务已经被标记为“rollback-only”,但方法却正常执行完毕了,这时spring就会抛出“org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only”。
解决方案:
如果希望内层事务回滚,但不影响外层事务提交,需要将内层事务的传播方式指定为PROPAGATION_NESTED。注:PROPAGATION_NESTED基于数据库savepoint实现的嵌套事务,外层事务的提交和回滚能够控制嵌内层事务,而内层事务报错时,可以返回原始savepoint,外层事务可以继续提交。

异步失效

首先我们得让我们项目支持异步
xml 加入

 <task:annotation-driven executor="exec"/>
 <task:executor id="exec" pool-size="20"/>
 <aop:aspectj-autoproxy expose-proxy="true"/>

web.xml 的DispatcherServlet加入 < async-supported>true< /async-supported>

@RequestMapping("async")
    public void async(){
        userService.testAsync();
        System.out.println(1);
    }
	@Async
    public void testAsync() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
         System.out.println(2);
    }

浏览器输入async后的打印为1 过两秒后打印2,如果不加async ,会等待两秒再打印2 1,证明我们配置正确,异步生效了。

为了测试异步失效,我们改成下面的样子

 @RequestMapping("async")
    public void async(){
        userService.testAsync();
    }
	@Async
    public void testAsync() {
        testAsync1();
        System.out.println("testAsync");
    }

    @Async
    public void testAsync1() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("testAsync1");
    }

浏览器输入async后,如果两个异步都生效了,那么结果应该是testAsync等待两秒testAsync1.而结果确实等待两秒testAsync1 testAsync,这说明异步又失效了,原因和事务失效一模一样。
解决方法

	UserService proxy= (UserService)AopContext.currentProxy();
	proxy.testAsync1();

如果UserService proxy= (UserService)AopContext.currentProxy();报一个Cannot find current proxy: Set ‘exposeProxy’ property on Advised to ‘true’ to make it available.异常,而你又确实加了上面的配置。可以试试下面的办法直接从appContext里面拿。

@Component
public class SpringUtil implements ApplicationContextAware {

	private static ApplicationContext applicationContext = null;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		SpringUtil.applicationContext = applicationContext;
	}

	public static <T> T getBean(Class<T> cla) {
		return applicationContext.getBean(cla);
	}

	public static <T> T getBean(String name, Class<T> cal) {
		return applicationContext.getBean(name, cal);
	}

	public static Object getBean(String name){
		return applicationContext.getBean(name);
	}

	public static String getProperty(String key) {
		return applicationContext.getBean(Environment.class).getProperty(key);
	}
}

//UserService proxy= (UserService)AopContext.currentProxy();
//proxy.testAsync1();
//改成这样拿
SpringUtil.getBean(this.getClass()).testAsync1();

spring中怎么使用callable提高数千倍效率

先配置该配置的东西web.xml

filter-mapping下加入< dispatcher >ASYNC< /dispatcher>
DispatcherServlet 配置 < async-supported>true< /async-supported>

@RequestMapping("call")
    public Callable<String> call(){
        System.out.println("main start "+System.currentTimeMillis());

        Callable<String> callable = new Callable<String>() {
            public String call() throws Exception {
                System.out.println("sub start "+System.currentTimeMillis());
                String result = userService.getUserInfo();
                System.out.println("sub end "+System.currentTimeMillis());
                return result;
            }
        };

        System.out.println("main end "+System.currentTimeMillis());
        return callable;
    }
public String getUserInfo() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "userInfo";
    }

结果

main start 1561024767618
main end 1561024767620
sub start 1561024767629
sub end 1561024768630

可以看到主线程只持有了2ms,子线程花了1001ms。在业务逻辑需要一秒的情况下性能提升了500倍。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值