注解注入
1、创建bean
(1)在创建IOC容器时,Spring 能够从 classpath 下自动扫描, 侦测和实例化具有特定注解的组件
特定注解包括:@Component(基本注解) @Repository(标识持久层) @Service(标识服务层) @Cotroller(标识控制层)
这四个注解对Spring而言没有什么区别。
在创建bean时,默认采用类名第一个字母小写作为bean的id。也可以通过注解的value属性制定bean的id。
(2)需要使用context:component-scan的base-package关键字指定需要扫描的包,因此需要引入context命名空间。Spring会扫描该包以及其子包,如果需要扫描多个包,则用逗号分隔。
(3)通过resource-pattern来过滤指定的类
(4)使用context:include-filter和context:exclude-filter子节点来过滤指定的类。
此时需要制定type和expression关键字。
type常用的有两种,annotation(所有标注了expression指定的注解的类都不扫描或者扫描)
assinable(所有继承或扩展了expression指定的类的类都不扫描或者扫描)
注意:context:include-filter需要先将use-default-filters关键字指定为false才能实现 只扫描指定的类。
<context:component-scan base-package="com.robin.spring"
resource-pattern="annotation/*.class"></context:component-scan>
<context:component-scan base-package="com.robin.spring">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"></context:exclude-filter>
<context:exclude-filter type="assignable" expression="com.robin.spring.controller.UserController"></context:exclude-filter>
</context:component-scan>
<context:component-scan base-package="com.robin.spring" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"></context:include-filter>
</context:component-scan>
2、使用bean
通过@Autowired、@Resource、@Inject来注入bean。注解可添加到构造器、变量、方法上(很多时候还会添加在形参的前面)
@Autowired,先按照类型匹配,如果同一类型有多个bean,则按照名称匹配。有一个required属性,若为true,则没有对应的bean会报错,如果为false,没有对应的bean则直接给null,默认为true。
@Resource,直接按照名称匹配,没有required属性。
@Inject,和Autowired一样,但是没有required属性。
注意:Autowired可以用于集合之上,则spring会扫描所有匹配的bean加入集合。如果为Map,且key为String,则会将bean的id作为key将bean作为value添加到Map里。
3、泛型依赖注入
Spring4之前并不支持泛型依赖注入,那么BaseRepository<User>和BaseRepository<Role>将会被认为是同一种类型的bean
Spring4之后支持泛型依赖注入,那么BaseRepository<User>和BaseRepository<Role>将会被认为是不同类型的bean
动态代理
实现Hook的一种方式,需要调用Proxy.newProxyInstance(classLoader,interfaces,h)方法来创建代理对象,classLoader是被代理对象的类加载器,interfaces是被代理对象对应的接口类的所有接口方法集合,h是处理函数。此方法内部实现是:创建一个新的被代理对象对应接口的实现类,类中的每个接口的实现均调用传入的h方法。然后实例化一个新类的对象,并返回。
public class ArithmeticCalculatorProxy {
private ArithmeticCalculator target;
public ArithmeticCalculatorProxy(ArithmeticCalculator arithmeticCalculator) {
target = arithmeticCalculator;
}
public ArithmeticCalculator getCalculator() {
ArithmeticCalculator proxy = null;
ClassLoader classLoader = target.getClass().getClassLoader();
Class[] interfaces = target.getClass().getInterfaces();
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invoke " + method.getName());
Object result = method.invoke(target,args);
System.out.println("After invoke " + method.getName());
return result;
}
};
proxy = (ArithmeticCalculator) Proxy.newProxyInstance(classLoader,interfaces,h);
return proxy;
}
}
AOP(spring内部用动态代理实现)
1、一些术语
切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
通知(Advice): 切面必须要完成的工作
目标(Target): 被通知的对象
代理(Proxy): 向目标对象应用通知之后创建的对象
连接点(Joinpoint):程序执行的某个特定位置
切点(pointcut):用于查询连接点
2、实现
(1)引入maven仓库:spring-aop、aspectjrt、aspectjweaver。
(2)在配置文件中引入aop和context命名空间,配置context:component-scan和aop:aspectj-autoproxy
(3)将切面抽象成一个类,并添加@Component和@Aspect注解。
(4)在切面类中添加处理方法,并在方法上加上 通知注解 来标明方法相对于 目标 的执行位置。
(5)如果处理方法需要使用连接点的详细信息时,可以在方法中加入JoinPoint形参来获取
注:通知注解有:
@Before(前置处理)
@After(后置处理,目标执行结束或者抛异常后都会执行,但是不能访问目标的执行结果)、
@AfterReturning(返回通知)
@AfterThrowing(异常通知)
@Around(环绕通知,拦截方法的执行,一定要有返回值)
在注解内需要指明目标,@Before("execute(返回值 接口名.方法名(形参列表))")
try {
//前置通知
method.invoke(); //环绕通知会在此拦截方法的执行,直接将此行替换成 环绕通知
//返回通知
}catch (Exception e) {
//异常通知
}
//后置通知
注:当同一个方法有多个关联的切面时,切面之间的执行顺序可以通过@Order(num)来指定,num值越小优先级越高
3、为了使通知表达式可重用,可以利用@Pointcut声明切点
@Order(1)
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(double com.robin.spring.aop.DataService.*(*,*))")
public void declarationPointCut() {
}
@Before("com.robin.spring.aop.LoggingAspect.declarationPointCut()")
public void beforeMethod(JoinPoint joinPoint) {
System.out.println("Before Method " + joinPoint.getSignature().getName() + " with param " + Arrays.asList(joinPoint.getArgs()));
}
@After("com.robin.spring.aop.LoggingAspect.declarationPointCut()")
public void afterMethod(JoinPoint joinPoint) {
System.out.println("After method " + joinPoint.getSignature().getName());
}
@AfterThrowing(value = "com.robin.spring.aop.LoggingAspect.declarationPointCut()", throwing = "exception")
public void afterMethodThrowing(Exception exception) {
System.out.println("Throw exception " + exception.getMessage());
}
@AfterReturning(value = "com.robin.spring.aop.LoggingAspect.declarationPointCut()",returning = "result")
public void afterMethodReturn(JoinPoint joinPoint, Object result) {
System.out.println("Method return " + result);
}
@Around("com.robin.spring.aop.LoggingAspect.declarationPointCut()")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) {
Object result = null;
Object param[] = proceedingJoinPoint.getArgs();
if ((Integer)param[1] == 0) {
param[1] = 1;
}
try {
result = proceedingJoinPoint.proceed(param);
} catch (Throwable e) {
System.out.println(e);
}
return result;
}
}
4、基于配置文件的aop
先生成服务和切面的bean,然后配置aop,在配置aop时,先生成切点的bean,然后再配置通知
<bean id="dataService" class="com.robin.spring.config.DataServiceImpl"></bean>
<bean id="aspect" class="com.robin.spring.config.LoggingAspect"></bean>
<aop:config>
<aop:pointcut id="pointCut" expression="execution(double com.robin.spring.config.DataService.*(*,*))"/>
<aop:aspect ref="aspect" order="1">
<aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
<aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
<aop:after-throwing method="afterMethodThrowing" pointcut-ref="pointCut" throwing="exception"></aop:after-throwing>
<aop:after-returning method="afterMethodReturn" pointcut-ref="pointCut" returning="result"></aop:after-returning>
<aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
</aop:config>
JDBC(jdbcTemplate / namedPrameterJdbcTemplate)
1、maven依赖
引入spring-jdbc、mysql-connector-java、c3p0的maven依赖
2、配置
定义一个C3P0数据源bean,然后定义一个jdbcTemplate或者namedPrameterJdbcTemplate的bean。
<context:property-placeholder location="db.properties"></context:property-placeholder>
<!--连接c3p0数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="jdbcNamedTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
注意,定义数据源时,使用了属性文件,其中
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/robin_practice
url规则:jdbc:mysql://(host): 端口(MySql的默认端口是3306) / 数据库名
3、使用
使用时调用jdbcTemplate.update(),query(),queryForObject()等来进行增删改查。
@Test
public void testNameJdbc() {
String sql = "INSERT INTO employees(id,name,DEPT_ID) VALUES(:id,:name,:deptId)";
EmployeeDao employeeDao = new EmployeeDao();
employeeDao.setDeptId(6);
employeeDao.setId(6L);
employeeDao.setName("cc");
SqlParameterSource paramSource = new BeanPropertySqlParameterSource(employeeDao);
namedParameterJdbcTemplate.update(sql,paramSource);
}
@Test
public void testUpdate() {
String sql = "UPDATE employees SET name = ? WHERE id = ?";
jdbcTemplate.update(sql,"robin",1);
}
@Test
public void batchInsert() {
String sql = "INSERT INTO employees(id, name, DEPT_ID) VALUES(?,?,?)";
List<Object[]> batchArgs = new ArrayList<Object[]>();
batchArgs.add(new Object[]{4,"aa",4});
batchArgs.add(new Object[]{5,"bb",5});
jdbcTemplate.batchUpdate(sql,batchArgs);
}
@Test
public void testSelect() {
String sql = "SELECT id,name,DEPT_ID deptId FROM employees WHERE id = ?";
RowMapper<EmployeeDao> rowMapper = new BeanPropertyRowMapper<EmployeeDao>(EmployeeDao.class);
EmployeeDao employeeDao = jdbcTemplate.queryForObject(sql,rowMapper,4);
System.out.println(employeeDao);
}
}
4、NameParameterJdbcTemplate
NameParameterJdbcTemplate是具名的JdbcTemplate,其sql语句的变量不是用?表示而是用变量名表示,然后调用update等方法时,传入Map<String,Object>或者SqlParameterSource。
事务
1、定义
保证多个数据库操作的原子性,如果某一个操作失败,则回滚。
2、配置
需要先定义一个transactionManager的bean,然后再使用tx:annotation-driven关键字让注解起效。
<context:property-placeholder location="db.properties"></context:property-placeholder>
<context:component-scan base-package="com.robin.tx"></context:component-scan>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
3、使用
在需要用事务的方法上添加事务注解@Transactional就可以实现事务了。
4、事务的传播行为
使用propagation属性指定事务的传播行为,默认为REQUIRED(只要有事务,则以最外层事务为准)还可以指定为REQUIRES_NEW(以内部事务为准,进入内部事务时外部事务失效),需要添加在内部事务上。
注:同时买多本书时,整个买操作为外部事务,买单本书为内部事务。required是指一旦有异常,则回滚到原始状态,required_new则表示哪本书有异常哪本书回滚。
例子:@Transactional(propagation = Propagation.REQUIRES_NEW)
5、事务的隔离级别
使用isolation属性制定隔离级别
6、指定异常回滚
默认对所有运行时异常回滚。使用rollbackFor、noRollbackFor、rollbackForClassName、noRollbackForClassName属性设置指定异常的回滚。一般默认就行。
7、优化
可以指定readOnly属性来帮助数据库引擎优化事务。
使用timeOut指定强制回滚之前事务占用时间(如果事务占用时间超过指定时间,则强制回滚)