@Configuration类注解等同于xml配置文件配置类
@ComponentScan 包扫描注解相当于<context:component-scan base-package="com.itheima"/>
注意:扫描的包路径不写,扫描当前包及其子包!
零配置IOC容器
加载纯注解格式上下文对象,需要使用AnnotationConfigApplicationContext // SpringConfig是一个配置类(有@Configuration注解)
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
@Service @Controller 用在指定的类上,用来替代bean标签。
@Autowired 用在需要注入的属性上,完成自动属性注入。
@Configuration 用在配置类上,用来替代spring核心配置xml文件。
@ComponentScan("com.itheima") 用来进行注解包的扫描
new AnnotationConfigApplicationContext(SpringConfig.class) 用于创建注解spring容器。
Spring整合Druid
@Bean注解
- 类型:方法注解
- 作用:将方法的返回值存储到Spring IOC容器中
- @Bean("user1") bean的id可以指定,默认是方法名 作用:将方法的返回值存储到Spring IOC容器中
public User user(){ return new User(1,"xiaoming",new Date(),"male","china"); } 相当于XML配置文件中的标签
@Value注解
作用:注入非引用类型,设置属性的值或对方法进行传参 @Value("${jdbc.username}") private String username;
- @value一般用于读取properties文件中的属性值
- 注解的方式注入非引用类型,不需要setter方法
@Import注解
- 类型:类注解 作用:导入其他配置类(对象) //RedisConfig是另外一个配置类 @Import(RedisConfig.class) public class SpringConfig{
}
相当于XML配置文件中的<import>标签
注意:也可以直接将配置类放在注解扫描器可以扫描的位置,就不需要再使用@Import注解导入了。
@PropertySource注解 引入外部属性文件(jdbc.properties)
相当于XML中<context:property-placeholder location="classpath:jdbc.properties"/> 完善 JdbcConfig 类的功能
1:使用注解形式完成外部属性文件(jdbc.properties)的引入。参考注解:@PropertySource
2: 将引入的文件中的属性注入到 成员变量上。参考注解: @Value注解
3: 将获取数据库连接池方法返回值,当做bean教给Spring管理.参考注解@Bean。
4: SpringConfig配置类 整合 JdbcConfig配置类。参考注解@Import。
5: 完成Test02测试,从Spring容器中获取连接池对象。
JdbcConfig.java
就是 获取了一个德鲁伊连接池对象
-
有几个问题要思考 -
1: 配置属性信息如何完成赋值? -
2: 连接池对象如何教给spring管理
//创建数据源对象并返回 DruidDataSource druidDataSource = new+++ DruidDataSource(); @PropertySource 用在配置类上,用于解析配置文件。
@Value 用在指定成员变量上,用于获取配置文件属性。
@Import(JdbcConfig.class) 用在主要配置类上,用来引入其他配置类。 配置初始化Spring容器的配置文件(配置类)
- @RunWith(SpringJUnit4ClassRunner.class)
- 注解作用:spring的单元测试运行器替换原有运行器
- @ContextConfiguration(classes = SpringConfig.class)
- 注解作用:指定配置类或者配置文件所在位置
- classes属性:设置配置类
- locations属性:设置配置文件 */
AOP - 面向切面编程--对管理的bean进行功能增强
- 怎么把横切逻辑再融合到原有业务逻辑上,达到和原来一样的效果?植入
这些功能最终是通过代理对象来封装。代理对象功能=代理增强功能+原对象原功能。
**Joinpoint(连接点)** : 所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
举例:所有的方法都是连接点
Pointcut(切入点) : 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
举例:需要增加事务功能,日志功能的方法都是切入点
Advice(通知) : 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
举例:增加的事务和日志功能就是 通知。
Introduction(引介) 【了解】: 引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。
Target(目标对象) :代理的目标对象。
举例:AccountServiceImpl类需要增加 事务和日志功能,他就是目标对象
Weaving(织入) : 是指把增强应用到目标对象来创建新的代理对象的过程。 spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
举例:把事务和日志功能动态添加到AccountServiceImpl对象的过程就是织入。
Proxy(代理) : 一个类被AOP织入增强后,就产生一个结果代理类。
举例:AccountServiceImpl中的方法 得到了事务和日志增强,就生成了代理对象。
Aspect(切面) : 是切入点和通知(引介)的结合。
举例:事务方法 加上 事务功能 就是一个切面
案例方法的执行效率currentTimeMillis
##### 创建一个新的类,将共性功能提取出来制作成独立的方法
AOPAdvice 通知类
步骤3:在新的类中定义一个方法,无参无返回值,方法体为空。上面添加一个注解@Pointcut
public class AOPAdvice {//只对你有感觉UserServiceImpl
// // 设置连接点 上有切入点 准备切谁
@Pointcut("execution( * com.itheima.service.impl.*.*(..))")
public void pc(){}
}
//Pointcut(切入点): 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
##### 步骤4:绑定原始操作与被抽取的功能之间的关系
public class AOPAdvice {
@Pointcut("execution(void com.itheima.service.impl.UserServiceImpl.save(..))")
//标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
private void pt() {
}
@Before("AOPAdvice.pt()")
public void before(){
System.out.println("功能执行前系统时间:" + System.currentTimeMillis());
}
@After("pt()")
public void afterSave(){
System.out.println("功能执行后系统时间:" + System.currentTimeMillis());
}
##### 步骤5:声明切面类
@Component 把当前类存入spring容器
@Aspect
Aspect(切面): 是切入点和通知(引介)的结合。
举例:事务方法 加上 事务功能 就是一个切面
public class AOPAdvice {
}
##### 步骤6:在Spring配置类上声明开启AOP功能
++++@Configuration 配置类
@ComponentScan("com.itheima")包扫描
@Enable开启AspectJAutoProxy
public class SpringConfig {
}
没有@EnableAspectJAutoProxy这个注解,注解是AOP是无效的,千万记得,很容易忘记写这个
// 举例:需要增加事务功能,日志功能的方法都是切入点
execution是执行的意思,整体含义定义了一个执行位置,这个位置是void返回值,,包名是com.itheima.service.impl这个,类名是UserServiceImpl,方法名是save,没有参数的方法。
参数也可以,携带两个参数可以写成( * , * ),携带两个参数,第一个int,第二个任意,就写成 ( int , * )
+:专用于匹配子类类型 这个使用率较低,描述子类的,
0. 包名书写尽量不使用..匹配,效率过低,常用*做单个包描述匹配,或精准匹配
0. 通常不使用异常作为匹配规则
起别名 @AliasFor("a2") String a1() default ""; @AliasFor("a1") String a2() default "";
之前xml获取
@Test
public void beanTest() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取 account对象
// account对象获取有三种形式 要求掌握一种
Object account = context.getBean("account");
// 第二种方式
Account bean = context.getBean(Account.class);
// 第三种方式 我们建议使用第三种
Account account1 = context.getBean("account", Account.class);
System.out.println("我是debug看对象的");
}
- 环绕通知 @Around 追加功能到方法执行前后(重要)
- 环绕通知 @Around 追加功能到方法执行前后(重要)
- 位置:通知方法定义上方
- 属性value(默认):切入点方法名,格式为类名.方法名()
@Around("AOPAdvice.pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前around before...");
Object ret = pjp.proceed();
System.out.println("环绕后around after...");
return ret;
}
0. 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
0. 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型
0. 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
0. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//获取执行签名信息
Signature signature = pjp.getSignature();
//通过签名获取执行类型(接口名)
String className = signature.getDeclaringTypeName();
//通过签名获取执行操作名称(方法名)
String methodName = signature.getName();
//执行时长累计值
long sum = 0L;
for (int i = 0; i < 10000; i++) {
//获取操作前系统时间beginTime
long startTime = System.currentTimeMillis();
//原始操作调用
pjp.proceed();
//获取操作后系统时间endTime
long endTime = System.currentTimeMillis();
sum += endTime-startTime;
}
System.out.println(className+":"+methodName+" (万次)run:"+sum+"ms");
return null;
}
这段程序中加了一个东西,使用ProceedingJoinPoint对象可以获得一个Signature对象。
通过Signature这个东西可以拿到当前执行的类、方法,这样最后打印输出的时候就能区分出
- JoinPoint:适用于前置、后置、返回后、抛出异常后通知
- ProccedJointPoint:适用于环绕通知
只要见到字符串,先去掉输入的数据中前后的无效空格数据。
trim()```操作去掉空格args[i] = args[i].toString().trim();,
pjp.proceed()```可以得到返回值
环绕通知最强大的地方,不仅可以修改参数,还可以修改返回值。
//调用原始操作,使用修改后的参数替换原始参数 Object ret = pjp.proceed(args);
@Around先获取参数,然后调用的时候修改参数就可以了
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
//对原始参数的每一个参数进行操作
for (int i = 0; i < args.length; i++) {
//如果是字符串数据
if(args.getClass().equals(String.class)){
//取出数据,trim()操作后,更新数据
args[i] = args[i].toString().trim();
}
}
//调用原始操作,使用修改后的参数替换原始参数
Object ret = pjp.proceed(args);
return ret;
}