搜索词>>Throwable 耗时0.0030
  • Java编程Throwable与Exception

    Java编程Throwable与ExceptionException这个类在Java中还是比较常见的。只要你乱来就会有XXXException跑出来找你,Throwable这个估计就见得少了。主要是一般报错也不会说这个玩意儿。好啦说说他们的关系吧。<br /> <span style="color:#ff0000"><strong>Throwable</strong></span><span style="color:#27ae60"><strong>其实是Exception的父类。关系瞬间明确。</strong></span><br /> <br /> 哪里可以见到神龙见首不见尾的Throwable呢?<br /> 一般来说切面编程的环绕通知会有的,例如: <pre> <code class="language-java">import org.springframework.stereotype.Component; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Around; @Component @Aspect public class TestAspect{ @Around(value = "execution(* com.test.controller.*Controller.*(..))") public Object aroundMethod(ProceedingJoinPoint joinpoint)throws Throwable{ Object result=null; try{ result = joinpoint.proceed(); }catch(Throwable e){ throw e; } } }</code></pre>
  • Spring Boot 入门 AOP 日志处理

    Spring Boot 入门 AOP 日志处理,这里主要讲解spring boot 中采用AOP方式进行日志的统一处理。spring 框架的处理也是一样。综合来讲,本文主要讲解了spring boot aop 拦截 AOP预处理日志/AOP后处理日志/AOP环绕日志和异常日志的处理。<h2>引言</h2>     在Java编程开发中,经常会遇到用户业务日志的记录处理,如果每一个地方都去写大量的日志信息,那么工作量也是惊人的。这里主要讲解spring boot 中采用AOP方式进行日志的统一处理。spring mvc框架也可以参考。 <h2>一.spring boot 项目结构图</h2> <br /> <br /> <img alt="spring boot 入门项目结构图" class="img-thumbnail" src="/assist/images/blog/793811a6732a41078b2a90b3fee8b7c4.png" /> <h2>二.代码讲解</h2> <h2>2.1 spring boot mavn pom.xml依赖</h2> pom.xml: <pre> <code class="language-xml"><?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.xqlee.project</groupId> <artifactId>demo-springboot-aop-log</artifactId> <version>2.0</version> <packaging>jar</packaging> <name>demo-springboot-aop-log</name> <description>demo project</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> </code></pre> 这里只是简单的实现AOP日志功能所以只引入了AOP和web包,实际业务情况可能会有数据库操作。 <h3>2.2 spring boot 配置启用AOP</h3> AspectConfig.java: <pre> <code class="language-java">package net.xqlee.project.config; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * 启用切面 * * @author xqlee * */ @Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) public class AspectConfig { } </code></pre> <h3>2.3 spring boot中自定义一个注解,用于处理日志</h3> <pre> <code class="language-java">package net.xqlee.project.aop.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 系统日志记录 * * @author xqlee * */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ServiceLog { /** * 操作类型,新增用户?删除用户 ?调用xx服务?使用接口?... * * @return */ public String operation(); /** * 日志级别 * * @return */ public LogType level() default LogType.INFO; } </code></pre> <pre> <code class="language-java">package net.xqlee.project.aop.annotation; public enum LogType { INFO, WARN, ERROR } </code></pre> <h3>2.4 spring boot 入门之编写处理AOP日志</h3> <pre> <code class="language-java">package net.xqlee.project.aop.aspect; import java.lang.reflect.Method; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import net.xqlee.project.aop.annotation.ServiceLog; @Component @Aspect public class LogAspect { private static final Logger log = LoggerFactory.getLogger(LogAspect.class); /** * 切入点 */ @Pointcut("@annotation(net.xqlee.project.aop.annotation.ServiceLog) ") public void entryPoint() { // 无需内容 } @Before("entryPoint()") public void before(JoinPoint joinPoint) { log.info("=====================开始执行前置通知=================="); try { String targetName = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); Object[] arguments = joinPoint.getArgs(); Class<?> targetClass = Class.forName(targetName); Method[] methods = targetClass.getMethods(); String operation = ""; for (Method method : methods) { if (method.getName().equals(methodName)) { Class<?>[] clazzs = method.getParameterTypes(); if (clazzs.length == arguments.length) { operation = method.getAnnotation(ServiceLog.class).operation();// 操作人 break; } } } StringBuilder paramsBuf = new StringBuilder(); for (Object arg : arguments) { paramsBuf.append(arg); paramsBuf.append("&"); } // *========控制台输出=========*// log.info("[X用户]执行了[" + operation + "],类:" + targetName + ",方法名:" + methodName + ",参数:" + paramsBuf.toString()); log.info("=====================执行前置通知结束=================="); } catch (Throwable e) { log.info("around " + joinPoint + " with exception : " + e.getMessage()); } } /** * 环绕通知处理处理 * * @param joinPoint * @throws Throwable */ @Around("entryPoint()") public Object around(ProceedingJoinPoint point) throws Throwable { // 先执行业务,注意:业务这样写业务发生异常不会拦截日志。 Object result = point.proceed(); try { handleAround(point);// 处理日志 } catch (Exception e) { log.error("日志记录异常", e); } return result; } /** * around日志记录 * * @param point * @throws SecurityException * @throws NoSuchMethodException */ public void handleAround(ProceedingJoinPoint point) throws Exception { Signature sig = point.getSignature(); MethodSignature msig = null; if (!(sig instanceof MethodSignature)) { throw new IllegalArgumentException("该注解只能用于方法"); } msig = (MethodSignature) sig; Object target = point.getTarget(); Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes()); // 方法名称 String methodName = currentMethod.getName(); // 获取注解对象 ServiceLog aLog = currentMethod.getAnnotation(ServiceLog.class); // 类名 String className = point.getTarget().getClass().getName(); // 方法的参数 Object[] params = point.getArgs(); StringBuilder paramsBuf = new StringBuilder(); for (Object arg : params) { paramsBuf.append(arg); paramsBuf.append("&"); } // 处理log。。。。 log.info("[X用户]执行了[" + aLog.operation() + "],类:" + className + ",方法名:" + methodName + ",参数:" + paramsBuf.toString()); } @AfterThrowing(pointcut = "entryPoint()", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint, Throwable e) { // 通过request获取登陆用户信息 // HttpServletRequest request = ((ServletRequestAttributes) // RequestContextHolder.getRequestAttributes()).getRequest(); try { String targetName = joinPoint.getTarget().getClass().getName(); String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); Object[] arguments = joinPoint.getArgs(); Class<?> targetClass = Class.forName(targetName); Method[] methods = targetClass.getMethods(); String operation = ""; for (Method method : methods) { if (method.getName().equals(methodName)) { Class<?>[] clazzs = method.getParameterTypes(); if (clazzs.length == arguments.length) { operation = method.getAnnotation(ServiceLog.class).operation(); break; } } } StringBuilder paramsBuf = new StringBuilder(); for (Object arg : arguments) { paramsBuf.append(arg); paramsBuf.append("&"); } log.info("异常方法:" + className + "." + methodName + "();参数:" + paramsBuf.toString() + ",处理了:" + operation); log.info("异常信息:" + e.getMessage()); } catch (Exception ex) { log.error("异常信息:{}", ex.getMessage()); } } } </code></pre> <h3>三.测试AOP日志</h3> <h3>3.1 编写一个service来进行日志测试</h3> 接口: <pre> <code class="language-java">package net.xqlee.project.service; import java.util.Map; public interface TestService { public boolean insert(Map<String, Object> params, String id); public boolean update(String name, String id); public boolean delete(String id); public boolean doError(String id); } </code></pre> 实现类: <pre> <code class="language-java">package net.xqlee.project.service; import java.util.Map; import org.springframework.stereotype.Service; import net.xqlee.project.aop.annotation.ServiceLog; import net.xqlee.project.aop.annotation.LogType; @Service public class TestServiceImp implements TestService { @ServiceLog(operation = "新增用户信息测试操作。。。。。") @Override public boolean insert(Map<String, Object> params, String id) { return false; } @ServiceLog(operation = "更新用户信息操作....") @Override public boolean update(String name, String id) { return false; } @ServiceLog(operation = "删除操作。。。。") @Override public boolean delete(String id) { return false; } @ServiceLog(operation = "异常操作测试", level = LogType.ERROR) @Override public boolean doError(String id) { try { @SuppressWarnings("unused") int i = 1 / 0; } catch (Exception e) { throw new RuntimeException(e.getMessage()); } return false; } } </code></pre> <h3>3.2 编写并执行spring boot的测试类</h3> <pre> <code class="language-java">package net.xqlee.project; import java.util.HashMap; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import net.xqlee.project.service.TestService; @RunWith(SpringRunner.class) @SpringBootTest public class DemoSpringbootAopLogApplicationTests { @Autowired TestService testService; @Test public void contextLoads() { Map<String, Object> params = new HashMap<>(); params.put("key1", "v1"); params.put("key2", "v2"); testService.insert(params, "000"); testService.update("name", "id"); testService.delete("leftso"); testService.doError("leftso.com"); } } </code></pre> <br /> 执行核心控制台结果:<br /> <img alt="spring boot aop日志执行结果" class="img-thumbnail" src="/assist/images/blog/688a7889d8d24b3f9e60be0d0c4b11ec.png" />从上图中可以看见,spring boot aop方式的日志已经拦截成功。并且注意观察在异常的时候并没有进行around日志拦截,也证实了代码中的备注。<br /> 综合来讲,本文主要讲解了spring boot aop 拦截 AOP预处理日志/AOP后处理日志/AOP环绕日志和异常日志的处理。
  • Spring Boot 异步任务执行程序

    在这篇文章中,我们将讨论有关使用异步任务执行程序功能在不同线程中执行任务的Spring boot异步执行支持。我们将看看在Spring项目中配置SimpleAsyncTaskExecutor,ConcurrentTaskExecutor,ThreadPoolExecutor。除此之外,我们还将研究在Spring中处理异步行为时如何将实际方法返回类型封装到Future对象中。因此,让我们开始使用Spring boot异步任务执行器。<h2>引言</h2> <blockquote> <p style="text-align:left"><span style="color:#212529"><span style="font-family:"Varela Round",sans-serif,Helvetica,Arial">在这篇文章中,我们将讨论有关使用异步任务执行程序功能在不同线程中执行任务的Spring boot 异步执行支持。我们将看看在Spring项目中配置<code>SimpleAsyncTaskExecutor</code>,<code>ConcurrentTaskExecutor</code>,<code>ThreadPoolExecutor</code>。除此之外,我们还将研究在Spring中处理异步行为时如何将实际方法返回类型封装到<code>Future</code>对象中。因此,让我们开始使用Spring boot异步任务执行器。</span></span></p> </blockquote> <div style="text-align:left"> <h2 style="margin-left:0px; margin-right:0px; text-align:left">Spring中异步的配置</h2> <p style="text-align:left"><span style="color:#212529"><span style="font-family:"Varela Round",sans-serif,Helvetica,Arial"><span style="background-color:#ffffff">要在Spring中启用异步行为,请使用<span style="color:#025969">@EnableAsync</span>注释您的配置类。</span></span></span><br />  </p> <pre> <code class="language-java">@EnableAsync @SpringBootApplication public class Application { public static void main(String [] args){ SpringApplication.run(Application.class, args); } }</code></pre> </div> <p style="text-align:left"><span style="color:#212529"><span style="font-family:"Varela Round",sans-serif,Helvetica,Arial"><span style="background-color:#ffffff"><span style="color:#008800">@EnableAsync:</span>它检测@Async注释。</span></span></span></p> <p style="text-align:left"><span style="color:#212529"><span style="font-family:"Varela Round",sans-serif,Helvetica,Arial"><span style="background-color:#ffffff"><span style="color:#008800">模式</span> - mode()属性控制如何应用建议。默认情况下,它的值是<span style="color:#025969">AdviceMode.PROXY</span>。请注意,如果mode()设置为<span style="color:#025969">AdviceMode.ASPECTJ</span>,则proxyTargetClass()属性的值将被忽略。还要注意,在这种情况下,spring-aspects模块JAR必须存在于类路径(classpath)中。</span></span></span></p> <span style="color:#008800"><span style="font-family:"Varela Round",sans-serif,Helvetica,Arial"><span style="background-color:#ffffff">proxyTargetClass</span></span></span> - 它定义了使用CGLIB或JDK的代理类型,默认为CGLIB。 <h2 style="margin-left:0px; margin-right:0px; text-align:left">使用@Async注释</h2> <p style="text-align:left"><span style="color:#212529"><span style="font-family:"Varela Round",sans-serif,Helvetica,Arial"><span style="background-color:#ffffff">此注释在方法级别上用于您希望它执行在单独线程中的那些方法。如果使用此注释标注公共方法,则此注释将按预期工作。</span></span></span></p> <p style="text-align:left"><span style="color:#212529"><span style="font-family:"Varela Round",sans-serif,Helvetica,Arial"><span style="background-color:#ffffff">此外,该方法需要从不同的类中调用,以便它可以被代理,否则代理将被绕过。</span></span></span></p> <p style="text-align:left"><span style="color:#212529"><span style="font-family:"Varela Round",sans-serif,Helvetica,Arial"><span style="background-color:#ffffff">以下是@Async注释方法的示例。它不会返回任何值。</span></span></span></p> <pre> <code class="language-java">@Override @Async public void createUserWithDefaultExecutor(){ //SimpleAsyncTaskExecutor System.out.println("Currently Executing thread name - " + Thread.currentThread().getName()); System.out.println("User created with default executor"); }</code></pre> 默认情况下,Spring将搜索关联的线程池定义:或者是上下文中唯一的TaskExecutor bean,或者是一个名为“taskExecutor”的Executor bean。如果两者都不可解析,则将使用<span style="color:#008800"><span style="font-family:"Varela Round",sans-serif,Helvetica,Arial"><span style="background-color:#ffffff">SimpleAsyncTaskExecutor</span></span></span>来处理异步方法调用 <h2 style="margin-left:0px; margin-right:0px; text-align:left">在方法返回类型中使用@Async注释</h2> <p style="text-align:left"><span style="color:#212529"><span style="font-family:"Varela Round",sans-serif,Helvetica,Arial"><span style="background-color:#ffffff">方法的实际返回类型可以包装在Future对象中。</span></span></span><br />  </p> <pre> <code class="language-java">@Override @Async public Future createAndReturnUser() { System.out.println("Currently Executing thread name - " + Thread.currentThread().getName()); try { User user = new User(); user.setFirstName("John"); user.setLastName("Doe"); user.setGender("Male"); Thread.sleep(5000); return new AsyncResult(user); } catch (InterruptedException e) { System.out.println(e.getMessage()); } return null; }</code></pre> 以下是对此的测试方法。 <pre> <code class="language-java">@Test public void createAndReturnUserTest() throws ExecutionException, InterruptedException { System.out.println("Current Thread in test class " + Thread.currentThread().getName()); long startTime = System.currentTimeMillis(); Future futureUser = userService.createAndReturnUser(); futureUser.get(); assertTrue((System.currentTimeMillis() - startTime) >= 5000); }</code></pre> <h2 style="margin-left:0px; margin-right:0px; text-align:left">在方法级定义ThreadPoolTask​​Executor和ConcurrentTaskExecutor</h2> <p style="text-align:left"><span style="color:#212529"><span style="font-family:"Varela Round",sans-serif,Helvetica,Arial"><span style="background-color:#ffffff">默认情况下,Spring使用<span style="color:#008800">SimpleAsyncTaskExecutor</span>运行用<span style="color:#008800">@Async</span>注释的方法。我们也可以按照以下方式定义我们的自定义执行程序bean并在方法级别使用它。</span></span></span></p> <strong>ThreadPoolTask​​Executor类</strong> <pre> <code class="language-java">@Bean(name = "threadPoolExecutor") public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(7); executor.setMaxPoolSize(42); executor.setQueueCapacity(11); executor.setThreadNamePrefix("threadPoolExecutor-"); executor.initialize(); return executor; } </code></pre> <strong>ConcurrentTaskExecutor</strong> <pre> <code class="language-java">@Bean(name = "ConcurrentTaskExecutor") public TaskExecutor taskExecutor2 () { return new ConcurrentTaskExecutor( Executors.newFixedThreadPool(3)); }</code></pre> 这些bean可以通过以下方式在方法级别使用 <pre> <code class="language-java">@Override @Async("threadPoolExecutor") public void createUserWithThreadPoolExecutor(){ System.out.println("Currently Executing thread name - " + Thread.currentThread().getName()); System.out.println("User created with thread pool executor"); } @Override @Async("ConcurrentTaskExecutor") public void createUserWithConcurrentExecutor(){ System.out.println("Currently Executing thread name - " + Thread.currentThread().getName()); System.out.println("User created with concurrent task executor"); }</code></pre> <p style="text-align:left"><span style="color:#212529"><span style="font-family:"Varela Round",sans-serif,Helvetica,Arial"><span style="background-color:#ffffff">如果你想执行一些长时间执行的任务,例如,如果你想在一天结束时压缩日志文件,SimpleAsyncTaskExecutor在情况下是有意义的。在其他情况下,如果您想每隔n秒或几分钟执行一次执行短时执行的任务,则应该使用ThreadPoolTask​​Executor,因为重用了系统资源。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:left">在应用程序级别实现执行程序</h2> <p style="text-align:left"><span style="color:#212529"><span style="font-family:"Varela Round",sans-serif,Helvetica,Arial"><span style="background-color:#ffffff">要在应用程序级别实现执行程序,我们需要实现AsyncConfigurer并覆盖folowing方法。</span></span></span><br />  </p> <pre> <code class="language-java">@Configuration public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(7); executor.setMaxPoolSize(42); executor.setQueueCapacity(11); executor.setThreadNamePrefix("MyExecutor-"); executor.initialize(); return executor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new AsyncExceptionHandler(); } } </code></pre> <p style="text-align:left"><span style="color:#212529"><span style="font-family:"Varela Round",sans-serif,Helvetica,Arial"><span style="background-color:#ffffff">以下是异常处理程序。</span></span></span></p> <strong>AsyncExceptionHandler.class</strong> <pre> <code class="language-java">public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler { @Override public void handleUncaughtException(Throwable throwable, Method method, Object... obj) { System.out.println("Exception Cause - " + throwable.getMessage()); System.out.println("Method name - " + method.getName()); for (Object param : obj) { System.out.println("Parameter value - " + param); } } }</code></pre>
  • Retrofit 2 如何解析sitemap xml接口/文件

           学习使用Retrofit 2在Android应用程序中解析sitemap (sitemap),使用简单的xml转换器依赖项进行xml解析       学习使用Retrofit 2在Android应用程序中解析sitemap (sitemap),使用简单的xml转换器依赖项进行xml解析。       在此示例中,我们将阅读并解析此博客的sitemap 。下面给出了一个示例条目:$title(Sitemap) <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://leftso.com/retrofit2/retrofit-sync-async-calls/</loc> <lastmod>2019-08-25T22:22:39+00:00</lastmod> <changefreq>monthly</changefreq> <priority>0.8</priority> </url> ... ... </urlset>1.依赖性 为了能够在android中解析sitemap ,我们至少需要两个依赖项,即retrofit和converter-simplexml。$title(build.gradle) dependencies { compile 'com.squareup.retrofit2:retrofit:2.6.1' compile 'com.squareup.retrofit2:converter-simplexml:2.6.1' }$title(pom.xml) <dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>retrofit</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>converter-simplexml</artifactId> <version>2.6.1</version> </dependency>2.sitemap 模型 首先创建用于使用sitemap 条目项的模型。$title(SitemapResponse.java) import java.util.ArrayList; import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Root; @Root(name = "urlset") public class SitemapResponse { @ElementList(name = "url", inline = true) private ArrayList<SitemapEntry> url; @Override public String toString() { return "SitemapResponse [urlset=" + url + "]"; } }$title(SitemapEntry.java) import org.simpleframework.xml.Element; import org.simpleframework.xml.Root; @Root(name="url") public class SitemapEntry { @Element(name="loc") private String loc; @Element(name="lastmod") private String lastmod; @Element(name="changefreq") private String changefreq; @Element(name="priority") private float priority; @Override public String toString() { return "SitemapEntry [loc=" + loc + ", lastmod=" + lastmod + ", changefreq=" + changefreq + ", priority=" + priority + "]"; } }3.sitemap 服务接口 创建服务接口,将通过改造来调用以执行HTTP请求。请注意API URL是sitemap.xml。$title(SitemapService.java) import retrofit2.Call; import retrofit2.http.GET; public interface SitemapService { @GET("sitemap.xml") Call<SitemapResponse> getFeed(); }4.读取sitemap 示例 让我们创建一个改造实例并执行sitemap 请求。给定示例使用异步请求示例。$title(SitemapServiceDemo.java) import java.io.IOException; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.simplexml.SimpleXmlConverterFactory; public class SitemapServiceDemo { private static final String BASE_URL = "https://leftso.com/"; private static Retrofit.Builder builder = new Retrofit.Builder().baseUrl(BASE_URL) .addConverterFactory(SimpleXmlConverterFactory.create()); private static HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor() .setLevel(HttpLoggingInterceptor.Level.BODY); private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); public static void main(String[] args) throws IOException { httpClient.addInterceptor(loggingInterceptor); builder.client(httpClient.build()); Retrofit retrofit = builder.build(); SitemapService SitemapService = retrofit.create(SitemapService.class); Call<SitemapResponse> callAsync = SitemapService.getFeed(); callAsync.enqueue(new Callback<SitemapResponse>() { @Override public void onResponse(Call<SitemapResponse> call, Response<SitemapResponse> response) { if (response.isSuccessful()) { SitemapResponse apiResponse = response.body(); // API response System.out.println(apiResponse); } else { System.out.println("Request Error :: " + response.errorBody()); } } @Override public void onFailure(Call<SitemapResponse> call, Throwable t) { if (call.isCanceled()) { System.out.println("Call was cancelled forcefully"); } else { System.out.println("Network Error :: " + t.getLocalizedMessage()); } } }); } } 程序输出。$title(Console) SitemapResponse [urlset= [SitemapEntry [loc=https://leftso.com/ retrofit2/retrofit-sync-async-calls/, lastmod=2019-08-25T22:22:39+00:00, changefreq=monthly, priority=0.8]. ... ... ...
  • Spring框架事物管理入门教程@Transactional注解使用

    Spring框架是一个非常智能的框架,本文主要讲解spring框架中事物的处理。使用@Transactional注解来标注事物的处理类型等属性<h2>事务有四个特性:ACID</h2> <ul> <li>原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。</li> <li>一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。</li> <li>隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。</li> <li>持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。</li> </ul> Spring框架事务管理涉及的接口的联系如下:<br /> <img alt="1" class="img-thumbnail" src="/assist/images/blog/6bca1098-a091-45f7-8eee-ce32549a556f.jpg" style="height:413px; width:945px" /><br /> @Transactional的属性<br />   <table border="1" class="table table-bordered table-hover"> <tbody> <tr> <td>属性名 </td> <td>类型 </td> <td>说明 </td> </tr> <tr> <td>isolation </td> <td>枚举org.springframework.transaction.annotation.Isolation的值 </td> <td>事务隔离级别 ,默认值Isolation.DEFAULT</td> </tr> <tr> <td>noRollbackFor </td> <td>Class<? extends Throwable>[] </td> <td>一组异常类,遇到时不回滚。默认为{}</td> </tr> <tr> <td>noRollbackForClassName </td> <td>Stirng[] </td> <td>一组异常类名,遇到时不回滚,默认为{}</td> </tr> <tr> <td>propagation </td> <td>枚举org.springframework.transaction.annotation.Propagation的值 </td> <td>事务传播行为 ,默认值 Propagation.REQUIRED</td> </tr> <tr> <td>readOnly </td> <td>boolean </td> <td>事务读写性 ,默认false</td> </tr> <tr> <td>rollbackFor </td> <td>Class<? extends Throwable>[] </td> <td>一组异常类,遇到时回滚 </td> </tr> <tr> <td>rollbackForClassName </td> <td>Stirng[] </td> <td>一组异常类名,遇到时回滚 </td> </tr> <tr> <td>timeout </td> <td>int </td> <td>超时时间,以秒为单位 ,ransactionDefinition.TIMEOUT_DEFAULT</td> </tr> <tr> <td>value </td> <td>String </td> <td>可选的限定描述符,指定使用的事务管理器 </td> </tr> </tbody> </table> <h2><br /> 事务传播行为类型:</h2> <table border="1" class="table table-bordered table-hover"> <tbody> <tr> <td>事务传播行为类型</td> <td>说明</td> </tr> <tr> <td>PROPAGATION_REQUIRED</td> <td>如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。<span style="color:#ff0000">默认值</span></td> </tr> <tr> <td>PROPAGATION_SUPPORTS</td> <td>支持当前事务,如果当前没有事务,就以非事务方式执行。</td> </tr> <tr> <td>PROPAGATION_MANDATORY</td> <td>使用当前的事务,如果当前没有事务,就抛出异常。</td> </tr> <tr> <td>PROPAGATION_REQUIRES_NEW</td> <td>新建事务,如果当前存在事务,把当前事务挂起。</td> </tr> <tr> <td>PROPAGATION_NOT_SUPPORTED</td> <td>以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。</td> </tr> <tr> <td>PROPAGATION_NEVER</td> <td>以非事务方式执行,如果当前存在事务,则抛出异常。</td> </tr> <tr> <td>PROPAGATION_NESTED</td> <td>如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类 似的操作。</td> </tr> </tbody> </table> <h1><br /> 事物隔离级别</h1>   <table class="table table-bordered table-hover" border="0" cellpadding="0" cellspacing="0" style="width:888px"> <tbody> <tr> <td>隔离级别          </td> <td>隔离级别的值</td> <td>导致的问题</td> </tr> <tr> <td>Read-Uncommitted</td> <td>0     </td> <td>导致脏读</td> </tr> <tr> <td>Read-Committed   </td> <td>1     </td> <td>避免脏读,允许不可重复读和幻读</td> </tr> <tr> <td>Repeatable-Read   </td> <td>2     </td> <td>避免脏读,不可重复读,允许幻读</td> </tr> <tr> <td>Serializable   </td> <td>3     </td> <td>串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重</td> </tr> </tbody> </table> <br /> <strong>名词解释:</strong> <ul> <li><strong>脏读:</strong>一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据。如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据。</li> <li><strong>不可重复读</strong>:一个事务中发生了两次读操作,第一次读操作和第二次操作之间,另外一个事务对数据进行了修改,这时候两次读取的数据是不一致的。</li> <li><strong>幻读</strong>:第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。</li> </ul> <br /> 提示: <ul> <li>隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。</li> <li>大多数的数据库默认隔离级别为 Read Commited,比如 SqlServer、Oracle</li> <li>少数数据库默认隔离级别为:Repeatable Read 比如: MySQL InnoDB</li> </ul> <h3><br /> Spring 中的隔离设置</h3> ISOLATION_DEFAULT     这是个 PlatfromTransactionManager <span style="color:#ff0000">默认的隔离级别</span>,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应。<br /> ISOLATION_READ_UNCOMMITTED     这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。<br /> ISOLATION_READ_COMMITTED     保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。<br /> ISOLATION_REPEATABLE_READ     这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。<br /> ISOLATION_SERIALIZABLE     这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。<br /> <br /> <span style="color:#ff0000"><strong>注意:</strong></span><br /> <strong><span style="color:#16a085">1.使用注解事物,必须在spring配置中开启事物<br /> 2.@Transactional注解可以标注在类和方法上,也可以标注在定义的接口和接口方法上。<br /> 如果我们在接口上标注@Transactional注解,会留下这样的隐患:因为注解不能被继承,所以业务接口中标注的@Transactional注解不会被业务实现类继承。所以可能会出现不启动事务的情况。所以,spring建议我们将@Transaction注解在实现类上。<br /> 在方法上的@Transactional注解会覆盖掉类上的@Transactional。</span></strong>
  • spring boot 文件上传 REST风格API ajax方式-Java编程

    Java编程之Spring Boot 文件上传 REST风格API ajax方式<h2>一、摘要说明</h2>   这篇文章将告诉你在spring boot(REST 风格)WEB项目中如何用ajax请求上传文件。<br /> <br />   本文章中需要使用的工具: <ol> <li>Spring Boot 1.4.3.RELEASE</li> <li>Spring 4.3.5.RELEASE</li> <li>Thymeleaf</li> <li>jQuery (webjars)</li> <li>Maven</li> <li>Embedded Tomcat 8.5.6</li> <li>Google Chrome 浏览器(Network Inspect)</li> </ol> <h2>二、项目结构</h2> 一个标准的maven项目结构<br /> <img alt="项目结构" class="img-thumbnail" src="/assist/images/blog/811600c9be364e56b3c68ebbe78e0b07.png" /> <h2>三、项目依赖</h2> 声明一个外部的jQuery webjar依赖项,用于HTML表单中的Ajax请求。<br /> <br /> <strong>pom.xml</strong> <pre> <code class="language-xml"><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mkyong</groupId> <artifactId>spring-boot-file-upload</artifactId> <packaging>jar</packaging> <version>1.0</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.3.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- hot swapping, disable cache for template, enable live reload --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>2.2.4</version> </dependency> </dependencies> <build> <plugins> <!-- Package as an executable jar/war --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project></code></pre> <h2>四、文件上传</h2> 为了支持AJAX请求和响应, 最简单的方式是返回<code>ResponseEntity</code>. <h2>4.1下面的例子演示了上传文件的三种可能方式:</h2> <ol> <li>单个文件上传 – <code>MultipartFile</code></li> <li>多个文件上传– <code>MultipartFile[]</code></li> <li>Map file upload to a Model – <code>@ModelAttribute</code></li> </ol> <strong>RestUploadController.java</strong> <pre> <code class="language-java">import com.mkyong.model.UploadModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @RestController public class RestUploadController { private final Logger logger = LoggerFactory.getLogger(RestUploadController.class); //Save the uploaded file to this folder private static String UPLOADED_FOLDER = "F://temp//"; // 3.1.1 Single file upload @PostMapping("/api/upload") // If not @RestController, uncomment this //@ResponseBody public ResponseEntity<?> uploadFile( @RequestParam("file") MultipartFile uploadfile) { logger.debug("Single file upload!"); if (uploadfile.isEmpty()) { return new ResponseEntity("please select a file!", HttpStatus.OK); } try { saveUploadedFiles(Arrays.asList(uploadfile)); } catch (IOException e) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } return new ResponseEntity("Successfully uploaded - " + uploadfile.getOriginalFilename(), new HttpHeaders(), HttpStatus.OK); } // 3.1.2 Multiple file upload @PostMapping("/api/upload/multi") public ResponseEntity<?> uploadFileMulti( @RequestParam("extraField") String extraField, @RequestParam("files") MultipartFile[] uploadfiles) { logger.debug("Multiple file upload!"); // Get file name String uploadedFileName = Arrays.stream(uploadfiles).map(x -> x.getOriginalFilename()) .filter(x -> !StringUtils.isEmpty(x)).collect(Collectors.joining(" , ")); if (StringUtils.isEmpty(uploadedFileName)) { return new ResponseEntity("please select a file!", HttpStatus.OK); } try { saveUploadedFiles(Arrays.asList(uploadfiles)); } catch (IOException e) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } return new ResponseEntity("Successfully uploaded - " + uploadedFileName, HttpStatus.OK); } // 3.1.3 maps html form to a Model @PostMapping("/api/upload/multi/model") public ResponseEntity<?> multiUploadFileModel(@ModelAttribute UploadModel model) { logger.debug("Multiple file upload! With UploadModel"); try { saveUploadedFiles(Arrays.asList(model.getFiles())); } catch (IOException e) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } return new ResponseEntity("Successfully uploaded!", HttpStatus.OK); } //save file private void saveUploadedFiles(List<MultipartFile> files) throws IOException { for (MultipartFile file : files) { if (file.isEmpty()) { continue; //next pls } byte[] bytes = file.getBytes(); Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename()); Files.write(path, bytes); } } }</code></pre> <h2>4.2选择4.1中的第三种方式上传文件并且使用注解@ModelAttribute</h2> <strong>UploadModel.java</strong> <pre> <code class="language-java">import org.springframework.web.multipart.MultipartFile; public class UploadModel { private String extraField; private MultipartFile[] files; //getters and setters }</code></pre> <h2>五、视图文件的编写</h2> HTML form for multiple file uploads.<br /> <strong>upload.html</strong> <pre> <code class="language-html"><!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <body> <h1>Spring Boot - Multiple file upload example - AJAX</h1> <form method="POST" enctype="multipart/form-data" id="fileUploadForm"> <input type="text" name="extraField"/><br/><br/> <input type="file" name="files"/><br/><br/> <input type="file" name="files"/><br/><br/> <input type="submit" value="Submit" id="btnSubmit"/> </form> <h1>Ajax Post Result</h1> <pre> <span id="result"></span> </pre> <script type="text/javascript" src="webjars/jquery/2.2.4/jquery.min.js"></script> <script type="text/javascript" src="js/main.js"></script> </body> </html></code></pre> <h2>六、jQuery – Ajax Request</h2> jQuery通过f<code>#id获取form</code>,并且发送multipart form 数据通过ajax请求.<br /> <strong>resources/static/js/main.js</strong> <pre> <code class="language-javascript">$(document).ready(function () { $("#btnSubmit").click(function (event) { //stop submit the form, we will post it manually. event.preventDefault(); fire_ajax_submit(); }); }); function fire_ajax_submit() { // Get form var form = $('#fileUploadForm')[0]; var data = new FormData(form); data.append("CustomField", "This is some extra data, testing"); $("#btnSubmit").prop("disabled", true); $.ajax({ type: "POST", enctype: 'multipart/form-data', url: "/api/upload/multi", data: data, //http://api.jquery.com/jQuery.ajax/ //https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects processData: false, //prevent jQuery from automatically transforming the data into a query string contentType: false, cache: false, timeout: 600000, success: function (data) { $("#result").text(data); console.log("SUCCESS : ", data); $("#btnSubmit").prop("disabled", false); }, error: function (e) { $("#result").text(e.responseText); console.log("ERROR : ", e); $("#btnSubmit").prop("disabled", false); } }); }</code></pre> <h2>七、Exception Handler异常拦截</h2> 拦截来自 Ajax请求的异常, 只需要继承 <code>ResponseEntityExceptionHandler</code> 并且返回 <code>ResponseEntity</code>.<br /> <strong>RestGlobalExceptionHandler.java</strong> <pre> <code class="language-java">import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import javax.servlet.http.HttpServletRequest; //http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-error-handling @ControllerAdvice public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler { // Catch file size exceeded exception! @ExceptionHandler(MultipartException.class) @ResponseBody ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) { HttpStatus status = getStatus(request); return new ResponseEntity(ex.getMessage(), status); // example //return new ResponseEntity("success", responseHeaders, HttpStatus.OK); } private HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); if (statusCode == null) { return HttpStatus.INTERNAL_SERVER_ERROR; } return HttpStatus.valueOf(statusCode); } }</code></pre> <h2>八、演示</h2> 使用默认的嵌入式Tomcat启动Spring启动<code>mvn spring-boot:run</code>.<br /> 8.1访问http://localhost:8080/,选择几个文件并单击submit以触发ajax请求。<br /> <img alt="访问并上传" class="img-thumbnail" src="/assist/images/blog/19d6d8c691d04a079fd5a18adc7560fa.png" /><br /> 8.2 Google Chrome浏览器,检查“网络检查”中的请求和响应<br /> <img alt="观察请求" class="img-thumbnail" src="/assist/images/blog/ae51363c32cf415b943dbdc57d659470.png" /><br /> 8.4 Google Chrome, “Request Payload”<br /> <img alt="上传内容" class="img-thumbnail" src="/assist/images/blog/7f1ff5d5dde64d89ae7afd664c5df0f5.png" /> <h2>9.curl工具测试</h2> More testing with <code>cURL</code> command.<br /> 9.1测试单个文件上传 <pre> <code>$ curl -F file=@"f:\\data.txt" http://localhost:8080/api/upload/ Successfully uploaded - data.txt</code></pre> <br /> 9.2.测试多个文件上传 <pre> <code>$ curl -F extraField="abc" -F files=@"f://data.txt" -F files=@"f://data2.txt" http://localhost:8080/api/upload/multi/ Successfully uploaded - data.txt , data2.txt</code></pre> 9.3测试多个文件上传使用maps to Model模式 <pre> <code>$ curl -F extraField="abc" -F files=@"f://data.txt" -F files=@"f://data2.txt" http://localhost:8080/api/upload/multi/model Successfully uploaded! </code></pre> <br /> 9.4测试一个大文件(超过100MB),将会提示下面的错误信息 <pre> <code>$ curl -F file=@"F://movies//300//Sample.mkv" http://localhost:8080/api/upload/ Attachment size exceeds the allowable limit! (10MB)</code></pre> <h2>十、curl测试加自定义错误对象测试</h2> 10.1创建一个自定义对象去存放异常信息<br /> <strong>CustomError.java</strong> <pre> <code class="language-java">public class CustomError { String errCode; String errDesc; public CustomError(String errCode, String errDesc) { this.errCode = errCode; this.errDesc = errDesc; } //getters and setters }</code></pre> 10.2更新全局的异常拦截支持自定义异常信息<br /> <strong>RestGlobalExceptionHandler.java</strong> <pre> <code class="language-java">import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import javax.servlet.http.HttpServletRequest; @ControllerAdvice public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(MultipartException.class) @ResponseBody ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) { HttpStatus status = getStatus(request); return new ResponseEntity(new CustomError("0x000123", "Attachment size exceeds the allowable limit! (10MB)"), status); //return new ResponseEntity("Attachment size exceeds the allowable limit! (10MB)", status); } private HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); if (statusCode == null) { return HttpStatus.INTERNAL_SERVER_ERROR; } return HttpStatus.valueOf(statusCode); } }</code></pre> <br /> 10.3再次上传一个大文件 <pre> <code>$ curl -F file=@"F://movies//300//Sample.mkv" http://localhost:8080/api/upload/ {"errCode":"0x000123","errDesc":"Attachment size exceeds the allowable limit! (10MB)"}</code></pre> <br /> <a href="http://www.mkyong.com/wp-content/uploads/2017/01/spring-boot-file-upload-ajax-rest.zip" rel="external nofollow" target="_blank">demo项目下载</a>
  • Spring AOP 实现原理

    Spring AOP 实现原理基础讲解<h2>什么是AOP</h2> <p>AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。</p> <p> </p> <p>而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。</p> <p> </p> <p>使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”</p> <p> </p> <p>实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。</p> <p> </p> <h2>AOP使用场景</h2> <p>AOP用来封装横切关注点,具体可以在下面的场景中使用:</p> <p> </p> <p>Authentication 权限</p> <p>Caching 缓存</p> <p>Context passing 内容传递</p> <p>Error handling 错误处理</p> <p>Lazy loading 懒加载</p> <p>Debugging  调试</p> <p>logging, tracing, profiling and monitoring 记录跟踪 优化 校准</p> <p>Performance optimization 性能优化</p> <p>Persistence  持久化</p> <p>Resource pooling 资源池</p> <p>Synchronization 同步</p> <p>Transactions 事务</p> <p> </p> <h2>AOP相关概念</h2> <p>方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用spring的 Advisor或拦截器实现。</p> <p> </p> <p>连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。</p> <p> </p> <p>通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice</p> <p> </p> <p>切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上</p> <p> </p> <p>引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口</p> <p> </p> <p>目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO</p> <p> </p> <p>AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。</p> <p> </p> <p>织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯JavaAOP框架一样,在运行时完成织入。</p> <p> </p> <h2>Spring AOP组件</h2> <p>下面这种类图列出了Spring中主要的AOP组件<br /> <img alt="Spring AOP结构图" class="img-thumbnail" src="/assist/images/blog/1e67c550-0908-411b-a4e1-77f8eed8fddd.png" style="height:1770px; width:2206px" /></p> <h2>如何使用Spring AOP</h2>   <p>可以通过配置文件或者编程的方式来使用Spring AOP。</p> <p> </p> <p>配置可以通过xml文件来进行,大概有四种方式:</p> <p>1.        配置ProxyFactoryBean,显式地设置advisors, advice, target等</p> <p>2.        配置AutoProxyCreator,这种方式下,还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象</p> <p>3.        通过<aop:config>来配置</p> <p>4.        通过<aop: aspectj-autoproxy>来配置,使用AspectJ的注解来标识通知及切入点</p> <p> </p> <p>也可以直接使用ProxyFactory来以编程的方式使用Spring AOP,通过ProxyFactory提供的方法可以设置target对象, advisor等相关配置,最终通过 getProxy()方法来获取代理对象</p> <p> </p> <p>具体使用的示例可以google. 这里略去</p> <p> </p> <h2>Spring AOP代理对象的生成</h2>   <p>Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。下面我们来研究一下Spring如何使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中,直接上相关代码:<br />  </p> <pre> <code class="language-java">/** * <ol> * <li>获取代理类要实现的接口,除了Advised对象中配置的,还会加上SpringProxy, Advised(opaque=false) * <li>检查上面得到的接口中有没有定义 equals或者hashcode的接口 * <li>调用Proxy.newProxyInstance创建代理对象 * </ol> */ public Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " +this.advised.getTargetSource()); } Class[] proxiedInterfaces =AopProxyUtils.completeProxiedInterfaces(this.advised); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } </code></pre> <p>那这个其实很明了,注释上我也已经写清楚了,不再赘述。</p> <p> </p> <p>下面的问题是,代理对象生成了,那切面是如何织入的?</p> <p>我们知道InvocationHandler是JDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。而通过JdkDynamicAopProxy的签名我们可以看到这个类其实也实现了InvocationHandler,下面我们就通过分析这个类中实现的invoke()方法来具体看下Spring AOP是如何织入切面的。<br />  </p> <pre> <code class="language-java">publicObject invoke(Object proxy, Method method, Object[] args) throwsThrowable { MethodInvocation invocation = null; Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; Class targetClass = null; Object target = null; try { //eqauls()方法,具目标对象未实现此方法 if (!this.equalsDefined && AopUtils.isEqualsMethod(method)){ return (equals(args[0])? Boolean.TRUE : Boolean.FALSE); } //hashCode()方法,具目标对象未实现此方法 if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)){ return newInteger(hashCode()); } //Advised接口或者其父接口中定义的方法,直接反射调用,不应用通知 if (!this.advised.opaque &&method.getDeclaringClass().isInterface() &&method.getDeclaringClass().isAssignableFrom(Advised.class)) { // Service invocations onProxyConfig with the proxy config... return AopUtils.invokeJoinpointUsingReflection(this.advised,method, args); } Object retVal = null; if (this.advised.exposeProxy) { // Make invocation available ifnecessary. oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } //获得目标对象的类 target = targetSource.getTarget(); if (target != null) { targetClass = target.getClass(); } //获取可以应用到此方法上的Interceptor列表 List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,targetClass); //如果没有可以应用到此方法的通知(Interceptor),此直接反射调用 method.invoke(target, args) if (chain.isEmpty()) { retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args); } else { //创建MethodInvocation invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); retVal = invocation.proceed(); } // Massage return value if necessary. if (retVal != null && retVal == target &&method.getReturnType().isInstance(proxy) &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { // Special case: it returned"this" and the return type of the method // is type-compatible. Notethat we can't help if the target sets // a reference to itself inanother returned object. retVal = proxy; } return retVal; } finally { if (target != null && !targetSource.isStatic()) { // Must have come fromTargetSource. targetSource.releaseTarget(target); } if (setProxyContext) { // Restore old proxy. AopContext.setCurrentProxy(oldProxy); } } } </code></pre> <p>主流程可以简述为:获取可以应用到此方法上的通知链(Interceptor Chain),如果有,则应用通知,并执行joinpoint; 如果没有,则直接反射执行joinpoint。而这里的关键是通知链是如何获取的以及它又是如何执行的,下面逐一分析下。</p> <p> </p> <p>首先,从上面的代码可以看到,通知链是通过Advised.getInterceptorsAndDynamicInterceptionAdvice()这个方法来获取的,我们来看下这个方法的实现:<br />  </p> <pre> <code class="language-java">public List<Object>getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) { MethodCacheKeycacheKey = new MethodCacheKey(method); List<Object>cached = this.methodCache.get(cacheKey); if(cached == null) { cached= this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice( this,method, targetClass); this.methodCache.put(cacheKey,cached); } returncached; } </code></pre> <p>可以看到实际的获取工作其实是由AdvisorChainFactory. getInterceptorsAndDynamicInterceptionAdvice()这个方法来完成的,获取到的结果会被缓存。</p> <p>下面来分析下这个方法的实现:<br />  </p> <pre> <code class="language-java">/** * 从提供的配置实例config中获取advisor列表,遍历处理这些advisor.如果是IntroductionAdvisor, * 则判断此Advisor能否应用到目标类targetClass上.如果是PointcutAdvisor,则判断 * 此Advisor能否应用到目标方法method上.将满足条件的Advisor通过AdvisorAdaptor转化成Interceptor列表返回. */ publicList getInterceptorsAndDynamicInterceptionAdvice(Advised config, Methodmethod, Class targetClass) { // This is somewhat tricky... we have to process introductions first, // but we need to preserve order in the ultimate list. List interceptorList = new ArrayList(config.getAdvisors().length); //查看是否包含IntroductionAdvisor boolean hasIntroductions = hasMatchingIntroductions(config,targetClass); //这里实际上注册一系列AdvisorAdapter,用于将Advisor转化成MethodInterceptor AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); Advisor[] advisors = config.getAdvisors(); for (int i = 0; i <advisors.length; i++) { Advisor advisor = advisors[i]; if (advisor instanceof PointcutAdvisor) { // Add it conditionally. PointcutAdvisor pointcutAdvisor= (PointcutAdvisor) advisor; if(config.isPreFiltered() ||pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) { //TODO: 这个地方这两个方法的位置可以互换下 //将Advisor转化成Interceptor MethodInterceptor[]interceptors = registry.getInterceptors(advisor); //检查当前advisor的pointcut是否可以匹配当前方法 MethodMatcher mm =pointcutAdvisor.getPointcut().getMethodMatcher(); if (MethodMatchers.matches(mm,method, targetClass, hasIntroductions)) { if(mm.isRuntime()) { // Creating a newobject instance in the getInterceptors() method // isn't a problemas we normally cache created chains. for (intj = 0; j < interceptors.length; j++) { interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptors[j],mm)); } } else { interceptorList.addAll(Arrays.asList(interceptors)); } } } } else if (advisor instanceof IntroductionAdvisor){ IntroductionAdvisor ia =(IntroductionAdvisor) advisor; if(config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) { Interceptor[] interceptors= registry.getInterceptors(advisor); interceptorList.addAll(Arrays.asList(interceptors)); } } else { Interceptor[] interceptors =registry.getInterceptors(advisor); interceptorList.addAll(Arrays.asList(interceptors)); } } return interceptorList; } </code></pre> <p>这个方法执行完成后,Advised中配置能够应用到连接点或者目标类的Advisor全部被转化成了MethodInterceptor.</p> <p> </p> <p>接下来我们再看下得到的拦截器链是怎么起作用的。</p> <pre> <code class="language-java">if (chain.isEmpty()) { retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args); } else { //创建MethodInvocation invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); retVal = invocation.proceed(); } </code></pre>    从这段代码可以看出,如果得到的拦截器链为空,则直接反射调用目标方法,否则创建MethodInvocation,调用其proceed方法,触发拦截器链的执行,来看下具体代码 <pre> <code class="language-java">public Object proceed() throws Throwable { // We start with an index of -1and increment early. if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()- 1) { //如果Interceptor执行完了,则执行joinPoint return invokeJoinpoint(); } Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); //如果要动态匹配joinPoint if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher){ // Evaluate dynamic method matcher here: static part will already have // been evaluated and found to match. InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice; //动态匹配:运行时参数是否满足匹配条件 if (dm.methodMatcher.matches(this.method, this.targetClass,this.arguments)) { //执行当前Intercetpor returndm.interceptor.invoke(this); } else { //动态匹配失败时,略过当前Intercetpor,调用下一个Interceptor return proceed(); } } else { // It's an interceptor, so we just invoke it: The pointcutwill have // been evaluated statically before this object was constructed. //执行当前Intercetpor return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } } </code></pre> <p>代码也比较简单,这里不再赘述。</p>