二、AOP
这一章节开始讲述有关
AOP
相关的注解内容,即面向切面变成,AOP
可以很方便的在某个方法某个业务逻辑前后,无侵入式的加入逻辑代码或者日志或者权限等
1. @Aspect
自定义切面
该注解可以用来标注一个类,则该类会被识别成一个
AOP
类,同时该类必须也需要交由容器管理,如使用@Component
,切面包含了一系列的注解来定义切面语法如@Pointcut
,
定义了切面方法的执行时机,如@Before
@EnableAspectJAutoProxy
开启基于注解的aop模式,如果使用aop则必须在配置类上加入这个注解
@Aspect
表明当前是一个切面类,需要配合@Component等类似功能注解,交由容器管理
@Pointcut
定义一个切面语法,该语法直接影响到当前类的实际作用范围
@Before
在目标方法执行之前执行
@After
在目标方法执行结束之后执行
@AfterReturning
在结果值返回之后,可以在这里获取到方法的返回值,如果在结果值返回之前出现i异常,则这个方法不会被执行
@AfterThrowing
如果方法出现异常,则执行该方法
@Around
环绕通知,在使用了以上注解,再使用这个之后,发现方法正常执行后,结果值不能正确获取,方法出现异常,依然能执行到@AfterReturning,而且控制台异常被屏蔽,这个我没去研究
- 使用
@Aspect
定义一个切面类,并使用@Component
将该类交由容器管理,定义切面语法
package com.ddf.spring.annotation.configuration;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Arrays;
/**
* @author DDf on 2018/8/10
*
* 标注一个AOP切面类
* @Aspect 表明当前是一个切面类
* @Component 必须将切面类交由容器管理,同时切面类也仅对同在容器中的类提供支持,仅仅满足切面语法不在类却不在容器中也是不行的,
*
* 还有一个很重要的地方,切面类的语法不能包含本切面类,如果本切面类也满足自己定义的切面语法,会出现问题
*
*
*/
@Aspect
@Component
public class LogAspect {
/**
* 使用@Pointcut来定义一个通用的切面表达式,方法体不会被执行,而别的方法切面类可以直接引用当前方法来复用表达式
* 切面语法:
* 首先是固定写法execution(访问修饰符 返回值 类路径 方法(参数1,参数2等))
*
* *的省略原则
* 如果有一层是希望不加以限制的,则可以使用*代替,而如果连续的两个位置都不加以限制,则可以直接使用一个*号
*
* 包与子包
* 如果类路径只限制到某个包下所有类则直接包名然后.*即可,但如果指定到某个包,某个包下面本身有类,而包下面又有子包,
* 则可以指定到当前包之后使用两个.然后再*,如..*
*
* 参数的省略
* 如果明确指定两个参数并且指定类型,则可以(string,string)
* 但是如果一个参数固定,另外一个不加以限制,则可以(string,*)
* 如果参数类型和数量都不加以限制,则可以(..)
*
* 如下示例,则拦截com.ddf.spring.annotation.service包以及所有子包下的所有public的方法(返回值不限,参数不限)
*
*/
@Pointcut("execution(public * com.ddf.spring.annotation.service..*.*(..))")
public void pointcut() {};
/**
* 在目标方法执行之前执行,获取参数列表可以获取到Body参数,可以获取到Body参数,可以获取到Body参数!
* @param joinPoint
*/
@Before("pointcut()")
public void beforeLog(JoinPoint joinPoint) {
// 可以获取到Body参数,可以获取到Body参数,可以获取到Body参数!
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName + "开始执行,参数列表" + Arrays.asList(args));
}
/**
* 在目标方法执行结束之后执行
* @param joinPoint
*/
@After("pointcut()")
public void afterLog(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName + "结束执行,参数列表" + Arrays.asList(args));
}
/**
* 在结果值返回之后,可以在这里获取到方法的返回值,如果在结果值返回之前出现i异常,则这个方法不会被执行
* @param joinPoint 这个参数要保证在第一位
* @param result 返回结果
*/
@AfterReturning(value = "pointcut()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName + "结束执行,参数列表" + Arrays.asList(args) + ",返回结果: " + result);
}
/**
* 如果方法出现异常,则执行该方法
* @param joinPoint
* @param exception
*/
@AfterThrowing(value="pointcut()",throwing="exception")
public void logException(JoinPoint joinPoint,Exception exception){
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName + "结束执行,参数列表" + Arrays.asList(args) + ",出现异常: " + exception);
}
}
- 修改
UserService.java
,加入两个验证方法,一定要希望被拦截的目标类也必须被Spring
管理
package com.ddf.spring.annotation.service;
import org.springframework.stereotype.Service;
/**
* @author DDf on 2018/7/19
* 默认@Scope为singleton,IOC容器启动会创建该bean的实例,并且以后再次使用不会重新创建新的实例
*/
@Service
public class UserService {
public UserService() {
System.out.println("UserService创建完成...................");
}
public String welcome(String userName) {
return "Hello " + userName;
}
public String welcomeException(String userName) {
throw new RuntimeException("出现异常");
}
}
- 修改主配置类
AnnotationConfiguration.java
,加入@EnableAspectJAutoProxy
,开启基于注解的AOP
代理
package com.ddf.spring.annotation.configuration;
import com.ddf.spring.annotation.bean.*;
import com.ddf.spring.annotation.entity.User;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
/**
* @author DDf on 2018/7/19
* @Configuration 表明当前类是一个配置类
* @ComponentScan 指定扫描的包路径,并且配置了excludeFilters来排除注解类型为@Controller的不纳入容器中,
* 排除符合自定义ExcludeTypeFilter类中规则的类
* @Import 导入组件,可以直接导入普通类,或者通过ImportSelector接口或者ImportBeanDefinitionRegistrar接口来自定义导入
*
* @EnableAspectJAutoProxy 开启基于注解的aop模式
*/
@Configuration
@ComponentScan(value = "com.ddf.spring.annotation", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class),
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = ExcludeTypeFilter.class)
})
@Import(value = {ImportBean.class, CustomImportSelector.class, CustomImportBeanDefinitionRegistrar.class})
@EnableAspectJAutoProxy
public class AnnotationConfiguration {
/**
* 注入一个Type为User(方法返回值)的bean,bean的名称为user(方法名)
* initMethod 指定Bean创建后调用的初始化方法
* destroyMethod 指定Bean在销毁后会调用的方法
* @return
*/
@Bean(initMethod = "init", destroyMethod = "destory")
public User user() {
return new User();
}
/**
* 测试@Conditional 满足{@link DevelopmentProfileCondition} 这个类的条件返回true则当前Bean能够成功注入,反之不能
*
* @return
*/
@Bean
@Conditional({DevelopmentProfileCondition.class})
public DevelopmentBean developmentService() {
return new DevelopmentBean();
}
/**
* 满足{@link ProductionProfileCondition} 这个类的条件返回true则当前Bean能够成功注入,反之不能
*
* @return
*/
@Bean
@Conditional({ProductionProfileCondition.class})
public ProductionBean productionService() {
return new ProductionBean();
}
/**
* 使用FactoryBean工厂来注册组件
* @return
*/
@Bean
public FactoryPrototypeBeanConfiguration factoryPrototypeBeanConfiguration() {
return new FactoryPrototypeBeanConfiguration();
}
/**
* 使用FactoryBean工厂来注册组件
* @return
*/
@Bean
public FactorySingletonBeanConfiguration factorySingletonBeanConfiguration() {
return new FactorySingletonBeanConfiguration();
}
/**
* 注册一个实现InitializingBean, DisposableBean接口来指定Bean的初始化和销毁方法的Bean
* @return
*/
@Bean
public InitAndDisposableBean initAndDisposableBean() {
return new InitAndDisposableBean();
}
/**
* 创建一个通过JSR250 @PostConstruct指定初始化方法/@PreDestroy指定销毁方法的Bean
* @return
*/
@Bean
public PostConstructAndPreDestoryBean postConstructAndPreDestoryBean() {
return new PostConstructAndPreDestoryBean();
}
/**
* 注入AutowiredBean,名称为autowiredBean2,并将该bean作为默认依赖注入的首选
* @return
*/
@Bean
@Primary
public AutowiredBean autowiredBean2() {
return new AutowiredBean();
}
}
- 修改主启动类
Application.java
,增加测试方法testAspect
package com.ddf.spring.annotation;
import com.ddf.spring.annotation.bean.AutowiredBean;
import com.ddf.spring.annotation.bean.FactoryPrototypeBean;
import com.ddf.spring.annotation.bean.FactorySingletonBean;
import com.ddf.spring.annotation.bean.Slf4jBean;
import com.ddf.spring.annotation.configuration.AnnotationConfiguration;
import com.ddf.spring.annotation.configuration.ProfileConfiguration;
import com.ddf.spring.annotation.entity.User;
import com.ddf.spring.annotation.service.AutowiredService;
import com.ddf.spring.annotation.service.LazyBeanService;
import com.ddf.spring.annotation.service.PrototypeScopeService;
import com.ddf.spring.annotation.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author DDf on 2018/7/19
*/
public class Application {
public static void main(String[] args) {
System.out.println("-----------------------IOC容器初始化-------------------------");
// 创建一个基于配置类启动的IOC容器,如果主配置类扫描包的路径下包含其他配置类,则其他配置类可以被自动识别
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnnotationConfiguration.class);
System.out.println("-----------------------IOC容器初始化完成-------------------------\n");
// 获取当前IOC中所有bean的名称,即使是懒加载类型的bean也会获取到
printBeans(applicationContext);
// 测试@Scope bean的作用域
testPrototypeScopeService(applicationContext);
// 测试单实例bean的@Lazy懒加载
testLazyBeanService(applicationContext);
// 测试FactoryBean接口导入单实例与Prototype作用域的组件
testFactoryBeanPrototypeBean(applicationContext);
// 测试@PropertySource和@Value属性赋值
testPropertySourceValue(applicationContext);
// 测试@Autowired注入多个相同类型的Bean
testAutowired(applicationContext);
// 测试@Profile根据环境注入Bean
testProfile(applicationContext);
// 测试AOP
testAspect(applicationContext);
// 销毁容器
applicationContext.close();
}
/**
* 打印当前IOC容器中所有预定义的Bean
* @param applicationContext
*/
private static void printBeans(AnnotationConfigApplicationContext applicationContext) {
String[] definitionNames = applicationContext.getBeanDefinitionNames();
// 打印当前IOC中对应名称的bean和bean的类型
for (String name : definitionNames) {
// 这个会影响到测试懒加载的效果,如果需要测试懒加载,这行代码需要注释掉,因为getBean方法一旦调用则会初始化
Object bean = applicationContext.getBean(name);
System.out.println("bean name:" + name + ", type: " + bean.getClass());
}
}
/**
* 测试@Scope bean的作用域
*
* @param applicationContext
*/
public static void testPrototypeScopeService(ApplicationContext applicationContext) {
System.out.println("\n-----------------------测试@Scope开始-------------------------");
UserService userService = (UserService) applicationContext.getBean("userService");
UserService userService1 = applicationContext.getBean(UserService.class);
System.out.println("默认单实例bean UserService是否相等 " + (userService == userService1));
PrototypeScopeService prototypeScopeService = applicationContext.getBean(PrototypeScopeService.class);
PrototypeScopeService prototypeScopeService1 = applicationContext.getBean(PrototypeScopeService.class);
System.out.println("PrototypeScopeService prototype scope作用域是否相等: " + (prototypeScopeService == prototypeScopeService1));
System.out.println("-----------------------测试@Scope结束-------------------------\n");
}
/**
* 测试单实例bean的懒加载,只有等使用的时候再创建实例。
* IOC容器启动后不会创建该bean的实例,如果是在该方法中才创建这个bean的实例,并且获得的两个bean是同一个的话,则测试通过。
*/
public static void testLazyBeanService(ApplicationContext applicationContext) {
System.out.println("\n---------------测试单实例bean的@Lazy懒加载开始----------------------");
LazyBeanService lazyBeanService = applicationContext.getBean(LazyBeanService.class);
LazyBeanService lazyBeanService1 = applicationContext.getBean(LazyBeanService.class);
System.out.println("lazyBeanService==lazyBeanService1?: " + (lazyBeanService == lazyBeanService1));
System.out.println("---------------测试单实例bean的@Lazy懒加载结束----------------------\n");
}
/**
* 测试通过FactoryBean接口导入单实例与Prototype作用域的组件,根据打印可以看出FactoryBean创建的单实例Bean都是懒加载的
* @param applicationContext
*/
public static void testFactoryBeanPrototypeBean(ApplicationContext applicationContext) {
System.out.println("\n----------测试通过FactoryBean注册单实例和Prototype作用域的组件开始----------");
FactorySingletonBean factorySingletonBean = applicationContext.getBean(FactorySingletonBean.class);
FactorySingletonBean factorySingletonBean1 = applicationContext.getBean(FactorySingletonBean.class);
FactoryPrototypeBean factoryPrototypeBean = applicationContext.getBean(FactoryPrototypeBean.class);
FactoryPrototypeBean factoryPrototypeBean1 = applicationContext.getBean(FactoryPrototypeBean.class);
System.out.println("单实例factorySingletonBean==factorySingletonBean1?" + (factorySingletonBean==factorySingletonBean1));
System.out.println("Prototype作用域factoryPrototypeBean==factoryPrototypeBean1?" + (factoryPrototypeBean==factoryPrototypeBean1));
System.out.println("----------测试通过FactoryBean注册单实例和Prototype作用域的组件结束----------\n");
}
/**
* 测试通过@PropertySource和@Value注解来对属性进行赋值
* @param applicationContext
*/
public static void testPropertySourceValue(ApplicationContext applicationContext) {
System.out.println("\n---------------测试@PropertySource和@Value赋值开始----------------");
User user = applicationContext.getBean(User.class);
System.out.println("user属性为: " + user.toString());
System.out.println("---------------测试@PropertySource和@Value赋值结束----------------\n");
}
/**
* 测试在IOC容器中存在两个相同类型的Bean,但是Bean的名称不一致
* 在这种情况下,使用@Autowired将该Bean注入到另外一个容器中
* @Autowired 默认使用Bean的类型去匹配注入,如果找到多个相同类型的Bean,则使用默认名称去获取Bean,Bean的默认名称为类首字母缩写
* 也可以配合@Autowired配合@Qualifier注解明确指定注入哪个Bean,还可以在注入Bean的地方使用@Primary,在不指定@Qualifier的情况下,
* 默认注入哪个Bean {@link AutowiredService}
* @param applicationContext
*/
public static void testAutowired(ApplicationContext applicationContext) {
System.out.println("\n--------------测试autowired注入多个相同类型的类开始-----------------");
AutowiredBean autowiredBean = (AutowiredBean) applicationContext.getBean("autowiredBean");
AutowiredBean autowiredBean2 = (AutowiredBean) applicationContext.getBean("autowiredBean2");
System.out.println("autowiredBean: " + autowiredBean);
System.out.println("autowiredBean2: " + autowiredBean2);
System.out.println(autowiredBean == autowiredBean2);
/**
* 这里已做更改,修改了默认注入 {@link com.ddf.spring.annotation.configuration.AnnotationConfiguration.autowiredBean2}
*/
AutowiredService autowiredService = applicationContext.getBean(AutowiredService.class);
AutowiredBean autowiredServiceBean = autowiredService.getAutowiredBean();
System.out.println("使用@Primay后AutowiredService默认注入bean: " + autowiredServiceBean);
AutowiredBean autowiredServiceBean2 = autowiredService.getQualifierAutowiredBean();
System.out.println("使用@Qualifier明确注入Bean: " + autowiredServiceBean2);
// 使用@Resource注入
AutowiredBean resourceAutowiredBean = autowiredService.getResourceAutowiredBean();
System.out.println("使用@Resource注入autowiredBean: " + resourceAutowiredBean);
AutowiredBean resourceAutowiredBean2 = autowiredService.getResourceAutowiredBean2();
System.out.println("使用@Resource注入autowiredBean2: " + resourceAutowiredBean2);
// 使用@Inject注入
UserService userService = autowiredService.getUserService();
System.out.println("使用@Inject注入UserService: " + userService);
System.out.println("--------------测试autowired注入多个相同类型的类结束-----------------\n");
}
/**
* 测试根据激活的Profile来根据环境注册不同的Bean
* 切换profile有两种方式:
* 1. 在java虚拟机启动参数加 -Dspring.profiles.active=test
* 2. 如下演示,使用代码切换,可以将切换的变量放在配置文件,spring-boot配置文件即是这种方式
* @param applicationContext
*/
public static void testProfile(AnnotationConfigApplicationContext applicationContext) {
System.out.println("\n------------------测试@Profile开始-------------------------");
// 重新新建一个IOC容器
applicationContext = new AnnotationConfigApplicationContext();
// 注册配置类
applicationContext.register(ProfileConfiguration.class);
// 设置当前激活的环境profile
applicationContext.getEnvironment().setActiveProfiles("dev");
// 刷新容器
applicationContext.refresh();
// 使用接口获得实际接口实现类的注入Bean
Slf4jBean devLogBean = applicationContext.getBean(Slf4jBean.class);
devLogBean.info("测试环境");
applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(ProfileConfiguration.class);
applicationContext.getEnvironment().setActiveProfiles("prd");
applicationContext.refresh();
Slf4jBean prdLogBean = applicationContext.getBean(Slf4jBean.class);
prdLogBean.info("生产环境");
System.out.println("------------------测试@Profile结束-------------------------\n");
}
/**
* 测试AOP
* @param applicationContext
*/
public static void testAspect(ApplicationContext applicationContext) {
System.out.println("\n--------------------测试AOP开始----------------------------");
UserService userService = applicationContext.getBean(UserService.class);
userService.welcome("ddf");
userService.welcomeException("ddf");
System.out.println("--------------------测试AOP结束----------------------------\n");
}
}
- 启动主启动类
Application.java
,查看控制台日志如下,可以看到,UserService
的welcome
方法顺利执行@Before
、@After
、AfterReturning
方法,并且接收到了返回值,但是welcomeException
方法出现了异常,只执行了@Before
方法和@After
方法,AfterReturning
方法没有执行取而代之执行了@AfterThrowing
方法
// 省略其它不相干日志
--------------------测试AOP开始----------------------------
welcome开始执行,参数列表[ddf]
welcome结束执行,参数列表[ddf]
welcome结束执行,参数列表[ddf],返回结果: Hello ddf
welcomeException开始执行,参数列表[ddf]
welcomeException结束执行,参数列表[ddf]
welcomeException结束执行,参数列表[ddf],出现异常: java.lang.RuntimeException: 出现异常
Exception in thread "main" java.lang.RuntimeException: 出现异常
at com.ddf.spring.annotation.service.UserService.welcomeException(UserService.java:20)
at com.ddf.spring.annotation.service.UserService$$FastClassBySpringCGLIB$$851f891e.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:52)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:47)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:52)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
at com.ddf.spring.annotation.service.UserService$$EnhancerBySpringCGLIB$$7f50f9a0.welcomeException(<generated>)
at com.ddf.spring.annotation.Application.testAspect(Application.java:206)
at com.ddf.spring.annotation.Application.main(Application.java:41)
Process finished with exit code 1
2. 事务控制
本节演示如何使用注解版的事务,一般使用事务的前提为:
1. 必须有数据源连接DataSource
2. 必须有事务管理器,并且将DataSource
注入到事务管理器中
2.1 配置数据源与事务支持
I. 使用mysql
和druid
来连接数据库,在之前的pom.xml
中已经导入依赖
II. 创建数据库,sql需要分开执行,先建立数据库,然后进入到数据库在执行建表语句
CREATE DATABASE `spring-annotation` CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `spring-annotation`
DROP TABLE IF EXISTS PERSON;
CREATE TABLE PERSON(
ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
NAME VARCHAR(64),
BIRTH_DAY DATE,
TEL VARCHAR(32),
ADDRESS VARCHAR(200)
);
III. 配置数据源
I. 在classpath
下新建一个数据库连接信息的配置文件jdbc.properties
jdbc.name=druid
jdbc.userName=root
jdbc.password=123456
jdbc.url=jdbc:mysql://localhost:3306/spring-annotation?characterEncoding=utf8&useSSL=true
jdbc.driverClassName=com.mysql.jdbc.Driver
II. 创建一个保存配置文件信息的类DataSourceConnection.java
package com.ddf.spring.annotation.bean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* @author DDf on 2018/8/13
*/
@Component
@PropertySource("classpath:jdbc.properties")
public class DataSourceConnection {
@Value("${jdbc.name}")
private String name;
@Value("${jdbc.userName}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public String toString() {
return "DataSourceConnection{" +
"name='" + name + '\'' +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
", driverClassName='" + driverClassName + '\'' +
", url='" + url + '\'' +
'}';
}
}
III.新建一个专门用户管理数据连接与数据相关的配置类,需要明确一点的是,配置类在同一个项目中可以存在多个,但是配置类必须被@CompontScan
扫描到才可以,所以可以项目中有一个主配置类,其他的配置类只要标注@Configuration
并且满足主配置类配置的@CompontScan
扫描规则即可,,就会被自动识别.@EnableTransactionManagement
这个注解标书在配置类上,则开启了基于注解版事务的支持,否则注解无效。这个注解最好放在主配置类上,这样更能方便的看到主配置类就能明确到当前项目开启了哪些功能注解,当然这个放在自己的配置类也是可以的。
主配置类加注解@EnableTransactionManagement
,其余省略与前面不变
@Configuration
@ComponentScan(value = "com.ddf.spring.annotation", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class),
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = ExcludeTypeFilter.class)
})
@Import(value = {ImportBean.class, CustomImportSelector.class, CustomImportBeanDefinitionRegistrar.class})
@EnableAspectJAutoProxy
@EnableTransactionManagement
public class AnnotationConfiguration {
//.............
}
新建配置类TransactionalConfiguration.java
,注入数据库连接DruidDataSource
,事务支持PlatformTransactionManager
以及数据库操作简化支持JdbcTemplate
package com.ddf.spring.annotation.configuration;
import com.alibaba.druid.pool.DruidDataSource;
import com.ddf.spring.annotation.bean.DataSourceConnection;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
/**
* @author DDf on 2018/8/14
* @Configuration 表名当时是一个配置类,如果自定义的扫描路径下包含了这个类,则该类会被自动识别成一个配置类
* @EnableTransactionManagement 开启一个基于注解的事务,已标注在主配置类
*/
@Configuration
public class TransactionalConfiguration {
/**
* 向容器中注入DruidDataSource,属性来自于DataSourceConnection,
* DataSourceConnection会自动从IOC容器中获取
* 目前采用的main函数的写法,手动指定一个主配置类,因为不是web环境,当前配置类没有指定扫描包,而是在主配置类上指定的,
* 所以当前类的这个方法的DataSourceConnection可能会提示没有这个bean,不用理会,如果是web环境就不会有问题了
* @param dataSourceConnection
* @return
*/
@Bean
public DruidDataSource druidDataSource(DataSourceConnection dataSourceConnection) {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setName(dataSourceConnection.getName());
druidDataSource.setUsername(dataSourceConnection.getUserName());
druidDataSource.setPassword(dataSourceConnection.getPassword());
druidDataSource.setUrl(dataSourceConnection.getUrl());
// druidDataSource可以不指定driverClassName,会自动根据url识别
druidDataSource.setDriverClassName(dataSourceConnection.getDriverClassName());
return druidDataSource;
}
/**
* 将数据源注入JdbcTemplate,再将JdbcTemplate注入到容器中,使用JdbcTemplate来操作数据库
* @param druidDataSource
* @return
*/
@Bean
public JdbcTemplate jdbcTemplate(DruidDataSource druidDataSource) {
return new JdbcTemplate(druidDataSource);
}
/**
* 将数据源注入PlatformTransactionManager,这是一个接口,使用DataSourceTransactionManager的实现来管理事务
* @param druidDataSource
* @return
*/
@Bean
public PlatformTransactionManager transactionManager(DruidDataSource druidDataSource) {
return new DataSourceTransactionManager(druidDataSource);
}
}
2.2 测试与总结
I. 新建一个Person
类
package com.ddf.spring.annotation.entity;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
/**
* @author DDf on 2018/8/14
*/
public class Person implements RowMapper<Person> {
private Integer id;
private String name;
private Date birthDay;
private String address;
private String tel;
public Person() {
}
public Person(Integer id, String name, Date birthDay, String address, String tel) {
this.id = id;
this.name = name;
this.birthDay = birthDay;
this.address = address;
this.tel = tel;
}
public Person(String name, Date birthDay, String address, String tel) {
this.name = name;
this.birthDay = birthDay;
this.address = address;
this.tel = tel;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthDay() {
return birthDay;
}
public void setBirthDay(Date birthDay) {
this.birthDay = birthDay;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
@Override
public Person mapRow(ResultSet resultSet, int i) throws SQLException {
Person person = new Person();
person.setId(resultSet.getInt("id"));
person.setName(resultSet.getString("name"));
person.setBirthDay(resultSet.getDate("birthDay"));
person.setAddress(resultSet.getString("address"));
person.setTel(resultSet.getString("tel"));
return person;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", birthDay=" + birthDay +
", address='" + address + '\'' +
", tel='" + tel + '\'' +
'}';
}
}
II. 新建PersonService
,增加添加person的方法,一个不使用事务,一个使用注解事务
package com.ddf.spring.annotation.service;
import com.ddf.spring.annotation.Application;
import com.ddf.spring.annotation.entity.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author DDf on 2018/8/14
*/
@Service
public class PersonService {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 不使用事务的方法保存一个person
* @param person
* @return
*/
public int add(Person person) {
String sql = "INSERT INTO PERSON(NAME, BIRTH_DAY, TEL, ADDRESS) VALUES (?, ?, ?, ?)";
int i = jdbcTemplate.update(sql, person.getName(), person.getBirthDay(), person.getTel(), person.getAddress());
if (i == 1) {
throw new RuntimeException("抛出异常");
}
return i;
}
/**
* 使用事务控制来保存一个person,在保存结束后抛出异常,看是否会回滚
* @param person
* @return
*/
@Transactional(rollbackFor = Exception.class)
public int addWithTransactional(Person person) {
String sql = "INSERT INTO PERSON(NAME, BIRTH_DAY, TEL, ADDRESS) VALUES (?, ?, ?, ?)";
int i = jdbcTemplate.update(sql, person.getName(), person.getBirthDay(), person.getTel(), person.getAddress());
if (i == 1) {
throw new RuntimeException("抛出异常");
}
return i;
}
}
III. 修改Application.java
,测试数据库连接是否正常以及测试person添加
/**
* 测试连接数据源
*/
public static void testDruidDataSource(ApplicationContext applicationContext) {
System.out.println("\n--------------------获取DruidDataSource数据库连接开始----------------------------");
DataSourceConnection dataSourceConnection = applicationContext.getBean(DataSourceConnection.class);
System.out.println(dataSourceConnection);
DruidDataSource druidDataSource = applicationContext.getBean(DruidDataSource.class);
DruidPooledConnection connection = null;
try {
connection = druidDataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
System.out.println(connection);
System.out.println("--------------------获取DruidDataSource数据库连接结束----------------------------\n");
}
/**
* 测试增加一个Person
* 使用事务和不使用事务对比
* @param applicationContext
*/
public static void testAddPerson(ApplicationContext applicationContext) {
System.out.println("\n-------------------测试增加一个Person开始-------------------------");
PersonService personService = applicationContext.getBean(PersonService.class);
Calendar calendar = new GregorianCalendar();
calendar.set(1992, 4, 29);
personService.add(new Person("no_transactional", calendar.getTime(), "上海市", "18356789999"));
personService.addWithTransactional(new Person("with_transactional", calendar.getTime(), "上海市", "18356789999"));
System.out.println("-------------------测试增加一个Person结束-------------------------\n");
}
完整版如下
package com.ddf.spring.annotation;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.ddf.spring.annotation.bean.*;
import com.ddf.spring.annotation.configuration.AnnotationConfiguration;
import com.ddf.spring.annotation.configuration.ProfileConfiguration;
import com.ddf.spring.annotation.entity.Person;
import com.ddf.spring.annotation.entity.User;
import com.ddf.spring.annotation.service.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* @author DDf on 2018/7/19
*/
public class Application {
public static void main(String[] args) {
System.out.println("-----------------------IOC容器初始化-------------------------");
// 创建一个基于配置类启动的IOC容器,如果主配置类扫描包的路径下包含其他配置类,则其他配置类可以被自动识别,如果主配置类扫描包的路径下包含其他配置类,则其他配置类可以被自动识别
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnnotationConfiguration.class);
System.out.println("-----------------------IOC容器初始化完成-------------------------\n");
// 获取当前IOC中所有bean的名称,即使是懒加载类型的bean也会获取到
printBeans(applicationContext);
// 测试@Scope bean的作用域
testPrototypeScopeService(applicationContext);
// 测试单实例bean的@Lazy懒加载
testLazyBeanService(applicationContext);
// 测试FactoryBean接口导入单实例与Prototype作用域的组件
testFactoryBeanPrototypeBean(applicationContext);
// 测试@PropertySource和@Value属性赋值
testPropertySourceValue(applicationContext);
// 测试@Autowired注入多个相同类型的Bean
testAutowired(applicationContext);
// 测试@Profile根据环境注入Bean
testProfile(applicationContext);
// 测试AOP
testAspect(applicationContext);
// 测试获取数据库连接
testDruidDataSource(applicationContext);
// 测试事务
testAddPerson(applicationContext);
// 销毁容器
applicationContext.close();
}
/**
* 打印当前IOC容器中所有预定义的Bean
* @param applicationContext
*/
private static void printBeans(AnnotationConfigApplicationContext applicationContext) {
String[] definitionNames = applicationContext.getBeanDefinitionNames();
// 打印当前IOC中对应名称的bean和bean的类型
for (String name : definitionNames) {
// 这个会影响到测试懒加载的效果,如果需要测试懒加载,这行代码需要注释掉,因为getBean方法一旦调用则会初始化
Object bean = applicationContext.getBean(name);
System.out.println("bean name:" + name + ", type: " + bean.getClass());
}
}
/**
* 测试@Scope bean的作用域
*
* @param applicationContext
*/
public static void testPrototypeScopeService(ApplicationContext applicationContext) {
System.out.println("\n-----------------------测试@Scope开始-------------------------");
UserService userService = (UserService) applicationContext.getBean("userService");
UserService userService1 = applicationContext.getBean(UserService.class);
System.out.println("默认单实例bean UserService是否相等 " + (userService == userService1));
PrototypeScopeService prototypeScopeService = applicationContext.getBean(PrototypeScopeService.class);
PrototypeScopeService prototypeScopeService1 = applicationContext.getBean(PrototypeScopeService.class);
System.out.println("PrototypeScopeService prototype scope作用域是否相等: " + (prototypeScopeService == prototypeScopeService1));
System.out.println("-----------------------测试@Scope结束-------------------------\n");
}
/**
* 测试单实例bean的懒加载,只有等使用的时候再创建实例。
* IOC容器启动后不会创建该bean的实例,如果是在该方法中才创建这个bean的实例,并且获得的两个bean是同一个的话,则测试通过。
*/
public static void testLazyBeanService(ApplicationContext applicationContext) {
System.out.println("\n---------------测试单实例bean的@Lazy懒加载开始----------------------");
LazyBeanService lazyBeanService = applicationContext.getBean(LazyBeanService.class);
LazyBeanService lazyBeanService1 = applicationContext.getBean(LazyBeanService.class);
System.out.println("lazyBeanService==lazyBeanService1?: " + (lazyBeanService == lazyBeanService1));
System.out.println("---------------测试单实例bean的@Lazy懒加载结束----------------------\n");
}
/**
* 测试通过FactoryBean接口导入单实例与Prototype作用域的组件,根据打印可以看出FactoryBean创建的单实例Bean都是懒加载的
* @param applicationContext
*/
public static void testFactoryBeanPrototypeBean(ApplicationContext applicationContext) {
System.out.println("\n----------测试通过FactoryBean注册单实例和Prototype作用域的组件开始----------");
FactorySingletonBean factorySingletonBean = applicationContext.getBean(FactorySingletonBean.class);
FactorySingletonBean factorySingletonBean1 = applicationContext.getBean(FactorySingletonBean.class);
FactoryPrototypeBean factoryPrototypeBean = applicationContext.getBean(FactoryPrototypeBean.class);
FactoryPrototypeBean factoryPrototypeBean1 = applicationContext.getBean(FactoryPrototypeBean.class);
System.out.println("单实例factorySingletonBean==factorySingletonBean1?" + (factorySingletonBean==factorySingletonBean1));
System.out.println("Prototype作用域factoryPrototypeBean==factoryPrototypeBean1?" + (factoryPrototypeBean==factoryPrototypeBean1));
System.out.println("----------测试通过FactoryBean注册单实例和Prototype作用域的组件结束----------\n");
}
/**
* 测试通过@PropertySource和@Value注解来对属性进行赋值
* @param applicationContext
*/
public static void testPropertySourceValue(ApplicationContext applicationContext) {
System.out.println("\n---------------测试@PropertySource和@Value赋值开始----------------");
User user = applicationContext.getBean(User.class);
System.out.println("user属性为: " + user.toString());
System.out.println("---------------测试@PropertySource和@Value赋值结束----------------\n");
}
/**
* 测试在IOC容器中存在两个相同类型的Bean,但是Bean的名称不一致
* 在这种情况下,使用@Autowired将该Bean注入到另外一个容器中
* @Autowired 默认使用Bean的类型去匹配注入,如果找到多个相同类型的Bean,则使用默认名称去获取Bean,Bean的默认名称为类首字母缩写
* 也可以配合@Autowired配合@Qualifier注解明确指定注入哪个Bean,还可以在注入Bean的地方使用@Primary,在不指定@Qualifier的情况下,
* 默认注入哪个Bean {@link AutowiredService}
* @param applicationContext
*/
public static void testAutowired(ApplicationContext applicationContext) {
System.out.println("\n--------------测试autowired注入多个相同类型的类开始-----------------");
AutowiredBean autowiredBean = (AutowiredBean) applicationContext.getBean("autowiredBean");
AutowiredBean autowiredBean2 = (AutowiredBean) applicationContext.getBean("autowiredBean2");
System.out.println("autowiredBean: " + autowiredBean);
System.out.println("autowiredBean2: " + autowiredBean2);
System.out.println(autowiredBean == autowiredBean2);
/**
* 这里已做更改,修改了默认注入 {@link com.ddf.spring.annotation.configuration.AnnotationConfiguration.autowiredBean2}
*/
AutowiredService autowiredService = applicationContext.getBean(AutowiredService.class);
AutowiredBean autowiredServiceBean = autowiredService.getAutowiredBean();
System.out.println("使用@Primay后AutowiredService默认注入bean: " + autowiredServiceBean);
AutowiredBean autowiredServiceBean2 = autowiredService.getQualifierAutowiredBean();
System.out.println("使用@Qualifier明确注入Bean: " + autowiredServiceBean2);
// 使用@Resource注入
AutowiredBean resourceAutowiredBean = autowiredService.getResourceAutowiredBean();
System.out.println("使用@Resource注入autowiredBean: " + resourceAutowiredBean);
AutowiredBean resourceAutowiredBean2 = autowiredService.getResourceAutowiredBean2();
System.out.println("使用@Resource注入autowiredBean2: " + resourceAutowiredBean2);
// 使用@Inject注入
UserService userService = autowiredService.getUserService();
System.out.println("使用@Inject注入UserService: " + userService);
System.out.println("--------------测试autowired注入多个相同类型的类结束-----------------\n");
}
/**
* 测试根据激活的Profile来根据环境注册不同的Bean
* 切换profile有两种方式:
* 1. 在java虚拟机启动参数加 -Dspring.profiles.active=test
* 2. 如下演示,使用代码切换,可以将切换的变量放在配置文件,spring-boot配置文件即是这种方式
* @param applicationContext
*/
public static void testProfile(AnnotationConfigApplicationContext applicationContext) {
System.out.println("\n------------------测试@Profile开始-------------------------");
// 重新新建一个IOC容器
applicationContext = new AnnotationConfigApplicationContext();
// 注册配置类
applicationContext.register(ProfileConfiguration.class);
// 设置当前激活的环境profile
applicationContext.getEnvironment().setActiveProfiles("dev");
// 刷新容器
applicationContext.refresh();
// 使用接口获得实际接口实现类的注入Bean
Slf4jBean devLogBean = applicationContext.getBean(Slf4jBean.class);
devLogBean.info("测试环境");
applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(ProfileConfiguration.class);
applicationContext.getEnvironment().setActiveProfiles("prd");
applicationContext.refresh();
Slf4jBean prdLogBean = applicationContext.getBean(Slf4jBean.class);
prdLogBean.info("生产环境");
System.out.println("------------------测试@Profile结束-------------------------\n");
}
/**
* 测试AOP
* @param applicationContext
*/
public static void testAspect(ApplicationContext applicationContext) {
System.out.println("\n--------------------测试AOP开始----------------------------");
UserService userService = applicationContext.getBean(UserService.class);
userService.welcome("ddf");
try {
userService.welcomeException("ddf");
} catch (Exception e) {
}
System.out.println("--------------------测试AOP结束----------------------------\n");
}
/**
* 测试连接数据源
*/
public static void testDruidDataSource(ApplicationContext applicationContext) {
System.out.println("\n--------------------获取DruidDataSource数据库连接开始----------------------------");
DataSourceConnection dataSourceConnection = applicationContext.getBean(DataSourceConnection.class);
System.out.println(dataSourceConnection);
DruidDataSource druidDataSource = applicationContext.getBean(DruidDataSource.class);
DruidPooledConnection connection = null;
try {
connection = druidDataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
System.out.println(connection);
System.out.println("--------------------获取DruidDataSource数据库连接结束----------------------------\n");
}
/**
* 测试增加一个Person
* 使用事务和不使用事务对比
* @param applicationContext
*/
public static void testAddPerson(ApplicationContext applicationContext) {
System.out.println("\n-------------------测试增加一个Person开始-------------------------");
PersonService personService = applicationContext.getBean(PersonService.class);
Calendar calendar = new GregorianCalendar();
calendar.set(1992, 4, 29);
try {
personService.add(new Person("no_transactional", calendar.getTime(), "上海市", "18356789999"));
} catch (Exception e) {}
try {
personService.addWithTransactional(new Person("with_transactional", calendar.getTime(), "上海市", "18356789999"));
} catch (Exception e) {}
System.out.println("-------------------测试增加一个Person结束-------------------------\n");
}
}
IIII. 运行主启动类,可以看到日志如下,省略不相关其它日志,数据库连接正常连接,然后测试保存的方法里每一个都抛出了异常,但是查看数据库记录,只有第一个用户no_transactional
保存了下来,另外一条记录回滚并没有保存下来
--------------------获取DruidDataSource数据库连接开始----------------------------
DataSourceConnection{name='druid', userName='root', password='123456', driverClassName='com.mysql.jdbc.Driver', url='jdbc:mysql://localhost:3306/spring-annotation?characterEncoding=utf8&useSSL=true'}
八月 14, 2018 1:09:14 下午 com.alibaba.druid.pool.DruidDataSource info
信息: {dataSource-1,druid} inited
com.mysql.jdbc.JDBC4Connection@36cda2c2
--------------------获取DruidDataSource数据库连接结束----------------------------
-------------------测试增加一个Person开始-------------------------
add开始执行,参数列表[Person{id=null, name='no_transactional', birthDay=Fri May 29 13:09:14 CST 1992, address='上海市', tel='18356789999'}]
add结束执行,参数列表[Person{id=null, name='no_transactional', birthDay=Fri May 29 13:09:14 CST 1992, address='上海市', tel='18356789999'}]
add结束执行,参数列表[Person{id=null, name='no_transactional', birthDay=Fri May 29 13:09:14 CST 1992, address='上海市', tel='18356789999'}],出现异常: java.lang.RuntimeException: 抛出异常
addWithTransactional开始执行,参数列表[Person{id=null, name='with_transactional', birthDay=Fri May 29 13:09:14 CST 1992, address='上海市', tel='18356789999'}]
addWithTransactional结束执行,参数列表[Person{id=null, name='with_transactional', birthDay=Fri May 29 13:09:14 CST 1992, address='上海市', tel='18356789999'}]
addWithTransactional结束执行,参数列表[Person{id=null, name='with_transactional', birthDay=Fri May 29 13:09:14 CST 1992, address='上海市', tel='18356789999'}],出现异常: java.lang.RuntimeException: 抛出异常
-------------------测试增加一个Person结束-------------------------